mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-11 09:46:37 +08:00
refactor(arch): decouple private imports, enforce contracts and comply with cordis architecture
This commit is contained in:
+71
-74
@@ -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 Code generated by swaggo/swag. DO NOT EDIT
|
||||||
package docs
|
package docs
|
||||||
|
|
||||||
@@ -2678,7 +2675,7 @@ const docTemplate = `{
|
|||||||
"data": {
|
"data": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Schedule"
|
"$ref": "#/definitions/model.Schedule"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2740,7 +2737,7 @@ const docTemplate = `{
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {
|
"data": {
|
||||||
"$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Schedule"
|
"$ref": "#/definitions/model.Schedule"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2822,7 +2819,7 @@ const docTemplate = `{
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {
|
"data": {
|
||||||
"$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Schedule"
|
"$ref": "#/definitions/model.Schedule"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3011,7 +3008,7 @@ const docTemplate = `{
|
|||||||
"data": {
|
"data": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Template"
|
"$ref": "#/definitions/model.Template"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3149,7 +3146,7 @@ const docTemplate = `{
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {
|
"data": {
|
||||||
"$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Template"
|
"$ref": "#/definitions/model.Template"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3229,7 +3226,7 @@ const docTemplate = `{
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {
|
"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": {
|
"Wavelet_plugins_domain_admin_model.SystemConfig": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"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": {
|
"agent.ActiveConfigMeta": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"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": {
|
"model.SystemStatusResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -18038,6 +18000,41 @@ const docTemplate = `{
|
|||||||
"TaskExecutionStatusFailed"
|
"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": {
|
"model.TestSMTPRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|||||||
+71
-71
@@ -2668,7 +2668,7 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Schedule"
|
"$ref": "#/definitions/model.Schedule"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2730,7 +2730,7 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {
|
"data": {
|
||||||
"$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Schedule"
|
"$ref": "#/definitions/model.Schedule"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2812,7 +2812,7 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {
|
"data": {
|
||||||
"$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Schedule"
|
"$ref": "#/definitions/model.Schedule"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3001,7 +3001,7 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Template"
|
"$ref": "#/definitions/model.Template"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3139,7 +3139,7 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {
|
"data": {
|
||||||
"$ref": "#/definitions/Wavelet_plugins_domain_admin_model.Template"
|
"$ref": "#/definitions/model.Template"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3219,7 +3219,7 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"data": {
|
"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": {
|
"Wavelet_plugins_domain_admin_model.SystemConfig": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"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": {
|
"agent.ActiveConfigMeta": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"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": {
|
"model.SystemStatusResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -18028,6 +17993,41 @@
|
|||||||
"TaskExecutionStatusFailed"
|
"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": {
|
"model.TestSMTPRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|||||||
+49
-49
@@ -156,26 +156,6 @@ definitions:
|
|||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
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:
|
Wavelet_plugins_domain_admin_model.SystemConfig:
|
||||||
properties:
|
properties:
|
||||||
created_at:
|
created_at:
|
||||||
@@ -233,29 +213,6 @@ definitions:
|
|||||||
updated_at:
|
updated_at:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
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:
|
agent.ActiveConfigMeta:
|
||||||
properties:
|
properties:
|
||||||
checksum:
|
checksum:
|
||||||
@@ -1890,6 +1847,26 @@ definitions:
|
|||||||
value:
|
value:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
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:
|
model.SystemStatusResponse:
|
||||||
properties:
|
properties:
|
||||||
alloc:
|
alloc:
|
||||||
@@ -2004,6 +1981,29 @@ definitions:
|
|||||||
- TaskExecutionStatusRunning
|
- TaskExecutionStatusRunning
|
||||||
- TaskExecutionStatusSucceeded
|
- TaskExecutionStatusSucceeded
|
||||||
- TaskExecutionStatusFailed
|
- 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:
|
model.TestSMTPRequest:
|
||||||
properties:
|
properties:
|
||||||
smtp_host:
|
smtp_host:
|
||||||
@@ -5873,7 +5873,7 @@ paths:
|
|||||||
- properties:
|
- properties:
|
||||||
data:
|
data:
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/Wavelet_plugins_domain_admin_model.Schedule'
|
$ref: '#/definitions/model.Schedule'
|
||||||
type: array
|
type: array
|
||||||
type: object
|
type: object
|
||||||
"401":
|
"401":
|
||||||
@@ -5910,7 +5910,7 @@ paths:
|
|||||||
- $ref: '#/definitions/response.Any'
|
- $ref: '#/definitions/response.Any'
|
||||||
- properties:
|
- properties:
|
||||||
data:
|
data:
|
||||||
$ref: '#/definitions/Wavelet_plugins_domain_admin_model.Schedule'
|
$ref: '#/definitions/model.Schedule'
|
||||||
type: object
|
type: object
|
||||||
"400":
|
"400":
|
||||||
description: Cron 表达式无效、异步任务类型不存在或参数错误
|
description: Cron 表达式无效、异步任务类型不存在或参数错误
|
||||||
@@ -6001,7 +6001,7 @@ paths:
|
|||||||
- $ref: '#/definitions/response.Any'
|
- $ref: '#/definitions/response.Any'
|
||||||
- properties:
|
- properties:
|
||||||
data:
|
data:
|
||||||
$ref: '#/definitions/Wavelet_plugins_domain_admin_model.Schedule'
|
$ref: '#/definitions/model.Schedule'
|
||||||
type: object
|
type: object
|
||||||
"400":
|
"400":
|
||||||
description: Cron 表达式无效、参数错误
|
description: Cron 表达式无效、参数错误
|
||||||
@@ -6072,7 +6072,7 @@ paths:
|
|||||||
- properties:
|
- properties:
|
||||||
data:
|
data:
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/Wavelet_plugins_domain_admin_model.Template'
|
$ref: '#/definitions/model.Template'
|
||||||
type: array
|
type: array
|
||||||
type: object
|
type: object
|
||||||
"401":
|
"401":
|
||||||
@@ -6200,7 +6200,7 @@ paths:
|
|||||||
- $ref: '#/definitions/response.Any'
|
- $ref: '#/definitions/response.Any'
|
||||||
- properties:
|
- properties:
|
||||||
data:
|
data:
|
||||||
$ref: '#/definitions/Wavelet_plugins_domain_admin_model.Template'
|
$ref: '#/definitions/model.Template'
|
||||||
type: object
|
type: object
|
||||||
"401":
|
"401":
|
||||||
description: 未登录
|
description: 未登录
|
||||||
@@ -6249,7 +6249,7 @@ paths:
|
|||||||
- $ref: '#/definitions/response.Any'
|
- $ref: '#/definitions/response.Any'
|
||||||
- properties:
|
- properties:
|
||||||
data:
|
data:
|
||||||
$ref: '#/definitions/Wavelet_plugins_domain_admin_model.Template'
|
$ref: '#/definitions/model.Template'
|
||||||
type: object
|
type: object
|
||||||
"400":
|
"400":
|
||||||
description: 参数错误
|
description: 参数错误
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"Wavelet/openflare/plugins/server/kernel/credential"
|
"Wavelet/openflare/plugins/server/kernel/credential"
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@@ -60,8 +59,8 @@ func setupCloudflareLogicDB(t *testing.T) (context.Context, uint) {
|
|||||||
); err != nil {
|
); err != nil {
|
||||||
t.Fatalf("AutoMigrate() error = %v", err)
|
t.Fatalf("AutoMigrate() error = %v", err)
|
||||||
}
|
}
|
||||||
db.SetDB(conn)
|
repository.SetDBForTest(conn)
|
||||||
t.Cleanup(func() { db.SetDB(nil) })
|
t.Cleanup(func() { repository.SetDBForTest(nil) })
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
sealed, err := credential.Seal(`{"api_token":"test-token"}`)
|
sealed, err := credential.Seal(`{"api_token":"test-token"}`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -143,11 +142,11 @@ func TestCreateMemberCopiesGroupDefaultProxied(t *testing.T) {
|
|||||||
t.Fatalf("GetCFPointingGroup() error = %v", err)
|
t.Fatalf("GetCFPointingGroup() error = %v", err)
|
||||||
}
|
}
|
||||||
zone := model.Zone{Domain: "example.net"}
|
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)
|
t.Fatalf("Create(zone) error = %v", err)
|
||||||
}
|
}
|
||||||
domain := model.ZoneDomain{ZoneID: zone.ID, Domain: "www.example.net"}
|
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)
|
t.Fatalf("Create(domain) error = %v", err)
|
||||||
}
|
}
|
||||||
restore := SetDispatchTaskForTest(func(context.Context, string, []byte, string) (string, error) { return "task-1", nil })
|
restore := SetDispatchTaskForTest(func(context.Context, string, []byte, string) (string, error) { return "task-1", nil })
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||||
"Wavelet/pkg/response"
|
"Wavelet/pkg/response"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -58,7 +57,7 @@ func TestGetGroupWithOrphanedMemberHealsAndSucceeds(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Simulate orphaned member by deleting the ZoneDomain directly
|
// 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)
|
t.Fatalf("DELETE FROM of_zone_domains error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -28,10 +27,10 @@ func setupDashboardTestDB(t *testing.T) func() {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, sqliteDB.AutoMigrate(&model.OpenFlareNode{}))
|
require.NoError(t, sqliteDB.AutoMigrate(&model.OpenFlareNode{}))
|
||||||
|
|
||||||
db.SetDB(sqliteDB)
|
repository.SetDBForTest(sqliteDB)
|
||||||
testhelper.SetupLogStoresForTest(t)
|
testhelper.SetupLogStoresForTest(t)
|
||||||
return func() {
|
return func() {
|
||||||
db.SetDB(nil)
|
repository.SetDBForTest(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +42,7 @@ func TestGetOverviewStructure(t *testing.T) {
|
|||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
lastSeen := now.Add(-15 * time.Second) // within default 60s offline threshold
|
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",
|
NodeID: "node-dashboard-1",
|
||||||
Name: "Edge 1",
|
Name: "Edge 1",
|
||||||
IP: "10.0.0.1",
|
IP: "10.0.0.1",
|
||||||
@@ -52,7 +51,7 @@ func TestGetOverviewStructure(t *testing.T) {
|
|||||||
CurrentVersion: "v1.0.0",
|
CurrentVersion: "v1.0.0",
|
||||||
LastSeenAt: &lastSeen,
|
LastSeenAt: &lastSeen,
|
||||||
}).Error)
|
}).Error)
|
||||||
require.NoError(t, db.DB(ctx).Create(&model.OpenFlareNode{
|
require.NoError(t, repository.DB(ctx).Create(&model.OpenFlareNode{
|
||||||
NodeID: "node-dashboard-2",
|
NodeID: "node-dashboard-2",
|
||||||
Name: "Edge 2",
|
Name: "Edge 2",
|
||||||
IP: "10.0.0.2",
|
IP: "10.0.0.2",
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import (
|
|||||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||||
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
||||||
"Wavelet/pkg/response"
|
"Wavelet/pkg/response"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
@@ -36,11 +35,11 @@ func setupAgentAuthTestDB(t *testing.T) func() {
|
|||||||
&model.SystemConfig{},
|
&model.SystemConfig{},
|
||||||
))
|
))
|
||||||
|
|
||||||
db.SetDB(sqliteDB)
|
repository.SetDBForTest(sqliteDB)
|
||||||
tokenCache.reset()
|
tokenCache.reset()
|
||||||
|
|
||||||
return func() {
|
return func() {
|
||||||
db.SetDB(nil)
|
repository.SetDBForTest(nil)
|
||||||
tokenCache.reset()
|
tokenCache.reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,7 +50,7 @@ func TestAuthenticateAccessToken(t *testing.T) {
|
|||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
now := time.Now()
|
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",
|
NodeID: "node-auth-1",
|
||||||
Name: "edge",
|
Name: "edge",
|
||||||
AccessToken: "valid-agent-token",
|
AccessToken: "valid-agent-token",
|
||||||
@@ -98,7 +97,7 @@ func TestAgentAuthMiddleware(t *testing.T) {
|
|||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
now := time.Now()
|
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",
|
NodeID: "node-mw-1",
|
||||||
Name: "edge",
|
Name: "edge",
|
||||||
AccessToken: "middleware-token",
|
AccessToken: "middleware-token",
|
||||||
@@ -145,7 +144,7 @@ func TestAgentRegisterAuthMiddleware(t *testing.T) {
|
|||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
now := time.Now()
|
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",
|
NodeID: "node-register-1",
|
||||||
Name: "edge",
|
Name: "edge",
|
||||||
AccessToken: "existing-node-token",
|
AccessToken: "existing-node-token",
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import (
|
|||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
"Wavelet/openflare/share/protocol"
|
"Wavelet/openflare/share/protocol"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -34,9 +33,9 @@ func setupWAFIPGroupTestDB(t *testing.T) func() {
|
|||||||
&model.ConfigVersion{},
|
&model.ConfigVersion{},
|
||||||
))
|
))
|
||||||
|
|
||||||
db.SetDB(sqliteDB)
|
repository.SetDBForTest(sqliteDB)
|
||||||
return func() {
|
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)
|
snapshotJSON, err := json.Marshal(snapshot)
|
||||||
require.NoError(t, err)
|
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",
|
Version: "20260618-001",
|
||||||
SnapshotJSON: string(snapshotJSON),
|
SnapshotJSON: string(snapshotJSON),
|
||||||
Checksum: "test-checksum",
|
Checksum: "test-checksum",
|
||||||
@@ -108,7 +107,7 @@ func seedActiveConfigWithWAFGraphIPGroup(t *testing.T, ctx context.Context, ipGr
|
|||||||
snapshotJSON, err := json.Marshal(snapshot)
|
snapshotJSON, err := json.Marshal(snapshot)
|
||||||
require.NoError(t, err)
|
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",
|
Version: "20260713-graph-001",
|
||||||
SnapshotJSON: string(snapshotJSON),
|
SnapshotJSON: string(snapshotJSON),
|
||||||
Checksum: "graph-test-checksum",
|
Checksum: "graph-test-checksum",
|
||||||
@@ -142,7 +141,7 @@ func TestChangedWAFIPGroupsForAgentRejectsMalformedIPMatchConfig(t *testing.T) {
|
|||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
ctx := context.Background()
|
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",
|
Version: "20260713-malformed-001",
|
||||||
SnapshotJSON: `{"waf":{"rule_groups":[{"id":7,"graph":{"entry":"match","nodes":{` +
|
SnapshotJSON: `{"waf":{"rule_groups":[{"id":7,"graph":{"entry":"match","nodes":{` +
|
||||||
`"match":{"type":"ip_match","config":{"ip_group_ids":"not-an-array"}}}}}],"bindings":[]}}`,
|
`"match":{"type":"ip_match","config":{"ip_group_ids":"not-an-array"}}}}}],"bindings":[]}}`,
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -23,8 +22,8 @@ func TestUptimeKumaSyncHandlerSkipsWhenDisabled(t *testing.T) {
|
|||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, sqliteDB.AutoMigrate(&model.SystemConfig{}))
|
require.NoError(t, sqliteDB.AutoMigrate(&model.SystemConfig{}))
|
||||||
db.SetDB(sqliteDB)
|
repository.SetDBForTest(sqliteDB)
|
||||||
t.Cleanup(func() { db.SetDB(nil) })
|
t.Cleanup(func() { repository.SetDBForTest(nil) })
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
require.NoError(t, repository.SaveOrUpdateSystemConfig(ctx, model.ConfigKeyUptimeKumaEnabled, "false"))
|
require.NoError(t, repository.SaveOrUpdateSystemConfig(ctx, model.ConfigKeyUptimeKumaEnabled, "false"))
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
"Wavelet/pkg/response"
|
"Wavelet/pkg/response"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
@@ -30,10 +29,10 @@ func setupFlaredMiddlewareTestDB(t *testing.T) func() {
|
|||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, sqliteDB.AutoMigrate(&model.OpenFlareNode{}))
|
require.NoError(t, sqliteDB.AutoMigrate(&model.OpenFlareNode{}))
|
||||||
db.SetDB(sqliteDB)
|
repository.SetDBForTest(sqliteDB)
|
||||||
|
|
||||||
return func() {
|
return func() {
|
||||||
db.SetDB(nil)
|
repository.SetDBForTest(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
|
|
||||||
"Wavelet/openflare/plugins/server/domain/fleet/agent"
|
"Wavelet/openflare/plugins/server/domain/fleet/agent"
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -33,11 +32,11 @@ func setupFlaredObservabilityTestDB(t *testing.T) func() {
|
|||||||
&model.ConfigVersion{},
|
&model.ConfigVersion{},
|
||||||
))
|
))
|
||||||
|
|
||||||
db.SetDB(sqliteDB)
|
repository.SetDBForTest(sqliteDB)
|
||||||
agent.ResetAuthCacheForTest()
|
agent.ResetAuthCacheForTest()
|
||||||
|
|
||||||
return func() {
|
return func() {
|
||||||
db.SetDB(nil)
|
repository.SetDBForTest(nil)
|
||||||
agent.ResetAuthCacheForTest()
|
agent.ResetAuthCacheForTest()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,7 +53,7 @@ func TestHeartbeatFlaredEmitsHealthEventOnUnhealthy(t *testing.T) {
|
|||||||
Status: "pending",
|
Status: "pending",
|
||||||
NodeType: "tunnel_client",
|
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{
|
_, err := Heartbeat(ctx, node, HeartbeatPayload{
|
||||||
ClientVersion: "v0.2.0",
|
ClientVersion: "v0.2.0",
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import (
|
|||||||
ofnode "Wavelet/openflare/plugins/server/domain/fleet/node"
|
ofnode "Wavelet/openflare/plugins/server/domain/fleet/node"
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
@@ -39,7 +38,7 @@ func setupProtocolTestEnv(t *testing.T) (*gin.Engine, func()) {
|
|||||||
&model.ConfigVersion{},
|
&model.ConfigVersion{},
|
||||||
))
|
))
|
||||||
|
|
||||||
db.SetDB(sqliteDB)
|
repository.SetDBForTest(sqliteDB)
|
||||||
agent.ResetAuthCacheForTest()
|
agent.ResetAuthCacheForTest()
|
||||||
testhelper.SetupLogStoresForTest(t)
|
testhelper.SetupLogStoresForTest(t)
|
||||||
|
|
||||||
@@ -47,7 +46,7 @@ func setupProtocolTestEnv(t *testing.T) (*gin.Engine, func()) {
|
|||||||
mountOpenFlareTestRoutes(engine)
|
mountOpenFlareTestRoutes(engine)
|
||||||
|
|
||||||
cleanup := func() {
|
cleanup := func() {
|
||||||
db.SetDB(nil)
|
repository.SetDBForTest(nil)
|
||||||
agent.ResetAuthCacheForTest()
|
agent.ResetAuthCacheForTest()
|
||||||
}
|
}
|
||||||
return engine, cleanup
|
return engine, cleanup
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import (
|
|||||||
|
|
||||||
"Wavelet/openflare/plugins/server/domain/fleet/agent"
|
"Wavelet/openflare/plugins/server/domain/fleet/agent"
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
|
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||||
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
@@ -55,7 +55,7 @@ func setupCoreChainTest(t *testing.T) (*gin.Engine, adminSeed, func()) {
|
|||||||
&model.ZoneDomain{},
|
&model.ZoneDomain{},
|
||||||
))
|
))
|
||||||
|
|
||||||
db.SetDB(sqliteDB)
|
repository.SetDBForTest(sqliteDB)
|
||||||
agent.ResetAuthCacheForTest()
|
agent.ResetAuthCacheForTest()
|
||||||
|
|
||||||
seed, err := seedAdminWithAccessToken(sqliteDB)
|
seed, err := seedAdminWithAccessToken(sqliteDB)
|
||||||
@@ -65,7 +65,7 @@ func setupCoreChainTest(t *testing.T) (*gin.Engine, adminSeed, func()) {
|
|||||||
mountOpenFlareTestRoutes(engine)
|
mountOpenFlareTestRoutes(engine)
|
||||||
|
|
||||||
cleanup := func() {
|
cleanup := func() {
|
||||||
db.SetDB(nil)
|
repository.SetDBForTest(nil)
|
||||||
agent.ResetAuthCacheForTest()
|
agent.ResetAuthCacheForTest()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,12 +144,12 @@ func TestCoreChainMigrationFlow(t *testing.T) {
|
|||||||
t.Run("create proxy route linked to origin", func(t *testing.T) {
|
t.Run("create proxy route linked to origin", func(t *testing.T) {
|
||||||
// Create Zone and ZoneDomain directly in the DB
|
// Create Zone and ZoneDomain directly in the DB
|
||||||
zone := model.Zone{Domain: "example.com"}
|
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{
|
zoneDomain := model.ZoneDomain{
|
||||||
ZoneID: zone.ID,
|
ZoneID: zone.ID,
|
||||||
Domain: "core-chain.example.com",
|
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{
|
rec := performJSONRequest(t, engine, http.MethodPost, apiPath("/proxy-routes/"), map[string]any{
|
||||||
"site_name": "core-chain-site",
|
"site_name": "core-chain-site",
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
|
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||||
"Wavelet/openflare/plugins/server/kernel/runtimeconfig"
|
"Wavelet/openflare/plugins/server/kernel/runtimeconfig"
|
||||||
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
@@ -51,7 +51,7 @@ func setupSecurityTest(t *testing.T) (*gin.Engine, adminSeed, func()) {
|
|||||||
&model.SystemConfig{},
|
&model.SystemConfig{},
|
||||||
))
|
))
|
||||||
|
|
||||||
db.SetDB(sqliteDB)
|
repository.SetDBForTest(sqliteDB)
|
||||||
|
|
||||||
seed, err := seedAdminWithAccessToken(sqliteDB)
|
seed, err := seedAdminWithAccessToken(sqliteDB)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -64,7 +64,7 @@ func setupSecurityTest(t *testing.T) (*gin.Engine, adminSeed, func()) {
|
|||||||
|
|
||||||
cleanup := func() {
|
cleanup := func() {
|
||||||
runtimeconfig.Set(previous)
|
runtimeconfig.Set(previous)
|
||||||
db.SetDB(nil)
|
repository.SetDBForTest(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return engine, seed, cleanup
|
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) {
|
t.Run("create proxy route for WAF binding", func(t *testing.T) {
|
||||||
// Create Zone and ZoneDomain directly in the DB
|
// Create Zone and ZoneDomain directly in the DB
|
||||||
routeZone := model.Zone{Domain: "example-route.com"}
|
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{
|
routeZoneDomain := model.ZoneDomain{
|
||||||
ZoneID: routeZone.ID,
|
ZoneID: routeZone.ID,
|
||||||
Domain: "route.example-route.com",
|
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{
|
rec := performJSONRequest(t, engine, http.MethodPost, apiPath("/proxy-routes/"), map[string]any{
|
||||||
"site_name": "security-site",
|
"site_name": "security-site",
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import (
|
|||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||||
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -40,13 +39,15 @@ func setupNodeTestDB(t *testing.T) func() {
|
|||||||
&model.OpenFlareNode{},
|
&model.OpenFlareNode{},
|
||||||
&model.SystemConfig{},
|
&model.SystemConfig{},
|
||||||
&model.OpenFlareApplyLog{},
|
&model.OpenFlareApplyLog{},
|
||||||
|
&model.OpenFlareNodeSystemProfile{},
|
||||||
|
&model.OpenFlareHealthEvent{},
|
||||||
))
|
))
|
||||||
|
|
||||||
db.SetDB(sqliteDB)
|
repository.SetDBForTest(sqliteDB)
|
||||||
testhelper.SetupLogStoresForTest(t)
|
testhelper.SetupLogStoresForTest(t)
|
||||||
|
|
||||||
return func() {
|
return func() {
|
||||||
db.SetDB(nil)
|
repository.SetDBForTest(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,7 +175,7 @@ func TestListNodesWithApplyLogMetadata(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
applyAt := time.Now().UTC().Truncate(time.Second)
|
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,
|
NodeID: created.NodeID,
|
||||||
Version: "20260618-001",
|
Version: "20260618-001",
|
||||||
Result: "success",
|
Result: "success",
|
||||||
@@ -280,7 +281,7 @@ func TestRequestOpenrestyRestart(t *testing.T) {
|
|||||||
|
|
||||||
func seedActiveConfigVersion(t *testing.T, ctx context.Context) {
|
func seedActiveConfigVersion(t *testing.T, ctx context.Context) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
conn := db.DB(ctx)
|
conn := repository.DB(ctx)
|
||||||
require.NotNil(t, conn)
|
require.NotNil(t, conn)
|
||||||
require.NoError(t, conn.AutoMigrate(&model.ConfigVersion{}))
|
require.NoError(t, conn.AutoMigrate(&model.ConfigVersion{}))
|
||||||
require.NoError(t, conn.Create(&model.ConfigVersion{
|
require.NoError(t, conn.Create(&model.ConfigVersion{
|
||||||
@@ -311,7 +312,7 @@ func TestRequestForceSyncRequiresActiveConfig(t *testing.T) {
|
|||||||
cleanup := setupNodeTestDB(t)
|
cleanup := setupNodeTestDB(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
conn := db.DB(ctx)
|
conn := repository.DB(ctx)
|
||||||
require.NotNil(t, conn)
|
require.NotNil(t, conn)
|
||||||
require.NoError(t, conn.AutoMigrate(&model.ConfigVersion{}))
|
require.NoError(t, conn.AutoMigrate(&model.ConfigVersion{}))
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import (
|
|||||||
|
|
||||||
"Wavelet/openflare/plugins/server/domain/fleet/agent"
|
"Wavelet/openflare/plugins/server/domain/fleet/agent"
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -38,12 +37,12 @@ func setupRelayTestDB(t *testing.T) func() {
|
|||||||
&model.OpenFlareNodeObservationFrps{},
|
&model.OpenFlareNodeObservationFrps{},
|
||||||
))
|
))
|
||||||
|
|
||||||
db.SetDB(sqliteDB)
|
repository.SetDBForTest(sqliteDB)
|
||||||
agent.ResetAuthCacheForTest()
|
agent.ResetAuthCacheForTest()
|
||||||
testhelper.SetupLogStoresForTest(t)
|
testhelper.SetupLogStoresForTest(t)
|
||||||
|
|
||||||
return func() {
|
return func() {
|
||||||
db.SetDB(nil)
|
repository.SetDBForTest(nil)
|
||||||
agent.ResetAuthCacheForTest()
|
agent.ResetAuthCacheForTest()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,7 +62,7 @@ func TestHeartbeatPayloadBindingAndFrpsObservationInsert(t *testing.T) {
|
|||||||
NodeType: "tunnel_relay",
|
NodeType: "tunnel_relay",
|
||||||
RelayStatus: "unknown",
|
RelayStatus: "unknown",
|
||||||
}
|
}
|
||||||
require.NoError(t, db.DB(ctx).Create(node).Error)
|
require.NoError(t, repository.DB(ctx).Create(node).Error)
|
||||||
|
|
||||||
proxies := []ProxyStat{
|
proxies := []ProxyStat{
|
||||||
{
|
{
|
||||||
@@ -103,7 +102,7 @@ func TestHeartbeatPayloadBindingAndFrpsObservationInsert(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var stored model.OpenFlareNode
|
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, "online", stored.Status)
|
||||||
assert.Equal(t, "healthy", stored.RelayStatus)
|
assert.Equal(t, "healthy", stored.RelayStatus)
|
||||||
assert.Equal(t, "203.0.113.9", stored.IP)
|
assert.Equal(t, "203.0.113.9", stored.IP)
|
||||||
@@ -147,7 +146,7 @@ func TestHeartbeatRelayReconcilesFrpsUnhealthyEvent(t *testing.T) {
|
|||||||
NodeType: "tunnel_relay",
|
NodeType: "tunnel_relay",
|
||||||
RelayStatus: "healthy",
|
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{
|
_, err := Heartbeat(ctx, node, HeartbeatPayload{
|
||||||
Version: "v0.1.0",
|
Version: "v0.1.0",
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
"Wavelet/pkg/response"
|
"Wavelet/pkg/response"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
@@ -30,10 +29,10 @@ func setupRelayMiddlewareTestDB(t *testing.T) func() {
|
|||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, sqliteDB.AutoMigrate(&model.OpenFlareNode{}))
|
require.NoError(t, sqliteDB.AutoMigrate(&model.OpenFlareNode{}))
|
||||||
db.SetDB(sqliteDB)
|
repository.SetDBForTest(sqliteDB)
|
||||||
|
|
||||||
return func() {
|
return func() {
|
||||||
db.SetDB(nil)
|
repository.SetDBForTest(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import (
|
|||||||
|
|
||||||
"Wavelet/openflare/plugins/server/domain/observability/chwriter"
|
"Wavelet/openflare/plugins/server/domain/observability/chwriter"
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Run with Docker ClickHouse + config.yaml:
|
// Run with Docker ClickHouse + config.yaml:
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import (
|
|||||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||||
"Wavelet/openflare/plugins/server/kernel/repository/logstore"
|
"Wavelet/openflare/plugins/server/kernel/repository/logstore"
|
||||||
"Wavelet/openflare/plugins/server/kernel/runtimeconfig"
|
"Wavelet/openflare/plugins/server/kernel/runtimeconfig"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var logDBSwitchDBSeq int64
|
var logDBSwitchDBSeq int64
|
||||||
@@ -57,13 +56,13 @@ func TestCopyAccessLogsPreservesIDs(t *testing.T) {
|
|||||||
srcDB := newLogDBSwitchDB(t)
|
srcDB := newLogDBSwitchDB(t)
|
||||||
dstDB := newLogDBSwitchDB(t)
|
dstDB := newLogDBSwitchDB(t)
|
||||||
|
|
||||||
db.SetDB(srcDB)
|
repository.SetDBForTest(srcDB)
|
||||||
src, err := logstore.Active(ctx) // 无 reader 时按 seed 规则解析为 sqlite
|
src, err := logstore.Active(ctx) // 无 reader 时按 seed 规则解析为 sqlite
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
db.SetDB(dstDB)
|
repository.SetDBForTest(dstDB)
|
||||||
dst, err := logstore.BuildForMigration(ctx, "sqlite")
|
dst, err := logstore.BuildForMigration(ctx, "sqlite")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Cleanup(func() { db.SetDB(nil) })
|
t.Cleanup(func() { repository.SetDBForTest(nil) })
|
||||||
|
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
rows := []analyticsmodel.NodeAccessLog{
|
rows := []analyticsmodel.NodeAccessLog{
|
||||||
@@ -103,13 +102,13 @@ func TestCopyUserAccessLogsPreservesIDs(t *testing.T) {
|
|||||||
srcDB := newLogDBSwitchDB(t)
|
srcDB := newLogDBSwitchDB(t)
|
||||||
dstDB := newLogDBSwitchDB(t)
|
dstDB := newLogDBSwitchDB(t)
|
||||||
|
|
||||||
db.SetDB(srcDB)
|
repository.SetDBForTest(srcDB)
|
||||||
src, err := logstore.Active(ctx)
|
src, err := logstore.Active(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
db.SetDB(dstDB)
|
repository.SetDBForTest(dstDB)
|
||||||
dst, err := logstore.BuildForMigration(ctx, "sqlite")
|
dst, err := logstore.BuildForMigration(ctx, "sqlite")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Cleanup(func() { db.SetDB(nil) })
|
t.Cleanup(func() { repository.SetDBForTest(nil) })
|
||||||
|
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
rows := []analyticsmodel.UserAccessLog{
|
rows := []analyticsmodel.UserAccessLog{
|
||||||
@@ -142,8 +141,8 @@ func TestClearTargetLogTablesClearsUserAccessLogs(t *testing.T) {
|
|||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
dstDB := newLogDBSwitchDB(t)
|
dstDB := newLogDBSwitchDB(t)
|
||||||
db.SetDB(dstDB)
|
repository.SetDBForTest(dstDB)
|
||||||
t.Cleanup(func() { db.SetDB(nil) })
|
t.Cleanup(func() { repository.SetDBForTest(nil) })
|
||||||
dst, err := logstore.BuildForMigration(ctx, "sqlite")
|
dst, err := logstore.BuildForMigration(ctx, "sqlite")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@@ -168,8 +167,8 @@ func TestClearTargetLogTablesDuringMigration(t *testing.T) {
|
|||||||
defer logstore.ResetForTest()
|
defer logstore.ResetForTest()
|
||||||
|
|
||||||
gdb := newLogDBSwitchDB(t)
|
gdb := newLogDBSwitchDB(t)
|
||||||
db.SetDB(gdb)
|
repository.SetDBForTest(gdb)
|
||||||
t.Cleanup(func() { db.SetDB(nil) })
|
t.Cleanup(func() { repository.SetDBForTest(nil) })
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
logstore.SetConfigReader(func(ctx context.Context, key string) (string, error) {
|
logstore.SetConfigReader(func(ctx context.Context, key string) (string, error) {
|
||||||
@@ -205,8 +204,8 @@ func TestValidateSwitch(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
gdb := newLogDBSwitchDB(t)
|
gdb := newLogDBSwitchDB(t)
|
||||||
db.SetDB(gdb)
|
repository.SetDBForTest(gdb)
|
||||||
t.Cleanup(func() { db.SetDB(nil) })
|
t.Cleanup(func() { repository.SetDBForTest(nil) })
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
setLogDB := func(v string) {
|
setLogDB := func(v string) {
|
||||||
require.NoError(t, repository.SaveOrUpdateSystemConfig(ctx, model.ConfigKeyLogDatabase, v))
|
require.NoError(t, repository.SaveOrUpdateSystemConfig(ctx, model.ConfigKeyLogDatabase, v))
|
||||||
@@ -288,8 +287,8 @@ func TestExecuteFailureClearsMigrationFlag(t *testing.T) {
|
|||||||
defer logstore.ResetForTest()
|
defer logstore.ResetForTest()
|
||||||
|
|
||||||
gdb := newLogDBSwitchDB(t)
|
gdb := newLogDBSwitchDB(t)
|
||||||
db.SetDB(gdb)
|
repository.SetDBForTest(gdb)
|
||||||
t.Cleanup(func() { db.SetDB(nil) })
|
t.Cleanup(func() { repository.SetDBForTest(nil) })
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// FRESH DB:log_db_migration 行不存在(不预置),log_database 预置为 sqlite。
|
// FRESH DB:log_db_migration 行不存在(不预置),log_database 预置为 sqlite。
|
||||||
@@ -323,8 +322,8 @@ func TestSetMigrationFlagObservableThroughCache(t *testing.T) {
|
|||||||
defer logstore.ResetForTest()
|
defer logstore.ResetForTest()
|
||||||
|
|
||||||
gdb := newLogDBSwitchDB(t)
|
gdb := newLogDBSwitchDB(t)
|
||||||
db.SetDB(gdb)
|
repository.SetDBForTest(gdb)
|
||||||
t.Cleanup(func() { db.SetDB(nil) })
|
t.Cleanup(func() { repository.SetDBForTest(nil) })
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// 按 bootstrap 同款注入 repository 读取,走 RAM 缓存路径。
|
// 按 bootstrap 同款注入 repository 读取,走 RAM 缓存路径。
|
||||||
@@ -353,8 +352,8 @@ func TestFlipLogDatabaseRefreshesCachedConfig(t *testing.T) {
|
|||||||
defer logstore.ResetForTest()
|
defer logstore.ResetForTest()
|
||||||
|
|
||||||
gdb := newLogDBSwitchDB(t)
|
gdb := newLogDBSwitchDB(t)
|
||||||
db.SetDB(gdb)
|
repository.SetDBForTest(gdb)
|
||||||
t.Cleanup(func() { db.SetDB(nil) })
|
t.Cleanup(func() { repository.SetDBForTest(nil) })
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
logstore.SetConfigReader(func(ctx context.Context, key string) (string, error) {
|
logstore.SetConfigReader(func(ctx context.Context, key string) (string, error) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||||
db "Wavelet/plugins/infra/database"
|
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -26,7 +26,8 @@ func setupOptionTestDB(t *testing.T) func() {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, sqliteDB.AutoMigrate(&model.SystemConfig{}))
|
require.NoError(t, sqliteDB.AutoMigrate(&model.SystemConfig{}))
|
||||||
|
|
||||||
db.SetDB(sqliteDB)
|
repository.SetDBForTest(sqliteDB)
|
||||||
|
repository.SetSystemConfigService(testhelper.NewMockSystemConfigService(sqliteDB))
|
||||||
|
|
||||||
// 预填充一些业务配置用于测试
|
// 预填充一些业务配置用于测试
|
||||||
seedConfigs := []model.SystemConfig{
|
seedConfigs := []model.SystemConfig{
|
||||||
@@ -38,14 +39,15 @@ func setupOptionTestDB(t *testing.T) func() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return func() {
|
return func() {
|
||||||
db.SetDB(nil)
|
repository.SetSystemConfigService(nil)
|
||||||
|
repository.SetDBForTest(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// setTestConfig 设置测试配置的辅助函数
|
// setTestConfig 设置测试配置的辅助函数
|
||||||
func setTestConfig(t *testing.T, ctx context.Context, key, value string) {
|
func setTestConfig(t *testing.T, ctx context.Context, key, value string) {
|
||||||
t.Helper()
|
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) {
|
func TestListOptionsFiltersSecretKeys(t *testing.T) {
|
||||||
@@ -89,7 +91,7 @@ func TestUpdateOpenRestyOptionPersistsToSystemConfig(t *testing.T) {
|
|||||||
defer cleanup()
|
defer cleanup()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
require.NoError(t, db.DB(ctx).Create(&model.SystemConfig{
|
require.NoError(t, repository.DB(ctx).Create(&model.SystemConfig{
|
||||||
Key: model.ConfigKeyOpenRestyEventsUse,
|
Key: model.ConfigKeyOpenRestyEventsUse,
|
||||||
Value: "epoll",
|
Value: "epoll",
|
||||||
Type: "business",
|
Type: "business",
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import (
|
|||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||||
db "Wavelet/plugins/infra/database"
|
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -125,17 +125,19 @@ func setupSyncTestDB(t *testing.T) func() {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, sqliteDB.AutoMigrate(&model.ProxyRoute{}, &model.Zone{}, &model.ZoneDomain{}, &model.SystemConfig{}))
|
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() {
|
return func() {
|
||||||
db.SetDB(nil)
|
repository.SetSystemConfigService(nil)
|
||||||
|
repository.SetDBForTest(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createRouteZoneDomain(t *testing.T, ctx context.Context, route *model.ProxyRoute, domain string) {
|
func createRouteZoneDomain(t *testing.T, ctx context.Context, route *model.ProxyRoute, domain string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
zone := &model.Zone{Domain: fmt.Sprintf("zone-%d.example", route.ID)}
|
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)
|
||||||
require.NoError(t, db.DB(ctx).Create(&model.ZoneDomain{
|
require.NoError(t, repository.DB(ctx).Create(&model.ZoneDomain{
|
||||||
ZoneID: zone.ID,
|
ZoneID: zone.ID,
|
||||||
ProxyRouteID: &route.ID,
|
ProxyRouteID: &route.ID,
|
||||||
Domain: domain,
|
Domain: domain,
|
||||||
@@ -166,7 +168,7 @@ func backupUptimeKumaConfig(ctx context.Context) func() {
|
|||||||
return func() {
|
return func() {
|
||||||
// 恢复所有配置
|
// 恢复所有配置
|
||||||
for key, value := range oldValues {
|
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)
|
restore := backupUptimeKumaConfig(ctx)
|
||||||
defer restore()
|
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{
|
routeA := &model.ProxyRoute{
|
||||||
SiteName: "site-a",
|
SiteName: "site-a",
|
||||||
@@ -306,7 +308,7 @@ func TestSyncToUptimeKumaSelectedScope(t *testing.T) {
|
|||||||
restore := backupUptimeKumaConfig(ctx)
|
restore := backupUptimeKumaConfig(ctx)
|
||||||
defer restore()
|
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{
|
routeA := &model.ProxyRoute{
|
||||||
SiteName: "site-a",
|
SiteName: "site-a",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGitHubSourceIdentityLengthPrefixesFieldsAndResetsRuntime(t *testing.T) {
|
func TestGitHubSourceIdentityLengthPrefixesFieldsAndResetsRuntime(t *testing.T) {
|
||||||
@@ -57,7 +57,7 @@ func TestGitHubSourceIdentityLengthPrefixesFieldsAndResetsRuntime(t *testing.T)
|
|||||||
syncedAt := time.Now().Add(-30 * time.Second)
|
syncedAt := time.Now().Add(-30 * time.Second)
|
||||||
nextCheckAt := time.Now().Add(time.Hour)
|
nextCheckAt := time.Now().Add(time.Hour)
|
||||||
leaseExpiresAt := time.Now().Add(time.Minute)
|
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).
|
Where("source_id = ?", firstSource.ID).
|
||||||
Updates(map[string]any{
|
Updates(map[string]any{
|
||||||
"etag": `"old-etag"`,
|
"etag": `"old-etag"`,
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import (
|
|||||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||||
"Wavelet/openflare/plugins/server/kernel/task"
|
"Wavelet/openflare/plugins/server/kernel/task"
|
||||||
"Wavelet/openflare/share/githubrelease"
|
"Wavelet/openflare/share/githubrelease"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/hibiken/asynq"
|
"github.com/hibiken/asynq"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@@ -62,7 +61,7 @@ func mustConfigureGitHubSourceWithoutDispatch(
|
|||||||
if err := validateGitHubSourceInput(input); err != nil {
|
if err := validateGitHubSourceInput(input); err != nil {
|
||||||
t.Fatalf("validateGitHubSourceInput(%+v) error = %v, want nil", input, err)
|
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)
|
_, err := updateGitHubSourceTx(tx, projectID, input)
|
||||||
return err
|
return err
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
@@ -78,11 +77,11 @@ func mustLoadPagesSource(
|
|||||||
) (*model.PagesProjectSource, *model.PagesProjectSourceRuntime) {
|
) (*model.PagesProjectSource, *model.PagesProjectSourceRuntime) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
var source model.PagesProjectSource
|
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)
|
t.Fatalf("load source for project %d error = %v, want nil", projectID, err)
|
||||||
}
|
}
|
||||||
var runtime model.PagesProjectSourceRuntime
|
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)
|
t.Fatalf("load runtime for source %d error = %v, want nil", source.ID, err)
|
||||||
}
|
}
|
||||||
return &source, &runtime
|
return &source, &runtime
|
||||||
@@ -136,14 +135,14 @@ func TestGitHubSourceValidationNormalizationAndProviderSwitch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var taskCount int64
|
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)
|
t.Fatalf("count initial checks error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
if _, err := UpdateSourceAs(ctx, project.ID, input, "user:42"); err != nil {
|
if _, err := UpdateSourceAs(ctx, project.ID, input, "user:42"); err != nil {
|
||||||
t.Fatalf("UpdateSourceAs(GitHub no-op) error = %v, want nil", err)
|
t.Fatalf("UpdateSourceAs(GitHub no-op) error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
var noOpTaskCount int64
|
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)
|
t.Fatalf("count no-op checks error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
if noOpTaskCount != taskCount {
|
if noOpTaskCount != taskCount {
|
||||||
@@ -275,7 +274,7 @@ func TestInitialCheckFailureUsesExactConfigFence(t *testing.T) {
|
|||||||
RepositoryURL: "https://github.com/a/b",
|
RepositoryURL: "https://github.com/a/b",
|
||||||
})
|
})
|
||||||
staleVersion := source.ConfigVersion
|
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)
|
t.Fatalf("increment source config version error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
markInitialCheckDispatchFailed(ctx, source.ID, staleVersion)
|
markInitialCheckDispatchFailed(ctx, source.ID, staleVersion)
|
||||||
@@ -294,7 +293,7 @@ func TestGitHubCheckUsesETagAndDetectsSameReleaseReplacement(t *testing.T) {
|
|||||||
})
|
})
|
||||||
appliedRevision := strings.Repeat("a", 64)
|
appliedRevision := strings.Repeat("a", 64)
|
||||||
appliedDetail := `{"provider":"github","release_id":"100","asset_id":"1","tag":"release/v1","asset_name":"dist.zip"}`
|
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"`,
|
"etag": `"old-etag"`,
|
||||||
"last_applied_revision": appliedRevision,
|
"last_applied_revision": appliedRevision,
|
||||||
"last_applied_detail": appliedDetail,
|
"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)
|
t.Errorf("304 runtime = %+v, want refreshed timestamps/etag and released lease", runtime)
|
||||||
}
|
}
|
||||||
var deployments int64
|
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)
|
t.Fatalf("count deployments error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
if deployments != 0 {
|
if deployments != 0 {
|
||||||
@@ -391,7 +390,7 @@ func TestGitHubTargetMismatchPreservesAttentionAndExpeditesRecheck(t *testing.T)
|
|||||||
RepositoryURL: "https://github.com/a/b",
|
RepositoryURL: "https://github.com/a/b",
|
||||||
})
|
})
|
||||||
appliedRevision := strings.Repeat("a", 64)
|
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_revision": appliedRevision,
|
||||||
"last_applied_detail": `{"provider":"github","release_id":"100","asset_id":"1","tag":"v1","asset_name":"dist.zip"}`,
|
"last_applied_detail": `{"provider":"github","release_id":"100","asset_id":"1","tag":"v1","asset_name":"dist.zip"}`,
|
||||||
}).Error; err != nil {
|
}).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)
|
t.Errorf("target mismatch NextCheckAt = %v, want server deadline >= %v", runtime.NextCheckAt, retryAt)
|
||||||
}
|
}
|
||||||
var deployments int64
|
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)
|
t.Fatalf("count mismatch deployments error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
if deployments != 0 {
|
if deployments != 0 {
|
||||||
@@ -454,7 +453,7 @@ func TestGitHubCheckLostLeaseReturnsStaleWithoutOverwritingRuntime(t *testing.T)
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
snapshot, _, _ := acquireSourceLease(ctx, source.ID, source.ConfigVersion, sourceActionCheck)
|
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_token": "new-owner",
|
||||||
"lease_expires_at": time.Now().Add(time.Minute),
|
"lease_expires_at": time.Now().Add(time.Minute),
|
||||||
"sync_status": pagesSourceStatusSyncing,
|
"sync_status": pagesSourceStatusSyncing,
|
||||||
@@ -741,7 +740,7 @@ func TestGitHubSyncActivatesExactConfirmedReplacement(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("buildGitHubSourceTarget() error = %v, want nil", err)
|
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_revision": target.Revision,
|
||||||
"last_seen_detail": target.DetailJSON,
|
"last_seen_detail": target.DetailJSON,
|
||||||
"last_applied_revision": strings.Repeat("a", 64),
|
"last_applied_revision": strings.Repeat("a", 64),
|
||||||
|
|||||||
@@ -320,6 +320,7 @@ func removePagesUploadIfUnreferenced(ctx context.Context, projectID uint, upload
|
|||||||
if uploadID == 0 {
|
if uploadID == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
shouldRemove := false
|
||||||
err := repository.WithPagesTx(ctx, func(tx *gorm.DB) error {
|
err := repository.WithPagesTx(ctx, func(tx *gorm.DB) error {
|
||||||
if projectID != 0 {
|
if projectID != 0 {
|
||||||
if _, projectErr := repository.LockPagesProjectByIDTx(tx, projectID); projectErr != nil &&
|
if _, projectErr := repository.LockPagesProjectByIDTx(tx, projectID); projectErr != nil &&
|
||||||
@@ -350,13 +351,13 @@ func removePagesUploadIfUnreferenced(ctx context.Context, projectID uint, upload
|
|||||||
if references > 0 {
|
if references > 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
_, err := ofupload.RemoveLockedTx(tx, &uploadRecord)
|
shouldRemove = true
|
||||||
return err
|
return nil
|
||||||
})
|
})
|
||||||
// Always invalidate after transaction completion, including idempotent no-op,
|
if err != nil || !shouldRemove {
|
||||||
// so a prior post-commit cache interruption can heal on retry.
|
return err
|
||||||
ofupload.InvalidateUploadMetaCache(ctx, uploadID)
|
}
|
||||||
return err
|
return ofupload.Remove(ctx, uploadID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func inspectPagesPackage(packagePath string, format pagesarchive.Format, rootDir string, entryFile string, limits pagesLimits) (*deploymentManifest, error) {
|
func inspectPagesPackage(packagePath string, format pagesarchive.Format, rootDir string, entryFile string, limits pagesLimits) (*deploymentManifest, error) {
|
||||||
|
|||||||
@@ -1020,7 +1020,8 @@ func hydrateLegacyDeploymentUpload(
|
|||||||
if deployment.UploadID > 0 {
|
if deployment.UploadID > 0 {
|
||||||
uploadRecord, err := ofupload.GetActiveUpload(ctx, deployment.UploadID)
|
uploadRecord, err := ofupload.GetActiveUpload(ctx, deployment.UploadID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return &uploadRecord, nil
|
record := model.FromUploadDTO(uploadRecord)
|
||||||
|
return &record, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1066,7 +1067,8 @@ func hydrateLegacyDeploymentUpload(
|
|||||||
}
|
}
|
||||||
deployment.UploadID = winnerUploadID
|
deployment.UploadID = winnerUploadID
|
||||||
deployment.ArtifactPath = ""
|
deployment.ArtifactPath = ""
|
||||||
return &winner, nil
|
winnerRecord := model.FromUploadDTO(winner)
|
||||||
|
return &winnerRecord, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func attachLegacyDeploymentUpload(
|
func attachLegacyDeploymentUpload(
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ import (
|
|||||||
oftask "Wavelet/openflare/plugins/server/kernel/task"
|
oftask "Wavelet/openflare/plugins/server/kernel/task"
|
||||||
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
||||||
"Wavelet/pkg/idgen"
|
"Wavelet/pkg/idgen"
|
||||||
uploadshared "Wavelet/plugins/domain/upload/shared"
|
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -39,6 +37,9 @@ func setupPagesTestDB(t *testing.T) func() {
|
|||||||
DisableForeignKeyConstraintWhenMigrating: true,
|
DisableForeignKeyConstraintWhenMigrating: true,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
if sqlDB, err := sqliteDB.DB(); err == nil {
|
||||||
|
sqlDB.SetMaxOpenConns(1)
|
||||||
|
}
|
||||||
require.NoError(t, sqliteDB.AutoMigrate(
|
require.NoError(t, sqliteDB.AutoMigrate(
|
||||||
&model.User{},
|
&model.User{},
|
||||||
&model.Upload{},
|
&model.Upload{},
|
||||||
@@ -74,30 +75,33 @@ func setupPagesTestDB(t *testing.T) func() {
|
|||||||
},
|
},
|
||||||
}).Error)
|
}).Error)
|
||||||
|
|
||||||
db.SetDB(sqliteDB)
|
repository.SetDBForTest(sqliteDB)
|
||||||
|
repository.SetSystemConfigService(testhelper.NewMockSystemConfigService(sqliteDB))
|
||||||
require.NoError(t, idgen.Init(1))
|
require.NoError(t, idgen.Init(1))
|
||||||
oftask.SetService(&testhelper.NoopTaskService{})
|
noopTask := &testhelper.NoopTaskService{}
|
||||||
mockStorage := uploadshared.NewMockStorageService()
|
oftask.SetService(noopTask)
|
||||||
uploadshared.SetDBService(db.NewService(sqliteDB))
|
repository.SetTaskService(noopTask)
|
||||||
uploadshared.SetStorageService(mockStorage)
|
mockStorage := testhelper.NewMockStorageService()
|
||||||
ofupload.SetStorage(mockStorage)
|
ofupload.SetStorage(mockStorage)
|
||||||
|
ofupload.SetUploadService(testhelper.NewMockUploadService(sqliteDB))
|
||||||
_ = repository.InvalidateSystemConfigCache(context.Background(), model.ConfigKeyPagesMaxPackageSizeMB)
|
_ = repository.InvalidateSystemConfigCache(context.Background(), model.ConfigKeyPagesMaxPackageSizeMB)
|
||||||
_ = repository.InvalidateSystemConfigCache(context.Background(), model.ConfigKeyPagesMaxHistoryCount)
|
_ = repository.InvalidateSystemConfigCache(context.Background(), model.ConfigKeyPagesMaxHistoryCount)
|
||||||
return func() {
|
return func() {
|
||||||
ofupload.SetStorage(nil)
|
ofupload.SetStorage(nil)
|
||||||
uploadshared.ResetServices()
|
ofupload.SetUploadService(nil)
|
||||||
db.SetDB(nil)
|
repository.SetTaskService(nil)
|
||||||
|
oftask.SetService(nil)
|
||||||
|
repository.SetSystemConfigService(nil)
|
||||||
|
repository.SetDBForTest(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupPagesStorageMock(t *testing.T) (restore func(), disable func()) {
|
func setupPagesStorageMock(t *testing.T) (restore func(), disable func()) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
mock := uploadshared.NewMockStorageService()
|
mock := testhelper.NewMockStorageService()
|
||||||
uploadshared.SetStorageService(mock)
|
|
||||||
ofupload.SetStorage(mock)
|
ofupload.SetStorage(mock)
|
||||||
restore = func() {
|
restore = func() {
|
||||||
ofupload.SetStorage(nil)
|
ofupload.SetStorage(nil)
|
||||||
uploadshared.ResetServices()
|
|
||||||
}
|
}
|
||||||
disable = restore
|
disable = restore
|
||||||
return restore, disable
|
return restore, disable
|
||||||
@@ -283,10 +287,10 @@ func TestUploadDeploymentStoresPackageInUploadFramework(t *testing.T) {
|
|||||||
assert.Empty(t, storedDeployment.ArtifactPath)
|
assert.Empty(t, storedDeployment.ArtifactPath)
|
||||||
|
|
||||||
var uploadCount int64
|
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)
|
assert.Equal(t, int64(1), uploadCount)
|
||||||
var uploadRecord model.Upload
|
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, ofupload.ReservedPagesDeploymentType, uploadRecord.Type)
|
||||||
assert.Equal(t, pagesIngestMarkerV2, uploadRecord.Metadata.Extra[pagesIngestMarkerKey])
|
assert.Equal(t, pagesIngestMarkerV2, uploadRecord.Metadata.Extra[pagesIngestMarkerKey])
|
||||||
assert.Equal(t, fmt.Sprint(project.ID), uploadRecord.Metadata.Extra[pagesProjectIDMetadataKey])
|
assert.Equal(t, fmt.Sprint(project.ID), uploadRecord.Metadata.Extra[pagesProjectIDMetadataKey])
|
||||||
@@ -323,8 +327,8 @@ func TestOpenDeploymentPackageHydratesLegacyArtifactPath(t *testing.T) {
|
|||||||
TotalSize: 10,
|
TotalSize: 10,
|
||||||
CreatedBy: "test",
|
CreatedBy: "test",
|
||||||
}
|
}
|
||||||
require.NoError(t, db.DB(ctx).Create(deployment).Error)
|
require.NoError(t, repository.DB(ctx).Create(deployment).Error)
|
||||||
require.NoError(t, db.DB(ctx).Create(&model.PagesDeploymentFile{
|
require.NoError(t, repository.DB(ctx).Create(&model.PagesDeploymentFile{
|
||||||
DeploymentID: deployment.ID,
|
DeploymentID: deployment.ID,
|
||||||
Path: "index.html",
|
Path: "index.html",
|
||||||
Size: 6,
|
Size: 6,
|
||||||
@@ -334,7 +338,7 @@ func TestOpenDeploymentPackageHydratesLegacyArtifactPath(t *testing.T) {
|
|||||||
_, err = ActivateDeployment(ctx, project.ID, deployment.ID)
|
_, err = ActivateDeployment(ctx, project.ID, deployment.ID)
|
||||||
require.NoError(t, err)
|
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",
|
Version: "v2026-legacy",
|
||||||
SnapshotJSON: fmt.Sprintf(`{"routes":[{"upstream_type":"pages","pages_deployment":{"deployment_id":%d}}]}`, deployment.ID),
|
SnapshotJSON: fmt.Sprintf(`{"routes":[{"upstream_type":"pages","pages_deployment":{"deployment_id":%d}}]}`, deployment.ID),
|
||||||
MainConfig: "",
|
MainConfig: "",
|
||||||
@@ -363,7 +367,7 @@ func TestOpenDeploymentPackageHydratesLegacyArtifactPath(t *testing.T) {
|
|||||||
assert.Empty(t, storedDeployment.ArtifactPath)
|
assert.Empty(t, storedDeployment.ArtifactPath)
|
||||||
|
|
||||||
var uploadCount int64
|
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)
|
assert.Equal(t, int64(1), uploadCount)
|
||||||
|
|
||||||
packageObj2, err := OpenDeploymentPackage(ctx, deployment.ID)
|
packageObj2, err := OpenDeploymentPackage(ctx, deployment.ID)
|
||||||
@@ -400,7 +404,7 @@ func TestOpenDeploymentPackageRequiresActiveConfigSnapshot(t *testing.T) {
|
|||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), "激活配置")
|
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",
|
Version: "v2026-001",
|
||||||
SnapshotJSON: fmt.Sprintf(`{"routes":[{"upstream_type":"pages","pages_deployment":{"deployment_id":%d}}]}`, deployment.ID),
|
SnapshotJSON: fmt.Sprintf(`{"routes":[{"upstream_type":"pages","pages_deployment":{"deployment_id":%d}}]}`, deployment.ID),
|
||||||
MainConfig: "",
|
MainConfig: "",
|
||||||
@@ -454,7 +458,7 @@ func TestProjectLatestRejectsWhenNotOnActiveConfigOrNotActive(t *testing.T) {
|
|||||||
_, _, err = GetProjectLatestPackageHash(ctx, project.ID)
|
_, _, err = GetProjectLatestPackageHash(ctx, project.ID)
|
||||||
require.Error(t, err)
|
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",
|
Version: "v-gate",
|
||||||
SnapshotJSON: fmt.Sprintf(
|
SnapshotJSON: fmt.Sprintf(
|
||||||
`{"routes":[{"upstream_type":"pages","pages_project_id":%d,"pages_deployment":{"project_id":%d,"deployment_id":%d}}]}`,
|
`{"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)
|
assert.NotEmpty(t, hash)
|
||||||
|
|
||||||
// Disabled project rejects.
|
// 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)
|
_, _, err = GetProjectLatestPackageHash(ctx, project.ID)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
@@ -572,7 +576,7 @@ func TestPruneProjectDeploymentHistory(t *testing.T) {
|
|||||||
defer disableStorage()
|
defer disableStorage()
|
||||||
ctx := context.Background()
|
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).
|
Where("key = ?", model.ConfigKeyPagesMaxHistoryCount).
|
||||||
Update("value", "2").Error)
|
Update("value", "2").Error)
|
||||||
require.NoError(t, repository.InvalidateSystemConfigCache(ctx, model.ConfigKeyPagesMaxHistoryCount))
|
require.NoError(t, repository.InvalidateSystemConfigCache(ctx, model.ConfigKeyPagesMaxHistoryCount))
|
||||||
@@ -634,7 +638,7 @@ func TestHistoryCountOnePreservesFreshCandidateUntilActivation(t *testing.T) {
|
|||||||
defer disableStorage()
|
defer disableStorage()
|
||||||
ctx := context.Background()
|
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).
|
Where("key = ?", model.ConfigKeyPagesMaxHistoryCount).
|
||||||
Update("value", "1").Error)
|
Update("value", "1").Error)
|
||||||
require.NoError(t, repository.InvalidateSystemConfigCache(ctx, model.ConfigKeyPagesMaxHistoryCount))
|
require.NoError(t, repository.InvalidateSystemConfigCache(ctx, model.ConfigKeyPagesMaxHistoryCount))
|
||||||
@@ -671,7 +675,7 @@ func TestHistoryCountOnePreservesFreshCandidateUntilActivation(t *testing.T) {
|
|||||||
assert.True(t, kept[newCandidate.ID])
|
assert.True(t, kept[newCandidate.ID])
|
||||||
assert.False(t, kept[oldCandidate.ID])
|
assert.False(t, kept[oldCandidate.ID])
|
||||||
var removedUpload model.Upload
|
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)
|
assert.Equal(t, model.UploadStatusDeleted, removedUpload.Status)
|
||||||
|
|
||||||
_, err = ActivateDeployment(ctx, project.ID, newCandidate.ID)
|
_, 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))
|
require.NoError(t, DeleteDeployment(ctx, project.ID, second.ID))
|
||||||
var secondUpload model.Upload
|
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)
|
assert.Equal(t, model.UploadStatusDeleted, secondUpload.Status)
|
||||||
|
|
||||||
require.NoError(t, DeleteProject(ctx, project.ID))
|
require.NoError(t, DeleteProject(ctx, project.ID))
|
||||||
var firstUpload model.Upload
|
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)
|
assert.Equal(t, model.UploadStatusDeleted, firstUpload.Status)
|
||||||
_, err = repository.GetPagesProjectByID(ctx, project.ID)
|
_, err = repository.GetPagesProjectByID(ctx, project.ID)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func GetProjectLatestPackageMetadata(ctx context.Context, projectID uint) (*Proj
|
|||||||
return &ProjectLatestPackageMetadata{
|
return &ProjectLatestPackageMetadata{
|
||||||
DeploymentID: deployment.ID,
|
DeploymentID: deployment.ID,
|
||||||
Hash: hash,
|
Hash: hash,
|
||||||
PackageSize: uploadRecord.FileSize,
|
PackageSize: uploadRecord.Size,
|
||||||
FileCount: deployment.FileCount,
|
FileCount: deployment.FileCount,
|
||||||
TotalSize: deployment.TotalSize,
|
TotalSize: deployment.TotalSize,
|
||||||
}, nil
|
}, nil
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetProjectLatestPackageMetadataPreservesZeroTotalSize(t *testing.T) {
|
func TestGetProjectLatestPackageMetadataPreservesZeroTotalSize(t *testing.T) {
|
||||||
@@ -46,7 +46,7 @@ func TestGetProjectLatestPackageMetadataPreservesZeroTotalSize(t *testing.T) {
|
|||||||
if _, err := ActivateDeployment(ctx, project.ID, deployment.ID); err != nil {
|
if _, err := ActivateDeployment(ctx, project.ID, deployment.ID); err != nil {
|
||||||
t.Fatalf("ActivateDeployment() error = %v", err)
|
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",
|
Version: "v-package-metadata",
|
||||||
SnapshotJSON: fmt.Sprintf(
|
SnapshotJSON: fmt.Sprintf(
|
||||||
`{"routes":[{"upstream_type":"pages","pages_project_id":%d}]}`,
|
`{"routes":[{"upstream_type":"pages","pages_project_id":%d}]}`,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"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/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -36,7 +36,7 @@ func TestRebindSnapshotPagesToCurrentActive(t *testing.T) {
|
|||||||
Status: model.PagesDeploymentStatusUploaded,
|
Status: model.PagesDeploymentStatusUploaded,
|
||||||
FileCount: 1,
|
FileCount: 1,
|
||||||
}
|
}
|
||||||
require.NoError(t, db.DB(ctx).Create(old).Error)
|
require.NoError(t, repository.DB(ctx).Create(old).Error)
|
||||||
active := &model.PagesDeployment{
|
active := &model.PagesDeployment{
|
||||||
ProjectID: project.ID,
|
ProjectID: project.ID,
|
||||||
DeploymentNumber: 2,
|
DeploymentNumber: 2,
|
||||||
@@ -44,8 +44,8 @@ func TestRebindSnapshotPagesToCurrentActive(t *testing.T) {
|
|||||||
Status: model.PagesDeploymentStatusActive,
|
Status: model.PagesDeploymentStatusActive,
|
||||||
FileCount: 1,
|
FileCount: 1,
|
||||||
}
|
}
|
||||||
require.NoError(t, db.DB(ctx).Create(active).Error)
|
require.NoError(t, repository.DB(ctx).Create(active).Error)
|
||||||
require.NoError(t, db.DB(ctx).Model(&model.PagesProject{}).
|
require.NoError(t, repository.DB(ctx).Model(&model.PagesProject{}).
|
||||||
Where("id = ?", project.ID).
|
Where("id = ?", project.ID).
|
||||||
Update("active_deployment_id", active.ID).Error)
|
Update("active_deployment_id", active.ID).Error)
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import (
|
|||||||
|
|
||||||
"Wavelet/core/contracts"
|
"Wavelet/core/contracts"
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
|
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||||
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -117,7 +117,7 @@ func TestPagesSourceHandlersReturnStableActionErrors(t *testing.T) {
|
|||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
future := time.Now().Add(time.Minute)
|
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).
|
Where("source_id = ?", busySource.ID).
|
||||||
Updates(map[string]any{
|
Updates(map[string]any{
|
||||||
"sync_status": pagesSourceStatusSyncing,
|
"sync_status": pagesSourceStatusSyncing,
|
||||||
@@ -181,7 +181,7 @@ func TestSyncSourceHandlerAcceptsEmptyBodyAndEmptyObject(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var executions []model.TaskExecution
|
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)
|
t.Fatalf("list Pages source task executions error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
if got, want := len(executions), 2; got != want {
|
if got, want := len(executions), 2; got != want {
|
||||||
|
|||||||
@@ -131,7 +131,6 @@ func reconcilePagesOrphanUploadCandidate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
outcome := pagesOrphanCleanupSkipped
|
outcome := pagesOrphanCleanupSkipped
|
||||||
uploadLocked := false
|
|
||||||
err := repository.WithPagesTx(ctx, func(tx *gorm.DB) error {
|
err := repository.WithPagesTx(ctx, func(tx *gorm.DB) error {
|
||||||
scopeOutcome, proceed, err := lockPagesOrphanCleanupScope(ctx, tx, candidate.ID, marker)
|
scopeOutcome, proceed, err := lockPagesOrphanCleanupScope(ctx, tx, candidate.ID, marker)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -141,7 +140,7 @@ func reconcilePagesOrphanUploadCandidate(
|
|||||||
outcome = scopeOutcome
|
outcome = scopeOutcome
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
lockedOutcome, locked, err := reconcileLockedPagesOrphanUpload(
|
lockedOutcome, _, err := reconcileLockedPagesOrphanUpload(
|
||||||
ctx,
|
ctx,
|
||||||
tx,
|
tx,
|
||||||
candidate.ID,
|
candidate.ID,
|
||||||
@@ -153,16 +152,15 @@ func reconcilePagesOrphanUploadCandidate(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
outcome = lockedOutcome
|
outcome = lockedOutcome
|
||||||
uploadLocked = locked
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return pagesOrphanCleanupSkipped, err
|
return pagesOrphanCleanupSkipped, err
|
||||||
}
|
}
|
||||||
if uploadLocked {
|
if outcome == pagesOrphanCleanupReconciled {
|
||||||
// Also heal a prior post-commit cache invalidation interruption when the
|
if err := ofupload.Remove(ctx, candidate.ID); err != nil {
|
||||||
// status transition was an idempotent no-op.
|
return pagesOrphanCleanupSkipped, err
|
||||||
ofupload.InvalidateUploadMetaCache(ctx, candidate.ID)
|
}
|
||||||
}
|
}
|
||||||
return outcome, nil
|
return outcome, nil
|
||||||
}
|
}
|
||||||
@@ -252,14 +250,7 @@ func reconcileLockedPagesOrphanUpload(
|
|||||||
return pagesOrphanCleanupReferenced, true, nil
|
return pagesOrphanCleanupReferenced, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
transitioned, err := ofupload.RemoveLockedTx(tx, &lockedUpload)
|
return pagesOrphanCleanupReconciled, true, nil
|
||||||
if err != nil {
|
|
||||||
return pagesOrphanCleanupSkipped, true, err
|
|
||||||
}
|
|
||||||
if transitioned {
|
|
||||||
return pagesOrphanCleanupReconciled, true, nil
|
|
||||||
}
|
|
||||||
return pagesOrphanCleanupSkipped, true, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func lockOptionalPagesCleanupRecord(
|
func lockOptionalPagesCleanupRecord(
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
"Wavelet/openflare/plugins/server/kernel/ofupload"
|
"Wavelet/openflare/plugins/server/kernel/ofupload"
|
||||||
db "Wavelet/plugins/infra/database"
|
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
@@ -164,7 +164,7 @@ func TestReconcilePagesOrphanUploadsSkipsBusyLeaseAndSourceMismatch(t *testing.T
|
|||||||
project := createPagesOrphanProject(t, ctx, "busy-orphan")
|
project := createPagesOrphanProject(t, ctx, "busy-orphan")
|
||||||
source := createPagesOrphanSource(t, ctx, project.ID)
|
source := createPagesOrphanSource(t, ctx, project.ID)
|
||||||
future := realNow.Add(time.Hour)
|
future := realNow.Add(time.Hour)
|
||||||
if err := db.DB(ctx).Create(&model.PagesProjectSourceRuntime{
|
if err := repository.DB(ctx).Create(&model.PagesProjectSourceRuntime{
|
||||||
SourceID: source.ID,
|
SourceID: source.ID,
|
||||||
LeaseToken: "busy-worker",
|
LeaseToken: "busy-worker",
|
||||||
LeaseExpiresAt: &future,
|
LeaseExpiresAt: &future,
|
||||||
@@ -213,7 +213,7 @@ func TestReconcilePagesOrphanUploadsRejectsMalformedMarker(t *testing.T) {
|
|||||||
metadata := candidate.Metadata
|
metadata := candidate.Metadata
|
||||||
metadata.Extra[pagesProjectIDMetadataKey] = "01"
|
metadata.Extra[pagesProjectIDMetadataKey] = "01"
|
||||||
candidate.Metadata = metadata
|
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)
|
t.Fatalf("seed malformed candidate marker error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,7 +239,7 @@ func TestPagesOrphanCleanupAndDeploymentCommitInterleavings(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("parsePagesOrphanMarker() error = %v, want nil", err)
|
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,
|
ProjectID: project.ID,
|
||||||
DeploymentNumber: 1,
|
DeploymentNumber: 1,
|
||||||
Checksum: "deployment-first",
|
Checksum: "deployment-first",
|
||||||
@@ -276,7 +276,7 @@ func TestPagesOrphanCleanupAndDeploymentCommitInterleavings(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
target := &model.PagesDeployment{ProjectID: project.ID, UploadID: candidate.ID}
|
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
|
var lockedProject model.PagesProject
|
||||||
if err := tx.Clauses(clause.Locking{Strength: pagesRowLockStrength}).First(&lockedProject, project.ID).Error; err != nil {
|
if err := tx.Clauses(clause.Locking{Strength: pagesRowLockStrength}).First(&lockedProject, project.ID).Error; err != nil {
|
||||||
return err
|
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)
|
t.Errorf("final deployment upload lock after cleanup error = %v, want %v", err, errSourceFinalFence)
|
||||||
}
|
}
|
||||||
var references int64
|
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)
|
t.Fatalf("count deployment references error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
if references != 0 {
|
if references != 0 {
|
||||||
@@ -303,7 +303,7 @@ func cleanupOutcomeTotal(summary PagesOrphanCleanupSummary) int {
|
|||||||
func createPagesOrphanProject(t *testing.T, ctx context.Context, slug string) *model.PagesProject {
|
func createPagesOrphanProject(t *testing.T, ctx context.Context, slug string) *model.PagesProject {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
project := &model.PagesProject{Name: slug, Slug: slug, Enabled: true}
|
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)
|
t.Fatalf("create Pages orphan project %q error = %v, want nil", slug, err)
|
||||||
}
|
}
|
||||||
return project
|
return project
|
||||||
@@ -317,7 +317,7 @@ func createPagesOrphanSource(t *testing.T, ctx context.Context, projectID uint)
|
|||||||
ConfigVersion: 1,
|
ConfigVersion: 1,
|
||||||
SourceIdentity: "orphan-source-identity",
|
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)
|
t.Fatalf("create Pages orphan source for project %d error = %v, want nil", projectID, err)
|
||||||
}
|
}
|
||||||
return source
|
return source
|
||||||
@@ -339,21 +339,19 @@ func createPagesOrphanUpload(
|
|||||||
extra[pagesSourceIDMetadataKey] = strconv.FormatUint(uint64(*sourceID), 10)
|
extra[pagesSourceIDMetadataKey] = strconv.FormatUint(uint64(*sourceID), 10)
|
||||||
}
|
}
|
||||||
candidate := &model.Upload{
|
candidate := &model.Upload{
|
||||||
UserID: 999,
|
UserID: 999,
|
||||||
FileName: "site.zip",
|
FileName: "site.zip",
|
||||||
FilePath: "pages/orphan-site.zip",
|
FilePath: "pages/orphan-site.zip",
|
||||||
FileSize: 64,
|
Size: 64,
|
||||||
MimeType: "application/zip",
|
MimeType: "application/zip",
|
||||||
Extension: "zip",
|
Hash: "orphan-checksum",
|
||||||
Hash: "orphan-checksum",
|
Type: ofupload.ReservedPagesDeploymentType,
|
||||||
Type: ofupload.ReservedPagesDeploymentType,
|
Status: model.UploadStatusUsed,
|
||||||
Status: model.UploadStatusUsed,
|
Metadata: model.UploadMetadata{Extra: extra},
|
||||||
AccessMode: 0,
|
CreatedAt: createdAt,
|
||||||
Metadata: model.UploadMetadata{Extra: extra},
|
UpdatedAt: createdAt,
|
||||||
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)
|
t.Fatalf("create Pages orphan upload error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
return candidate
|
return candidate
|
||||||
@@ -362,7 +360,7 @@ func createPagesOrphanUpload(
|
|||||||
func assertPagesCleanupUploadStatus(t *testing.T, ctx context.Context, uploadID uint64, want model.UploadStatus) {
|
func assertPagesCleanupUploadStatus(t *testing.T, ctx context.Context, uploadID uint64, want model.UploadStatus) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
var got model.Upload
|
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)
|
t.Fatalf("load upload %d error = %v, want nil", uploadID, err)
|
||||||
}
|
}
|
||||||
if got.Status != want {
|
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) {
|
func assertPagesCleanupTotalStat(t *testing.T, ctx context.Context, want int64) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
var stat model.UploadStat
|
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)
|
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)
|
t.Errorf("total upload stat FileCount = %d, want %d", stat.FileCount, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSourceLeaseHeartbeatRenewsAndCancelsOnOwnershipLoss(t *testing.T) {
|
func TestSourceLeaseHeartbeatRenewsAndCancelsOnOwnershipLoss(t *testing.T) {
|
||||||
@@ -38,7 +37,7 @@ func TestSourceLeaseHeartbeatRenewsAndCancelsOnOwnershipLoss(t *testing.T) {
|
|||||||
t.Cleanup(func() { _ = heartbeat.stop() })
|
t.Cleanup(func() { _ = heartbeat.stop() })
|
||||||
|
|
||||||
var initial model.PagesProjectSourceRuntime
|
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)
|
t.Fatalf("load initial heartbeat runtime error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
if initial.LeaseExpiresAt == nil {
|
if initial.LeaseExpiresAt == nil {
|
||||||
@@ -47,7 +46,7 @@ func TestSourceLeaseHeartbeatRenewsAndCancelsOnOwnershipLoss(t *testing.T) {
|
|||||||
deadline := time.Now().Add(2 * time.Second)
|
deadline := time.Now().Add(2 * time.Second)
|
||||||
for {
|
for {
|
||||||
var renewedRuntime model.PagesProjectSourceRuntime
|
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)
|
t.Fatalf("load renewed heartbeat runtime error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
if renewedRuntime.LeaseExpiresAt != nil && renewedRuntime.LeaseExpiresAt.After(*initial.LeaseExpiresAt) {
|
if renewedRuntime.LeaseExpiresAt != nil && renewedRuntime.LeaseExpiresAt.After(*initial.LeaseExpiresAt) {
|
||||||
@@ -59,7 +58,7 @@ func TestSourceLeaseHeartbeatRenewsAndCancelsOnOwnershipLoss(t *testing.T) {
|
|||||||
time.Sleep(10 * time.Millisecond)
|
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).
|
Where("source_id = ?", source.ID).
|
||||||
Update("lease_token", "replacement-owner").Error; err != nil {
|
Update("lease_token", "replacement-owner").Error; err != nil {
|
||||||
t.Fatalf("replace heartbeat lease owner error = %v, want nil", err)
|
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)
|
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).
|
Where("source_id = ?", source.ID).
|
||||||
Update("lease_expires_at", &past).Error; err != nil {
|
Update("lease_expires_at", &past).Error; err != nil {
|
||||||
t.Fatalf("expire first lease error = %v, want nil", err)
|
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)
|
t.Fatalf("failSourceLease(expired owner) error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
var runtime model.PagesProjectSourceRuntime
|
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)
|
t.Fatalf("load runtime after takeover error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
if got, want := runtime.LeaseToken, takeover.LeaseToken; got != want {
|
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)
|
t.Fatalf("failSourceLease(current owner) error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
var failedRuntime model.PagesProjectSourceRuntime
|
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)
|
t.Fatalf("load failed runtime error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
if got, want := failedRuntime.SyncStatus, pagesSourceStatusFailed; got != want {
|
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")
|
t.Error("renewSourceLease(after source update) = true, want false")
|
||||||
}
|
}
|
||||||
var updatedSource model.PagesProjectSource
|
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)
|
t.Fatalf("load updated source error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
if got, want := updatedSource.ConfigVersion, source.ConfigVersion+1; got != want {
|
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)
|
t.Errorf("ContentConfigVersion after RootDir update = %d, want %d", got, want)
|
||||||
}
|
}
|
||||||
var runtime model.PagesProjectSourceRuntime
|
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)
|
t.Fatalf("load fenced runtime error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
if runtime.LeaseToken != "" || runtime.LeaseExpiresAt != nil {
|
if runtime.LeaseToken != "" || runtime.LeaseExpiresAt != nil {
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import (
|
|||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||||
"Wavelet/openflare/share/githubrelease"
|
"Wavelet/openflare/share/githubrelease"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@@ -42,7 +41,7 @@ func TestGitHubLatestAutoConfigPreservesIdentityAndRuntimeCursor(t *testing.T) {
|
|||||||
seenRevision := strings.Repeat("a", sourceRevisionHexLength)
|
seenRevision := strings.Repeat("a", sourceRevisionHexLength)
|
||||||
appliedRevision := strings.Repeat("b", sourceRevisionHexLength)
|
appliedRevision := strings.Repeat("b", sourceRevisionHexLength)
|
||||||
future := time.Now().Add(time.Hour)
|
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).
|
Where("source_id = ?", source.ID).
|
||||||
Updates(map[string]any{
|
Updates(map[string]any{
|
||||||
"etag": `"cursor-etag"`,
|
"etag": `"cursor-etag"`,
|
||||||
@@ -67,7 +66,7 @@ func TestGitHubLatestAutoConfigPreservesIdentityAndRuntimeCursor(t *testing.T) {
|
|||||||
if err := validateGitHubSourceInput(input); err != nil {
|
if err := validateGitHubSourceInput(input); err != nil {
|
||||||
t.Fatalf("validateGitHubSourceInput(auto latest) error = %v, want nil", err)
|
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)
|
changed, err := updateGitHubSourceTx(tx, project.ID, input)
|
||||||
if err == nil && !changed {
|
if err == nil && !changed {
|
||||||
return errors.New("auto config update was treated as no-op")
|
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)
|
now := time.Date(2026, 7, 19, 12, 0, 0, 0, time.UTC)
|
||||||
usePagesSourceScannerClock(t, now)
|
usePagesSourceScannerClock(t, now)
|
||||||
expiredAt := now.Add(-time.Minute)
|
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).
|
Where("source_id = ?", source.ID).
|
||||||
Updates(map[string]any{
|
Updates(map[string]any{
|
||||||
"sync_status": pagesSourceStatusChecking,
|
"sync_status": pagesSourceStatusChecking,
|
||||||
@@ -167,7 +166,7 @@ func TestRecoverExpiredPagesSourceLeaseUsesExactCASAndStableJitter(t *testing.T)
|
|||||||
}
|
}
|
||||||
|
|
||||||
renewedExpiry := now.Add(time.Minute)
|
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).
|
Where("source_id = ?", source.ID).
|
||||||
Updates(map[string]any{
|
Updates(map[string]any{
|
||||||
"sync_status": pagesSourceStatusSyncing,
|
"sync_status": pagesSourceStatusSyncing,
|
||||||
@@ -274,24 +273,24 @@ func TestPagesSourceScannerSerialBatchIsolation304AndActualBacklog(t *testing.T)
|
|||||||
byRepository := make(map[string]int, 22)
|
byRepository := make(map[string]int, 22)
|
||||||
for index := 1; index <= 22; index++ {
|
for index := 1; index <= 22; index++ {
|
||||||
project := mustCreatePagesSourceProject(t, ctx, fmt.Sprintf("scanner-batch-%02d", 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{
|
source, runtime := mustConfigureGitHubSourceWithoutDispatch(t, ctx, project.ID, SourceUpdateInput{
|
||||||
SourceType: PagesSourceTypeGitHubRelease,
|
SourceType: PagesSourceTypeGitHubRelease,
|
||||||
RepositoryURL: "https://github.com/" + repository,
|
RepositoryURL: "https://github.com/" + repoPath,
|
||||||
AutoUpdateEnabled: index != 4,
|
AutoUpdateEnabled: index != 4,
|
||||||
CheckIntervalMinutes: 60,
|
CheckIntervalMinutes: 60,
|
||||||
})
|
})
|
||||||
if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||||
Where("source_id = ?", source.ID).
|
Where("source_id = ?", source.ID).
|
||||||
Update("next_check_at", &dueAt).Error; err != nil {
|
Update("next_check_at", &dueAt).Error; err != nil {
|
||||||
t.Fatalf("mark source %d due error = %v, want nil", source.ID, err)
|
t.Fatalf("mark source %d due error = %v, want nil", source.ID, err)
|
||||||
}
|
}
|
||||||
fixtures = append(fixtures, fixture{source: source, runtime: runtime, repository: repository})
|
fixtures = append(fixtures, fixture{source: source, runtime: runtime, repository: repoPath})
|
||||||
byRepository[repository] = index
|
byRepository[repoPath] = index
|
||||||
}
|
}
|
||||||
|
|
||||||
busyUntil := now.Add(time.Hour)
|
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).
|
Where("source_id = ?", fixtures[0].source.ID).
|
||||||
Updates(map[string]any{
|
Updates(map[string]any{
|
||||||
"sync_status": pagesSourceStatusChecking,
|
"sync_status": pagesSourceStatusChecking,
|
||||||
@@ -302,7 +301,7 @@ func TestPagesSourceScannerSerialBatchIsolation304AndActualBacklog(t *testing.T)
|
|||||||
}
|
}
|
||||||
|
|
||||||
stored304Revision := strings.Repeat("3", sourceRevisionHexLength)
|
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).
|
Where("source_id = ?", fixtures[2].source.ID).
|
||||||
Updates(map[string]any{
|
Updates(map[string]any{
|
||||||
"etag": `"stored-etag"`,
|
"etag": `"stored-etag"`,
|
||||||
@@ -313,7 +312,7 @@ func TestPagesSourceScannerSerialBatchIsolation304AndActualBacklog(t *testing.T)
|
|||||||
t.Fatalf("seed 304 cursor error = %v, want nil", err)
|
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).
|
Where("source_id = ?", fixtures[4].source.ID).
|
||||||
Updates(map[string]any{
|
Updates(map[string]any{
|
||||||
"last_applied_revision": strings.Repeat("a", sourceRevisionHexLength),
|
"last_applied_revision": strings.Repeat("a", sourceRevisionHexLength),
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import (
|
|||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
"Wavelet/openflare/plugins/server/kernel/ofupload"
|
"Wavelet/openflare/plugins/server/kernel/ofupload"
|
||||||
"Wavelet/openflare/share/pagesarchive"
|
"Wavelet/openflare/share/pagesarchive"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"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)
|
t.Errorf("deployment provenance = label:%q meta:%q, want no query secret", deployment.SourceLabel, deployment.SourceMeta)
|
||||||
}
|
}
|
||||||
var runtime model.PagesProjectSourceRuntime
|
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)
|
t.Fatalf("load source runtime error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
if got, want := runtime.SyncStatus, pagesSourceStatusIdle; got != want {
|
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)
|
t.Errorf("runtime lease = (%q, %v), want cleared", runtime.LeaseToken, runtime.LeaseExpiresAt)
|
||||||
}
|
}
|
||||||
var uploadRecord model.Upload
|
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)
|
t.Fatalf("load deployment upload %d error = %v, want nil", deployment.UploadID, err)
|
||||||
}
|
}
|
||||||
if got, want := uploadRecord.Status, model.UploadStatusUsed; got != want {
|
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)
|
t.Errorf("reused deployment ID = %d, want %d", got, want)
|
||||||
}
|
}
|
||||||
var deploymentCount, uploadCount int64
|
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)
|
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)
|
t.Fatalf("count source uploads error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
if got, want := deploymentCount, int64(1); got != want {
|
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"})
|
packageBytes := testPagesZip(t, map[string]string{"index.html": "never-activate"})
|
||||||
mutationResult := make(chan error, 1)
|
mutationResult := make(chan error, 1)
|
||||||
server := newPagesArchiveServer(t, http.StatusOK, packageBytes, func() error {
|
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).
|
Where("id = ?", project.ID).
|
||||||
Update("content_config_version", gorm.Expr("content_config_version + 1")).Error
|
Update("content_config_version", gorm.Expr("content_config_version + 1")).Error
|
||||||
mutationResult <- err
|
mutationResult <- err
|
||||||
@@ -304,14 +303,14 @@ func TestSyncRemoteSourceFinalFenceCompensatesIngest(t *testing.T) {
|
|||||||
t.Errorf("ActiveDeploymentID after final fence = %v, want %d", storedProject.ActiveDeploymentID, oldActive.ID)
|
t.Errorf("ActiveDeploymentID after final fence = %v, want %d", storedProject.ActiveDeploymentID, oldActive.ID)
|
||||||
}
|
}
|
||||||
var deployments []model.PagesDeployment
|
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)
|
t.Fatalf("list deployments after final fence error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
if got, want := len(deployments), 1; got != want {
|
if got, want := len(deployments), 1; got != want {
|
||||||
t.Errorf("deployment count after final fence = %d, want %d", got, want)
|
t.Errorf("deployment count after final fence = %d, want %d", got, want)
|
||||||
}
|
}
|
||||||
var uploads []model.Upload
|
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)
|
t.Fatalf("list uploads after final fence error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
var compensated *model.Upload
|
var compensated *model.Upload
|
||||||
@@ -328,7 +327,7 @@ func TestSyncRemoteSourceFinalFenceCompensatesIngest(t *testing.T) {
|
|||||||
t.Errorf("compensated upload Status = %q, want %q", got, want)
|
t.Errorf("compensated upload Status = %q, want %q", got, want)
|
||||||
}
|
}
|
||||||
var danglingCount int64
|
var danglingCount int64
|
||||||
if err := db.DB(ctx).Model(&model.PagesDeployment{}).
|
if err := repository.DB(ctx).Model(&model.PagesDeployment{}).
|
||||||
Where("upload_id = ?", compensated.ID).
|
Where("upload_id = ?", compensated.ID).
|
||||||
Count(&danglingCount).Error; err != nil {
|
Count(&danglingCount).Error; err != nil {
|
||||||
t.Fatalf("count compensated upload references error = %v, want nil", err)
|
t.Fatalf("count compensated upload references error = %v, want nil", err)
|
||||||
@@ -358,12 +357,12 @@ func TestCommitSourceDeploymentRechecksLeaseAfterUploadLocks(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("GetPagesDeploymentByID(%d) error = %v, want nil", first.Deployment.ID, err)
|
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).
|
Where("id = ?", project.ID).
|
||||||
Update("active_deployment_id", nil).Error; err != nil {
|
Update("active_deployment_id", nil).Error; err != nil {
|
||||||
t.Fatalf("clear active deployment error = %v, want nil", err)
|
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).
|
Where("id = ?", deployment.ID).
|
||||||
Update("status", model.PagesDeploymentStatusUploaded).Error; err != nil {
|
Update("status", model.PagesDeploymentStatusUploaded).Error; err != nil {
|
||||||
t.Fatalf("reset deployment status error = %v, want nil", err)
|
t.Fatalf("reset deployment status error = %v, want nil", err)
|
||||||
@@ -371,7 +370,7 @@ func TestCommitSourceDeploymentRechecksLeaseAfterUploadLocks(t *testing.T) {
|
|||||||
|
|
||||||
snapshot := mustAcquireRemoteSyncLease(t, ctx, source)
|
snapshot := mustAcquireRemoteSyncLease(t, ctx, source)
|
||||||
expiresAt := time.Now().Add(time.Hour)
|
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).
|
Where("source_id = ? AND lease_token = ?", source.ID, snapshot.LeaseToken).
|
||||||
Update("lease_expires_at", &expiresAt).Error; err != nil {
|
Update("lease_expires_at", &expiresAt).Error; err != nil {
|
||||||
t.Fatalf("set deterministic lease expiry error = %v, want nil", err)
|
t.Fatalf("set deterministic lease expiry error = %v, want nil", err)
|
||||||
@@ -465,7 +464,7 @@ func TestCompensateSourceIngestSurvivesCanceledParentContext(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
var uploadRecord model.Upload
|
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)
|
t.Fatalf("load compensated upload error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
if got, want := uploadRecord.Status, model.UploadStatusDeleted; got != want {
|
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)
|
t.Errorf("project %d ActiveDeploymentID = %v, want %d", projectID, project.ActiveDeploymentID, oldActiveID)
|
||||||
}
|
}
|
||||||
var deploymentCount int64
|
var deploymentCount int64
|
||||||
if err := db.DB(ctx).Model(&model.PagesDeployment{}).
|
if err := repository.DB(ctx).Model(&model.PagesDeployment{}).
|
||||||
Where("project_id = ?", projectID).
|
Where("project_id = ?", projectID).
|
||||||
Count(&deploymentCount).Error; err != nil {
|
Count(&deploymentCount).Error; err != nil {
|
||||||
t.Fatalf("count project %d deployments error = %v, want nil", projectID, err)
|
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)
|
t.Errorf("project %d deployment count = %d, want %d", projectID, got, want)
|
||||||
}
|
}
|
||||||
var runtime model.PagesProjectSourceRuntime
|
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)
|
t.Fatalf("load source %d runtime error = %v, want nil", sourceID, err)
|
||||||
}
|
}
|
||||||
if got, want := runtime.SyncStatus, pagesSourceStatusFailed; got != want {
|
if got, want := runtime.SyncStatus, pagesSourceStatusFailed; got != want {
|
||||||
@@ -529,19 +528,17 @@ func TestCommitSourceDeploymentRejectsDeletedTargetUpload(t *testing.T) {
|
|||||||
identity := source.SourceIdentity
|
identity := source.SourceIdentity
|
||||||
revision := strings.Repeat("d", 64)
|
revision := strings.Repeat("d", 64)
|
||||||
uploadRecord := &model.Upload{
|
uploadRecord := &model.Upload{
|
||||||
ID: 987654321,
|
ID: 987654321,
|
||||||
UserID: 999,
|
UserID: 999,
|
||||||
FileName: "deleted.zip",
|
FileName: "deleted.zip",
|
||||||
FilePath: "deleted.zip",
|
FilePath: "deleted.zip",
|
||||||
FileSize: 1,
|
Size: 1,
|
||||||
MimeType: "application/zip",
|
MimeType: "application/zip",
|
||||||
Extension: "zip",
|
Hash: revision,
|
||||||
Hash: revision,
|
Type: ofupload.ReservedPagesDeploymentType,
|
||||||
Type: ofupload.ReservedPagesDeploymentType,
|
Status: model.UploadStatusDeleted,
|
||||||
Status: model.UploadStatusDeleted,
|
|
||||||
AccessMode: 0,
|
|
||||||
}
|
}
|
||||||
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)
|
t.Fatalf("create deleted upload error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
deployment := &model.PagesDeployment{
|
deployment := &model.PagesDeployment{
|
||||||
@@ -560,10 +557,10 @@ func TestCommitSourceDeploymentRejectsDeletedTargetUpload(t *testing.T) {
|
|||||||
SourceMeta: `{"provider":"remote_url","display_name":"deleted.zip"}`,
|
SourceMeta: `{"provider":"remote_url","display_name":"deleted.zip"}`,
|
||||||
TriggerType: pagesSourceTriggerManualSync,
|
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)
|
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,
|
DeploymentID: deployment.ID,
|
||||||
Path: "index.html",
|
Path: "index.html",
|
||||||
Size: 1,
|
Size: 1,
|
||||||
@@ -600,7 +597,7 @@ func TestCommitSourceDeploymentRejectsDeletedTargetUpload(t *testing.T) {
|
|||||||
t.Errorf("ActiveDeploymentID after deleted upload rejection = %v, want nil", storedProject.ActiveDeploymentID)
|
t.Errorf("ActiveDeploymentID after deleted upload rejection = %v, want nil", storedProject.ActiveDeploymentID)
|
||||||
}
|
}
|
||||||
var activeCount int64
|
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).
|
Where("project_id = ? AND status = ?", project.ID, model.PagesDeploymentStatusActive).
|
||||||
Count(&activeCount).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
Count(&activeCount).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
t.Fatalf("count active deployments error = %v, want nil", err)
|
t.Fatalf("count active deployments error = %v, want nil", err)
|
||||||
|
|||||||
@@ -13,16 +13,15 @@ import (
|
|||||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupPagesSourceTest(t *testing.T) context.Context {
|
func setupPagesSourceTest(t *testing.T) context.Context {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
cleanup := setupPagesTestDB(t)
|
cleanup := setupPagesTestDB(t)
|
||||||
t.Cleanup(cleanup)
|
t.Cleanup(cleanup)
|
||||||
sqlDB, err := db.DB(t.Context()).DB()
|
sqlDB, err := repository.DB(t.Context()).DB()
|
||||||
if err != nil {
|
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
|
// SQLite :memory: is scoped to one connection. Keeping one connection also
|
||||||
// makes lease tests exercise the production CAS without creating empty
|
// 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)
|
t.Fatalf("UpdateSource(%d, %q) error = %v, want nil", projectID, remoteURL, err)
|
||||||
}
|
}
|
||||||
var source model.PagesProjectSource
|
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)
|
t.Fatalf("load source for project %d error = %v, want nil", projectID, err)
|
||||||
}
|
}
|
||||||
var runtime model.PagesProjectSourceRuntime
|
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)
|
t.Fatalf("load runtime for source %d error = %v, want nil", source.ID, err)
|
||||||
}
|
}
|
||||||
return &source, &runtime
|
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)
|
t.Fatalf("UpdateSource(%d, no-op) error = %v, want nil", project.ID, err)
|
||||||
}
|
}
|
||||||
var unchangedSource model.PagesProjectSource
|
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)
|
t.Fatalf("load no-op source error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
if got, want := unchangedSource.ConfigVersion, source.ConfigVersion; got != want {
|
if got, want := unchangedSource.ConfigVersion, source.ConfigVersion; got != want {
|
||||||
@@ -197,7 +196,7 @@ func TestRemoteSourceCRUDPreservesSecretAndResetsRuntimeByIdentity(t *testing.T)
|
|||||||
seenRevision := strings.Repeat("a", 64)
|
seenRevision := strings.Repeat("a", 64)
|
||||||
appliedRevision := strings.Repeat("b", 64)
|
appliedRevision := strings.Repeat("b", 64)
|
||||||
future := time.Now().Add(time.Hour)
|
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).
|
Where("source_id = ?", source.ID).
|
||||||
Updates(map[string]any{
|
Updates(map[string]any{
|
||||||
"last_seen_revision": seenRevision,
|
"last_seen_revision": seenRevision,
|
||||||
@@ -327,10 +326,10 @@ func TestDeleteSourceIsIdempotentAndKeepsDeploymentState(t *testing.T) {
|
|||||||
SourceType: "manual_upload",
|
SourceType: "manual_upload",
|
||||||
TriggerType: "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)
|
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).
|
Where("id = ?", project.ID).
|
||||||
Update("active_deployment_id", deployment.ID).Error; err != nil {
|
Update("active_deployment_id", deployment.ID).Error; err != nil {
|
||||||
t.Fatalf("set active deployment error = %v, want nil", err)
|
t.Fatalf("set active deployment error = %v, want nil", err)
|
||||||
@@ -346,13 +345,13 @@ func TestDeleteSourceIsIdempotentAndKeepsDeploymentState(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var sourceCount, runtimeCount, deploymentCount int64
|
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)
|
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)
|
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)
|
t.Fatalf("count deployment error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
if sourceCount != 0 || runtimeCount != 0 || deploymentCount != 1 {
|
if sourceCount != 0 || runtimeCount != 0 || deploymentCount != 1 {
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -29,10 +28,10 @@ func setupApplyLogTestDB(t *testing.T) func() {
|
|||||||
err = sqliteDB.AutoMigrate(&model.OpenFlareApplyLog{})
|
err = sqliteDB.AutoMigrate(&model.OpenFlareApplyLog{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
db.SetDB(sqliteDB)
|
repository.SetDBForTest(sqliteDB)
|
||||||
|
|
||||||
return func() {
|
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},
|
{NodeID: "node-logs", Version: "v3", Result: "success", Message: "3", CreatedAt: now},
|
||||||
}
|
}
|
||||||
for i := range logs {
|
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{
|
pageResult, err := ListPage(ctx, ListQuery{
|
||||||
|
|||||||
+5
-6
@@ -20,7 +20,6 @@ import (
|
|||||||
oftls "Wavelet/openflare/plugins/server/domain/tls"
|
oftls "Wavelet/openflare/plugins/server/domain/tls"
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
"Wavelet/openflare/plugins/server/kernel/runtimeconfig"
|
"Wavelet/openflare/plugins/server/kernel/runtimeconfig"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -29,7 +28,7 @@ import (
|
|||||||
func TestBuildCertificateSupportFilesDecryptsSealedPrivateKey(t *testing.T) {
|
func TestBuildCertificateSupportFilesDecryptsSealedPrivateKey(t *testing.T) {
|
||||||
cleanup := setupConfigVersionTestDB(t)
|
cleanup := setupConfigVersionTestDB(t)
|
||||||
defer cleanup()
|
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()
|
previous := runtimeconfig.Get()
|
||||||
runtimeconfig.SetSessionSecret("test-session-secret-for-tls-seal")
|
runtimeconfig.SetSessionSecret("test-session-secret-for-tls-seal")
|
||||||
@@ -65,7 +64,7 @@ func TestBuildSnapshotReadsZoneDomainCertificates(t *testing.T) {
|
|||||||
cleanup := setupConfigVersionTestDB(t)
|
cleanup := setupConfigVersionTestDB(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
require.NoError(t, db.DB(ctx).AutoMigrate(&model.TLSCertificate{}))
|
require.NoError(t, repository.DB(ctx).AutoMigrate(&model.TLSCertificate{}))
|
||||||
|
|
||||||
previous := runtimeconfig.Get()
|
previous := runtimeconfig.Get()
|
||||||
runtimeconfig.SetSessionSecret("test-session-secret-for-zone-domain-snapshots")
|
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}
|
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))
|
require.NoError(t, repository.CreateProxyRouteRecord(ctx, route))
|
||||||
zone := &model.Zone{Domain: "example.com"}
|
zone := &model.Zone{Domain: "example.com"}
|
||||||
require.NoError(t, db.DB(ctx).Create(zone).Error)
|
require.NoError(t, repository.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, repository.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(&model.ZoneDomain{ZoneID: zone.ID, ProxyRouteID: &route.ID, Domain: "two.example.com", CertID: &second.ID}).Error)
|
||||||
|
|
||||||
bundle, err := buildCurrentConfigBundle(ctx, true)
|
bundle, err := buildCurrentConfigBundle(ctx, true)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import (
|
|||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
openrestyrender "Wavelet/openflare/share/render/openresty"
|
openrestyrender "Wavelet/openflare/share/render/openresty"
|
||||||
"Wavelet/pkg/cache/ram"
|
"Wavelet/pkg/cache/ram"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -45,9 +44,9 @@ func setupConfigVersionTestDB(t *testing.T) func() {
|
|||||||
&model.SystemConfig{},
|
&model.SystemConfig{},
|
||||||
))
|
))
|
||||||
|
|
||||||
db.SetDB(sqliteDB)
|
repository.SetDBForTest(sqliteDB)
|
||||||
return func() {
|
return func() {
|
||||||
db.SetDB(nil)
|
repository.SetDBForTest(nil)
|
||||||
ram.ResetForTest()
|
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) {
|
func createSnapshotZoneDomains(t *testing.T, ctx context.Context, route *model.ProxyRoute, domains ...string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
zone := &model.Zone{Domain: fmt.Sprintf("zone-%d.example", route.ID)}
|
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 {
|
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,
|
ZoneID: zone.ID,
|
||||||
ProxyRouteID: &route.ID,
|
ProxyRouteID: &route.ID,
|
||||||
Domain: domain,
|
Domain: domain,
|
||||||
@@ -69,7 +68,7 @@ func TestListConfigVersionsOrdersByCreatedAtDesc(t *testing.T) {
|
|||||||
cleanup := setupConfigVersionTestDB(t)
|
cleanup := setupConfigVersionTestDB(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
conn := db.DB(ctx)
|
conn := repository.DB(ctx)
|
||||||
require.NotNil(t, conn)
|
require.NotNil(t, conn)
|
||||||
|
|
||||||
newer := &model.ConfigVersion{
|
newer := &model.ConfigVersion{
|
||||||
@@ -220,7 +219,7 @@ func TestBuildCurrentConfigBundleEnablesGlobalPoWWithoutExplicitBinding(t *testi
|
|||||||
graphJSON, err := json.Marshal(snapshotPoWGraph())
|
graphJSON, err := json.Marshal(snapshotPoWGraph())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
globalGroup.Graph = string(graphJSON)
|
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)
|
bundle, err := buildCurrentConfigBundle(ctx, true)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|||||||
+7
-19
@@ -9,13 +9,12 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
|
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||||
|
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
||||||
"Wavelet/pkg/cache/ram"
|
"Wavelet/pkg/cache/ram"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupOriginErrorPageSnapshotDB(t *testing.T) func() {
|
func setupOriginErrorPageSnapshotDB(t *testing.T) func() {
|
||||||
@@ -25,15 +24,10 @@ func setupOriginErrorPageSnapshotDB(t *testing.T) func() {
|
|||||||
// 重置,否则 shuffle 下先跑的用例会污染后跑的用例。
|
// 重置,否则 shuffle 下先跑的用例会污染后跑的用例。
|
||||||
ram.ResetForTest()
|
ram.ResetForTest()
|
||||||
|
|
||||||
sqliteDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
|
_, _, cleanup := testhelper.SetupTestEnvironment(t)
|
||||||
DisableForeignKeyConstraintWhenMigrating: true,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NoError(t, sqliteDB.AutoMigrate(&model.SystemConfig{}))
|
|
||||||
db.SetDB(sqliteDB)
|
|
||||||
|
|
||||||
return func() {
|
return func() {
|
||||||
db.SetDB(nil)
|
cleanup()
|
||||||
ram.ResetForTest()
|
ram.ResetForTest()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,15 +52,9 @@ func TestBuildOpenRestyConfigSnapshotOriginErrorPageCustom(t *testing.T) {
|
|||||||
defer cleanup()
|
defer cleanup()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
require.NoError(t, db.DB(ctx).Create(&model.SystemConfig{
|
require.NoError(t, repository.SaveOrUpdateSystemConfig(ctx, model.ConfigKeyOriginErrorPageEnabled, "false"))
|
||||||
Key: model.ConfigKeyOriginErrorPageEnabled, Value: "false", Type: "business",
|
require.NoError(t, repository.SaveOrUpdateSystemConfig(ctx, model.ConfigKeyOriginErrorPageStatusCodes, `["522","500-502"]`))
|
||||||
}).Error)
|
require.NoError(t, repository.SaveOrUpdateSystemConfig(ctx, model.ConfigKeyOriginErrorPageHTML, "<h1>{{status}}</h1>"))
|
||||||
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: "<h1>{{status}}</h1>", Type: "business",
|
|
||||||
}).Error)
|
|
||||||
|
|
||||||
snapshot := buildOpenRestyConfigSnapshot(ctx)
|
snapshot := buildOpenRestyConfigSnapshot(ctx)
|
||||||
assert.False(t, snapshot.OriginErrorPageEnabled)
|
assert.False(t, snapshot.OriginErrorPageEnabled)
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
openrestyrender "Wavelet/openflare/share/render/openresty"
|
openrestyrender "Wavelet/openflare/share/render/openresty"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -95,7 +94,7 @@ func TestBuildSnapshotPagesDeploymentRejectsUnsafeStoredPaths(t *testing.T) {
|
|||||||
|
|
||||||
func requireDB(t *testing.T, ctx context.Context) *gorm.DB {
|
func requireDB(t *testing.T, ctx context.Context) *gorm.DB {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
conn := db.DB(ctx)
|
conn := repository.DB(ctx)
|
||||||
require.NotNil(t, conn)
|
require.NotNil(t, conn)
|
||||||
require.NoError(t, conn.AutoMigrate(
|
require.NoError(t, conn.AutoMigrate(
|
||||||
&model.PagesProject{},
|
&model.PagesProject{},
|
||||||
|
|||||||
+5
-6
@@ -13,7 +13,6 @@ import (
|
|||||||
|
|
||||||
"Wavelet/openflare/plugins/server/domain/waf"
|
"Wavelet/openflare/plugins/server/domain/waf"
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -38,7 +37,7 @@ func TestBuildSnapshotRejectsOversizedAggregateWAFIPGroups(t *testing.T) {
|
|||||||
Enabled: true,
|
Enabled: true,
|
||||||
IPList: string(ipList),
|
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)
|
groupIDs = append(groupIDs, group.ID)
|
||||||
}
|
}
|
||||||
createSnapshotRule(t, ctx, "oversized-aggregate", snapshotIPMatchGraphForGroups(groupIDs))
|
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"]`}
|
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"]`}
|
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, repository.DB(ctx).Create(referenced).Error)
|
||||||
require.NoError(t, db.DB(ctx).Create(unused).Error)
|
require.NoError(t, repository.DB(ctx).Create(unused).Error)
|
||||||
|
|
||||||
customA := createSnapshotRule(t, ctx, "custom-a", waf.DefaultRuleGraph())
|
customA := createSnapshotRule(t, ctx, "custom-a", waf.DefaultRuleGraph())
|
||||||
customB := createSnapshotRule(t, ctx, "custom-b", snapshotIPMatchGraph(referenced.ID))
|
customB := createSnapshotRule(t, ctx, "custom-b", snapshotIPMatchGraph(referenced.ID))
|
||||||
@@ -114,7 +113,7 @@ func TestBuildSnapshotRejectsInvalidWAFGraph(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
invalid := &model.OpenFlareWAFRuleGroup{Name: "invalid", Enabled: true, Graph: `{"schema_version":1,"nodes":[],"edges":[]}`, Revision: 1}
|
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)
|
_, err := buildSnapshotWAFDocument(ctx, nil)
|
||||||
require.ErrorContains(t, err, "invalid")
|
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)
|
raw, err := json.Marshal(graph)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
rule := &model.OpenFlareWAFRuleGroup{Name: name, Enabled: true, Graph: string(raw), Revision: 1}
|
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
|
return rule
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -25,9 +25,9 @@ func setupOriginTestDB(t *testing.T) func() {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, sqliteDB.AutoMigrate(&model.Origin{}))
|
require.NoError(t, sqliteDB.AutoMigrate(&model.Origin{}))
|
||||||
|
|
||||||
db.SetDB(sqliteDB)
|
repository.SetDBForTest(sqliteDB)
|
||||||
return func() {
|
return func() {
|
||||||
db.SetDB(nil)
|
repository.SetDBForTest(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -29,21 +28,21 @@ func setupProxyRouteTestDB(t *testing.T) func() {
|
|||||||
&model.TLSCertificate{},
|
&model.TLSCertificate{},
|
||||||
&model.PagesProject{},
|
&model.PagesProject{},
|
||||||
))
|
))
|
||||||
db.SetDB(sqliteDB)
|
repository.SetDBForTest(sqliteDB)
|
||||||
return func() { db.SetDB(nil) }
|
return func() { repository.SetDBForTest(nil) }
|
||||||
}
|
}
|
||||||
|
|
||||||
func createZoneDomain(t *testing.T, ctx context.Context, domain string, certID *uint) *model.ZoneDomain {
|
func createZoneDomain(t *testing.T, ctx context.Context, domain string, certID *uint) *model.ZoneDomain {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
zone := &model.Zone{Domain: "example.com"}
|
zone := &model.Zone{Domain: "example.com"}
|
||||||
var existing model.Zone
|
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
|
zone = &existing
|
||||||
} else {
|
} 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}
|
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
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +101,7 @@ func TestPagesRouteLocksAndRevalidatesTargetProject(t *testing.T) {
|
|||||||
Enabled: true,
|
Enabled: true,
|
||||||
ActiveDeploymentID: &activeDeploymentID,
|
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{
|
view, err := CreateProxyRoute(ctx, Input{
|
||||||
SiteName: "pages",
|
SiteName: "pages",
|
||||||
@@ -115,7 +114,7 @@ func TestPagesRouteLocksAndRevalidatesTargetProject(t *testing.T) {
|
|||||||
require.NotNil(t, view.PagesProjectID)
|
require.NotNil(t, view.PagesProjectID)
|
||||||
assert.Equal(t, project.ID, *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}
|
route := &model.ProxyRoute{UpstreamType: proxyRouteUpstreamTypePages, PagesProjectID: &project.ID}
|
||||||
err = repository.WithProxyRouteTx(ctx, func(tx *gorm.DB) error {
|
err = repository.WithProxyRouteTx(ctx, func(tx *gorm.DB) error {
|
||||||
return lockPagesProjectsForRouteMutation(tx, 0, route)
|
return lockPagesProjectsForRouteMutation(tx, 0, route)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
db "Wavelet/plugins/infra/database"
|
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -65,10 +65,10 @@ func setupLegacyImportDB(t *testing.T) (*sql.DB, func()) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
previous := db.DB(context.Background())
|
previous := repository.DB(context.Background())
|
||||||
db.SetDB(gormDB)
|
repository.SetDBForTest(gormDB)
|
||||||
return sqlDB, func() {
|
return sqlDB, func() {
|
||||||
db.SetDB(previous)
|
repository.SetDBForTest(previous)
|
||||||
_ = sqlDB.Close()
|
_ = sqlDB.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"github.com/stretchr/testify/require"
|
"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})
|
conn, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, conn.AutoMigrate(&model.Zone{}, &model.ZoneDomain{}, &model.TLSCertificate{}, &model.CFPointingGroup{}, &model.CFPointingMember{}))
|
require.NoError(t, conn.AutoMigrate(&model.Zone{}, &model.ZoneDomain{}, &model.TLSCertificate{}, &model.CFPointingGroup{}, &model.CFPointingMember{}))
|
||||||
db.SetDB(conn)
|
repository.SetDBForTest(conn)
|
||||||
t.Cleanup(func() { db.SetDB(nil) })
|
t.Cleanup(func() { repository.SetDBForTest(nil) })
|
||||||
return context.Background()
|
return context.Background()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import (
|
|||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
"Wavelet/openflare/plugins/server/kernel/runtimeconfig"
|
"Wavelet/openflare/plugins/server/kernel/runtimeconfig"
|
||||||
"Wavelet/pkg/idgen"
|
"Wavelet/pkg/idgen"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -51,7 +50,7 @@ func setupTLSTestDB(t *testing.T) func() {
|
|||||||
&model.TaskExecution{}, // 异步任务执行记录也需要 migrate
|
&model.TaskExecution{}, // 异步任务执行记录也需要 migrate
|
||||||
))
|
))
|
||||||
|
|
||||||
db.SetDB(sqliteDB)
|
repository.SetDBForTest(sqliteDB)
|
||||||
require.NoError(t, idgen.Init(1))
|
require.NoError(t, idgen.Init(1))
|
||||||
previous := runtimeconfig.Get()
|
previous := runtimeconfig.Get()
|
||||||
runtimeconfig.SetSessionSecret("test_session_secret_for_tls_encryption")
|
runtimeconfig.SetSessionSecret("test_session_secret_for_tls_encryption")
|
||||||
@@ -59,7 +58,7 @@ func setupTLSTestDB(t *testing.T) func() {
|
|||||||
oftask.SetService(&testhelper.NoopTaskService{})
|
oftask.SetService(&testhelper.NoopTaskService{})
|
||||||
|
|
||||||
return func() {
|
return func() {
|
||||||
db.SetDB(nil)
|
repository.SetDBForTest(nil)
|
||||||
runtimeconfig.Set(previous)
|
runtimeconfig.Set(previous)
|
||||||
credential.SetSessionSecret(previous.SessionSecret)
|
credential.SetSessionSecret(previous.SessionSecret)
|
||||||
tlsTestDBMu.Unlock()
|
tlsTestDBMu.Unlock()
|
||||||
@@ -75,8 +74,8 @@ func TestDeleteCertificateRejectsZoneDomainReference(t *testing.T) {
|
|||||||
certificate, err := CreateCertificate(ctx, CertificateInput{Name: "api-cert", CertPEM: certPEM, KeyPEM: keyPEM})
|
certificate, err := CreateCertificate(ctx, CertificateInput{Name: "api-cert", CertPEM: certPEM, KeyPEM: keyPEM})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
zone := &model.Zone{Domain: "example.com"}
|
zone := &model.Zone{Domain: "example.com"}
|
||||||
require.NoError(t, db.DB(ctx).Create(zone).Error)
|
require.NoError(t, repository.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(&model.ZoneDomain{ZoneID: zone.ID, Domain: "api.example.com", CertID: &certificate.ID}).Error)
|
||||||
|
|
||||||
err = DeleteCertificate(ctx, certificate.ID)
|
err = DeleteCertificate(ctx, certificate.ID)
|
||||||
require.EqualError(t, err, errCertificateDeleteReferenced)
|
require.EqualError(t, err, errCertificateDeleteReferenced)
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
"Wavelet/openflare/plugins/server/kernel/runtimeconfig"
|
"Wavelet/openflare/plugins/server/kernel/runtimeconfig"
|
||||||
oftask "Wavelet/openflare/plugins/server/kernel/task"
|
oftask "Wavelet/openflare/plugins/server/kernel/task"
|
||||||
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -23,7 +22,7 @@ func setupSSLRenewTestDB(t *testing.T) func() {
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
_, _, cleanup := testhelper.SetupTestEnvironment(t)
|
_, _, 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()
|
previous := runtimeconfig.Get()
|
||||||
runtimeconfig.SetSessionSecret("test_session_secret_for_ssl_renew")
|
runtimeconfig.SetSessionSecret("test_session_secret_for_ssl_renew")
|
||||||
oftask.SetService(&testhelper.NoopTaskService{})
|
oftask.SetService(&testhelper.NoopTaskService{})
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import (
|
|||||||
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -35,10 +34,10 @@ func setupIPGroupSyncTestDB(t *testing.T) func() {
|
|||||||
&model.OpenFlareWAFIPGroup{},
|
&model.OpenFlareWAFIPGroup{},
|
||||||
))
|
))
|
||||||
|
|
||||||
db.SetDB(sqliteDB)
|
repository.SetDBForTest(sqliteDB)
|
||||||
testhelper.SetupLogStoresForTest(t)
|
testhelper.SetupLogStoresForTest(t)
|
||||||
return func() {
|
return func() {
|
||||||
db.SetDB(nil)
|
repository.SetDBForTest(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -32,9 +31,9 @@ func setupWAFTestDB(t *testing.T) func() {
|
|||||||
&model.OriginProxyRoute{},
|
&model.OriginProxyRoute{},
|
||||||
))
|
))
|
||||||
|
|
||||||
db.SetDB(sqliteDB)
|
repository.SetDBForTest(sqliteDB)
|
||||||
return func() {
|
return func() {
|
||||||
db.SetDB(nil)
|
repository.SetDBForTest(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import (
|
|||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
"Wavelet/pkg/response"
|
"Wavelet/pkg/response"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -84,7 +83,7 @@ func TestRuleHandlersMapFailures(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return cleanup
|
return cleanup
|
||||||
}, want: http.StatusConflict},
|
}, 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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
@@ -157,7 +156,7 @@ func TestReplaceSiteRuleGroupsPreservesOrderAndRejectsGlobal(t *testing.T) {
|
|||||||
defer cleanup()
|
defer cleanup()
|
||||||
ctx := context.Background()
|
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"})
|
first, err := CreateRule(ctx, CreateRuleInput{Name: "first"})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
second, err := CreateRule(ctx, CreateRuleInput{Name: "second"})
|
second, err := CreateRule(ctx, CreateRuleInput{Name: "second"})
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
|
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||||
pkggeoip "Wavelet/openflare/share/geoip"
|
pkggeoip "Wavelet/openflare/share/geoip"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@@ -23,16 +23,16 @@ func TestEnsureRuntimeProviderInitializesConfiguredProvider(t *testing.T) {
|
|||||||
if err := sqliteDB.AutoMigrate(&model.SystemConfig{}); err != nil {
|
if err := sqliteDB.AutoMigrate(&model.SystemConfig{}); err != nil {
|
||||||
t.Fatalf("migrate: %v", err)
|
t.Fatalf("migrate: %v", err)
|
||||||
}
|
}
|
||||||
db.SetDB(sqliteDB)
|
repository.SetDBForTest(sqliteDB)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
db.SetDB(nil)
|
repository.SetDBForTest(nil)
|
||||||
ResetRuntimeForTest()
|
ResetRuntimeForTest()
|
||||||
})
|
})
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
ResetRuntimeForTest()
|
ResetRuntimeForTest()
|
||||||
// 通过 SystemConfig 设置 GeoIPProvider 配置
|
// 通过 SystemConfig 设置 GeoIPProvider 配置
|
||||||
if err := db.DB(ctx).Create(&model.SystemConfig{
|
if err := repository.DB(ctx).Create(&model.SystemConfig{
|
||||||
Key: model.ConfigKeyGeoIPProvider,
|
Key: model.ConfigKeyGeoIPProvider,
|
||||||
Value: pkggeoip.ProviderIPInfo,
|
Value: pkggeoip.ProviderIPInfo,
|
||||||
Type: "business",
|
Type: "business",
|
||||||
|
|||||||
@@ -4,8 +4,34 @@
|
|||||||
package analytics
|
package analytics
|
||||||
|
|
||||||
import (
|
import (
|
||||||
risklogstore "Wavelet/plugins/domain/risk_control/logstore"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UserAccessLog is Wavelet risk_control's w_user_access_logs entity.
|
const (
|
||||||
type UserAccessLog = risklogstore.UserAccessLog
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,10 +9,9 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
adminmodel "Wavelet/plugins/domain/admin/model"
|
"time"
|
||||||
authmodel "Wavelet/plugins/domain/auth"
|
|
||||||
uploadmodels "Wavelet/plugins/domain/upload/models"
|
"Wavelet/core/contracts"
|
||||||
usermodel "Wavelet/plugins/domain/user"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -20,57 +19,161 @@ const (
|
|||||||
maskThreshold = 8
|
maskThreshold = 8
|
||||||
)
|
)
|
||||||
|
|
||||||
// User is the Wavelet w_users entity.
|
// User represents a user identity view.
|
||||||
type User = usermodel.User
|
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.
|
func (User) TableName() string {
|
||||||
type AccessToken = usermodel.AccessToken
|
return "w_users"
|
||||||
|
}
|
||||||
|
|
||||||
// AuthSource is the Wavelet w_auth_sources entity.
|
func (u *User) SetEncryptedPassword(pwd string) error {
|
||||||
type AuthSource = authmodel.AuthSource
|
u.Password = pwd
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ExternalAccount is the Wavelet w_external_accounts entity.
|
// AccessToken represents an access token view.
|
||||||
type ExternalAccount = authmodel.ExternalAccount
|
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.
|
func (AccessToken) TableName() string {
|
||||||
type TaskExecution = adminmodel.TaskExecution
|
return "w_access_tokens"
|
||||||
|
}
|
||||||
|
|
||||||
// Template is the Wavelet w_templates entity.
|
// AuthSource represents an authentication source view.
|
||||||
type Template = adminmodel.Template
|
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.
|
// TaskExecution represents task execution entity.
|
||||||
type Schedule = adminmodel.Schedule
|
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.
|
func (TaskExecution) TableName() string {
|
||||||
type Upload = uploadmodels.Upload
|
return "w_task_executions"
|
||||||
|
}
|
||||||
|
|
||||||
// UploadMetadata is the Wavelet upload metadata JSON.
|
type UploadStatus = string
|
||||||
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
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// UploadStatusPending is a newly stored unused upload.
|
UploadStatusPending UploadStatus = "pending"
|
||||||
UploadStatusPending = uploadmodels.UploadStatusPending
|
UploadStatusUsed UploadStatus = "used"
|
||||||
// UploadStatusUsed is an in-use upload.
|
UploadStatusDeleted UploadStatus = "deleted"
|
||||||
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
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 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 值
|
// GenerateTokenString 生成加密安全的随机 Token 值
|
||||||
func GenerateTokenString() (string, error) {
|
func GenerateTokenString() (string, error) {
|
||||||
bytes := make([]byte, tokenByteLength)
|
bytes := make([]byte, tokenByteLength)
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
adminmodel "Wavelet/plugins/domain/admin/model"
|
"time"
|
||||||
|
|
||||||
|
"Wavelet/core/contracts"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 配置键常量 - 所有系统配置的 key 定义
|
// 配置键常量 - 所有系统配置的 key 定义
|
||||||
@@ -136,10 +138,46 @@ const (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// ConfigVisibilityHidden 表示配置不通过公共配置接口暴露
|
// ConfigVisibilityHidden 表示配置不通过公共配置接口暴露
|
||||||
ConfigVisibilityHidden = adminmodel.ConfigVisibilityHidden
|
ConfigVisibilityHidden = 0
|
||||||
// ConfigVisibilityVisible 表示配置通过公共配置接口暴露
|
// ConfigVisibilityVisible 表示配置通过公共配置接口暴露
|
||||||
ConfigVisibilityVisible = adminmodel.ConfigVisibilityVisible
|
ConfigVisibilityVisible = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
// SystemConfig is the Wavelet w_system_configs entity.
|
// SystemConfig is the system configuration model.
|
||||||
type SystemConfig = adminmodel.SystemConfig
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,9 +13,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"Wavelet/core/contracts"
|
"Wavelet/core/contracts"
|
||||||
waveletupload "Wavelet/plugins/domain/upload"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
"Wavelet/plugins/domain/upload/models"
|
|
||||||
"Wavelet/plugins/infra/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReservedPagesDeploymentType is managed exclusively by the Pages domain.
|
// ReservedPagesDeploymentType is managed exclusively by the Pages domain.
|
||||||
@@ -23,40 +21,77 @@ const ReservedPagesDeploymentType = "openflare_pages_deployment"
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// PolicyCreate always stores a new object and creates a new upload record.
|
// 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 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 returns an existing upload on hash match.
|
||||||
PolicyResolveExisting = waveletupload.PolicyResolveExisting
|
PolicyResolveExisting = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
// IngestRequest is the programmatic upload ingest payload.
|
||||||
// IngestRequest is the programmatic upload ingest payload.
|
type IngestRequest struct {
|
||||||
IngestRequest = waveletupload.IngestRequest
|
UserID uint64
|
||||||
// IngestResult reports ingest side effects.
|
Type string
|
||||||
IngestResult = waveletupload.IngestResult
|
FileName string
|
||||||
// IngestPolicy controls hash-collision behavior during ingest.
|
MimeType string
|
||||||
IngestPolicy = waveletupload.IngestPolicy
|
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 (
|
var (
|
||||||
storageMu sync.RWMutex
|
svcMu sync.RWMutex
|
||||||
storageSvc contracts.StorageService
|
storageSvc contracts.StorageService
|
||||||
|
uploadSvc contracts.UploadService
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetStorage injects the platform StorageService used to open stored objects.
|
// SetStorage injects the platform StorageService used to open stored objects.
|
||||||
func SetStorage(s contracts.StorageService) {
|
func SetStorage(s contracts.StorageService) {
|
||||||
storageMu.Lock()
|
svcMu.Lock()
|
||||||
defer storageMu.Unlock()
|
defer svcMu.Unlock()
|
||||||
storageSvc = s
|
storageSvc = s
|
||||||
}
|
}
|
||||||
|
|
||||||
func currentStorage() contracts.StorageService {
|
// SetUploadService injects the platform UploadService.
|
||||||
storageMu.RLock()
|
func SetUploadService(s contracts.UploadService) {
|
||||||
defer storageMu.RUnlock()
|
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
|
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.
|
// IngestFromLocalPath ingests a local regular file through Wavelet upload ingest.
|
||||||
func IngestFromLocalPath(ctx context.Context, localPath string, req IngestRequest) (IngestResult, error) {
|
func IngestFromLocalPath(ctx context.Context, localPath string, req IngestRequest) (IngestResult, error) {
|
||||||
localPath = strings.TrimSpace(localPath)
|
localPath = strings.TrimSpace(localPath)
|
||||||
@@ -79,55 +114,87 @@ func IngestFromLocalPath(ctx context.Context, localPath string, req IngestReques
|
|||||||
if req.Size <= 0 {
|
if req.Size <= 0 {
|
||||||
req.Size = info.Size()
|
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.
|
// GetActiveUpload loads an active (non-deleted) upload by ID.
|
||||||
func GetActiveUpload(ctx context.Context, id uint64) (models.Upload, error) {
|
func GetActiveUpload(ctx context.Context, id uint64) (contracts.UploadDTO, error) {
|
||||||
conn := database.DB(ctx)
|
svc := currentUpload()
|
||||||
if conn == nil {
|
if svc == nil {
|
||||||
return models.Upload{}, errors.New("database not initialized")
|
return contracts.UploadDTO{}, errors.New("upload service not available")
|
||||||
}
|
}
|
||||||
var upload models.Upload
|
u, err := svc.GetByID(ctx, id)
|
||||||
err := conn.Where("id = ? AND status <> ?", id, models.UploadStatusDeleted).First(&upload).Error
|
if err != nil {
|
||||||
return upload, err
|
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.
|
// OpenedUploadObject is a stored object stream plus the upload record.
|
||||||
type OpenedUploadObject struct {
|
type OpenedUploadObject struct {
|
||||||
Upload models.Upload
|
Upload contracts.UploadDTO
|
||||||
Body io.ReadCloser
|
Body io.ReadCloser
|
||||||
ContentType string
|
ContentType string
|
||||||
ContentLength int64
|
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) {
|
func OpenStoredUpload(ctx context.Context, id uint64) (*OpenedUploadObject, error) {
|
||||||
upload, err := GetActiveUpload(ctx, id)
|
svc := currentUpload()
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
svc := currentStorage()
|
|
||||||
if svc == nil {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &OpenedUploadObject{
|
return &OpenedUploadObject{
|
||||||
Upload: upload,
|
Upload: obj.Upload,
|
||||||
Body: obj.Body,
|
Body: obj.Body,
|
||||||
ContentType: obj.ContentType,
|
ContentType: obj.ContentType,
|
||||||
ContentLength: obj.ContentLength,
|
ContentLength: obj.ContentLength,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalFileCandidateRequest describes filesystem locations that may host a legacy blob.
|
// Remove removes an upload by ID.
|
||||||
type LocalFileCandidateRequest struct {
|
func Remove(ctx context.Context, id uint64) error {
|
||||||
StoredPath string
|
svc := currentUpload()
|
||||||
RelativePaths []string
|
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.
|
// 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
|
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.
|
// RebuildUploadStats rebuilds aggregate upload stats.
|
||||||
func RebuildUploadStats(ctx context.Context) error {
|
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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -5,12 +5,10 @@ package analytics
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics"
|
analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics"
|
||||||
"Wavelet/openflare/plugins/server/kernel/runtimeconfig"
|
"Wavelet/openflare/plugins/server/kernel/runtimeconfig"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ClickHouseOperationalStats summarizes ClickHouse merge/mutation pressure
|
// ClickHouseOperationalStats summarizes ClickHouse merge/mutation pressure
|
||||||
@@ -19,8 +17,9 @@ type ClickHouseOperationalStats = analyticsmodel.ClickHouseOperationalStats
|
|||||||
|
|
||||||
// GetClickHouseOperationalStats returns operational metrics for the configured database.
|
// GetClickHouseOperationalStats returns operational metrics for the configured database.
|
||||||
func GetClickHouseOperationalStats(ctx context.Context) (*ClickHouseOperationalStats, error) {
|
func GetClickHouseOperationalStats(ctx context.Context) (*ClickHouseOperationalStats, error) {
|
||||||
if db.ChConn == nil {
|
conn, err := ChConn(ctx)
|
||||||
return nil, errors.New("clickhouse native connection is not initialized")
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("clickhouse native connection is not initialized: %w", err)
|
||||||
}
|
}
|
||||||
database := runtimeconfig.Get().ClickHouse.Database
|
database := runtimeconfig.Get().ClickHouse.Database
|
||||||
stats := &ClickHouseOperationalStats{Database: database}
|
stats := &ClickHouseOperationalStats{Database: database}
|
||||||
@@ -32,35 +31,33 @@ SELECT
|
|||||||
FROM system.parts
|
FROM system.parts
|
||||||
WHERE active AND database = ?`
|
WHERE active AND database = ?`
|
||||||
var activeParts, totalRows uint64
|
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)
|
return nil, fmt.Errorf("query system.parts: %w", err)
|
||||||
}
|
}
|
||||||
stats.ActiveParts = safeInt64Count(activeParts)
|
stats.ActiveParts = safeInt64Count(activeParts)
|
||||||
stats.TotalRows = safeInt64Count(totalRows)
|
stats.TotalRows = safeInt64Count(totalRows)
|
||||||
|
|
||||||
mutationsSQL := `
|
mutationsSQL := `
|
||||||
SELECT count()
|
SELECT
|
||||||
|
count() AS pending_mutations
|
||||||
FROM system.mutations
|
FROM system.mutations
|
||||||
WHERE is_done = 0 AND database = ?`
|
WHERE NOT is_done AND database = ?`
|
||||||
if err := db.ChConn.QueryRow(ctx, mutationsSQL, database).Scan(&stats.PendingMutations); err != nil {
|
if err := conn.QueryRow(ctx, mutationsSQL, database).Scan(&stats.PendingMutations); err != nil {
|
||||||
return nil, fmt.Errorf("query system.mutations: %w", err)
|
return nil, fmt.Errorf("query system.mutations: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
asyncSQL := `
|
asyncSQL := `
|
||||||
SELECT
|
SELECT
|
||||||
count() AS queue_entries,
|
ifNull(sum(entries), 0) AS queue_entries,
|
||||||
ifNull(sum(bytes), 0) AS queue_bytes
|
ifNull(sum(bytes), 0) AS queue_bytes
|
||||||
FROM system.asynchronous_inserts
|
FROM system.asynchronous_inserts
|
||||||
WHERE database = ?`
|
WHERE database = ?`
|
||||||
var queueEntries, queueBytes uint64
|
var queueEntries, queueBytes uint64
|
||||||
if err := db.ChConn.QueryRow(ctx, asyncSQL, database).Scan(&queueEntries, &queueBytes); err != nil {
|
if err := conn.QueryRow(ctx, asyncSQL, database).Scan(&queueEntries, &queueBytes); err != nil {
|
||||||
// Older ClickHouse versions may not expose asynchronous_inserts; treat as optional.
|
return nil, fmt.Errorf("query system.asynchronous_inserts: %w", err)
|
||||||
stats.AsyncInsertQueue = 0
|
|
||||||
stats.AsyncInsertBytes = 0
|
|
||||||
} else {
|
|
||||||
stats.AsyncInsertQueue = safeInt64Count(queueEntries)
|
|
||||||
stats.AsyncInsertBytes = safeInt64Count(queueBytes)
|
|
||||||
}
|
}
|
||||||
|
stats.AsyncInsertQueue = safeInt64Count(queueEntries)
|
||||||
|
stats.AsyncInsertBytes = safeInt64Count(queueBytes)
|
||||||
|
|
||||||
return stats, nil
|
return stats, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -5,13 +5,11 @@ package analytics
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics"
|
analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
|
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
|
||||||
)
|
)
|
||||||
@@ -20,10 +18,7 @@ import (
|
|||||||
type NodeAccessLogRegionCount = analyticsmodel.NodeAccessLogRegionCount
|
type NodeAccessLogRegionCount = analyticsmodel.NodeAccessLogRegionCount
|
||||||
|
|
||||||
func nodeAccessLogConn() (driver.Conn, error) {
|
func nodeAccessLogConn() (driver.Conn, error) {
|
||||||
if db.ChConn == nil {
|
return ChConn(context.Background())
|
||||||
return nil, errors.New("clickhouse connection is not initialized")
|
|
||||||
}
|
|
||||||
return db.ChConn, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListNodeAccessLogs returns access logs matching filter.
|
// ListNodeAccessLogs returns access logs matching filter.
|
||||||
|
|||||||
+2
-3
@@ -10,7 +10,6 @@ import (
|
|||||||
|
|
||||||
analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics"
|
analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics"
|
||||||
"Wavelet/pkg/idgen"
|
"Wavelet/pkg/idgen"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -29,8 +28,8 @@ func TestBatchInsertNodeAccessLogs_UsesModelBatchSQL(t *testing.T) {
|
|||||||
batch: mockBatch,
|
batch: mockBatch,
|
||||||
batchQuery: analyticsmodel.NodeAccessLog{}.BatchInsertSQL(),
|
batchQuery: analyticsmodel.NodeAccessLog{}.BatchInsertSQL(),
|
||||||
}
|
}
|
||||||
db.SetChConnForTest(mockConn)
|
SetChConnForTest(mockConn)
|
||||||
t.Cleanup(func() { db.SetChConnForTest(nil) })
|
t.Cleanup(func() { SetChConnForTest(nil) })
|
||||||
|
|
||||||
loggedAt := time.Now().UTC()
|
loggedAt := time.Now().UTC()
|
||||||
err := BatchInsertNodeAccessLogs(ctx, []analyticsmodel.NodeAccessLog{
|
err := BatchInsertNodeAccessLogs(ctx, []analyticsmodel.NodeAccessLog{
|
||||||
|
|||||||
+4
-5
@@ -5,14 +5,12 @@ package analytics
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics"
|
analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics"
|
||||||
"Wavelet/pkg/idgen"
|
"Wavelet/pkg/idgen"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// BatchInsertNodeAccessLogs writes node access logs to ClickHouse using the native batch API.
|
// 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 {
|
if len(logs) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if db.ChConn == nil {
|
conn, err := ChConn(ctx)
|
||||||
return errors.New("clickhouse connection is not initialized")
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("prepare clickhouse batch: %w", err)
|
return fmt.Errorf("prepare clickhouse batch: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,22 +5,17 @@ package analytics
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics"
|
analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
|
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
|
||||||
)
|
)
|
||||||
|
|
||||||
func observabilityConn() (driver.Conn, error) {
|
func observabilityConn() (driver.Conn, error) {
|
||||||
if db.ChConn == nil {
|
return ChConn(context.Background())
|
||||||
return nil, errors.New("clickhouse connection is not initialized")
|
|
||||||
}
|
|
||||||
return db.ChConn, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListNodeMetricSnapshots returns metric snapshots matching filter.
|
// ListNodeMetricSnapshots returns metric snapshots matching filter.
|
||||||
|
|||||||
+8
-10
@@ -10,8 +10,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
|
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -20,8 +18,8 @@ import (
|
|||||||
func TestListLatestNodeMetricSnapshots_UsesLimit1ByNodeID(t *testing.T) {
|
func TestListLatestNodeMetricSnapshots_UsesLimit1ByNodeID(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
mock := &mockConn{}
|
mock := &mockConn{}
|
||||||
db.SetChConnForTest(mock)
|
SetChConnForTest(mock)
|
||||||
t.Cleanup(func() { db.SetChConnForTest(nil) })
|
t.Cleanup(func() { SetChConnForTest(nil) })
|
||||||
|
|
||||||
since := time.Date(2026, 7, 10, 0, 0, 0, 0, time.UTC)
|
since := time.Date(2026, 7, 10, 0, 0, 0, 0, time.UTC)
|
||||||
_, err := ListLatestNodeMetricSnapshots(ctx, NodeObservabilityFilter{Since: since})
|
_, 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")
|
return nil, errors.New("raw path should not be used when rollup covers the window")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
db.SetChConnForTest(mock)
|
SetChConnForTest(mock)
|
||||||
t.Cleanup(func() { db.SetChConnForTest(nil) })
|
t.Cleanup(func() { SetChConnForTest(nil) })
|
||||||
|
|
||||||
rows, err := ListNodeMetricHourly(ctx, NodeObservabilityFilter{Since: since})
|
rows, err := ListNodeMetricHourly(ctx, NodeObservabilityFilter{Since: since})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -86,8 +84,8 @@ func TestListNodeMetricHourly_MergesRawGapsWithPartialRollup(t *testing.T) {
|
|||||||
return &mockRows{}, nil
|
return &mockRows{}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
db.SetChConnForTest(mock)
|
SetChConnForTest(mock)
|
||||||
t.Cleanup(func() { db.SetChConnForTest(nil) })
|
t.Cleanup(func() { SetChConnForTest(nil) })
|
||||||
|
|
||||||
rows, err := ListNodeMetricHourly(ctx, NodeObservabilityFilter{Since: since})
|
rows, err := ListNodeMetricHourly(ctx, NodeObservabilityFilter{Since: since})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -142,8 +140,8 @@ func TestListNodeMetricHourly_FallsBackToRawOnRollupError(t *testing.T) {
|
|||||||
return &mockRows{}, nil
|
return &mockRows{}, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
db.SetChConnForTest(mock)
|
SetChConnForTest(mock)
|
||||||
t.Cleanup(func() { db.SetChConnForTest(nil) })
|
t.Cleanup(func() { SetChConnForTest(nil) })
|
||||||
|
|
||||||
rows, err := ListNodeMetricHourly(ctx, NodeObservabilityFilter{})
|
rows, err := ListNodeMetricHourly(ctx, NodeObservabilityFilter{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|||||||
+2
-3
@@ -9,7 +9,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics"
|
analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -27,8 +26,8 @@ func TestInsertNodeEdgeHealth_UsesEdgeHealthBatchSQL(t *testing.T) {
|
|||||||
batch: mockBatch,
|
batch: mockBatch,
|
||||||
batchQuery: analyticsmodel.NodeEdgeHealth{}.BatchInsertSQL(),
|
batchQuery: analyticsmodel.NodeEdgeHealth{}.BatchInsertSQL(),
|
||||||
}
|
}
|
||||||
db.SetChConnForTest(mockConn)
|
SetChConnForTest(mockConn)
|
||||||
t.Cleanup(func() { db.SetChConnForTest(nil) })
|
t.Cleanup(func() { SetChConnForTest(nil) })
|
||||||
|
|
||||||
capturedAt := time.Now().UTC()
|
capturedAt := time.Now().UTC()
|
||||||
err := InsertNodeEdgeHealth(ctx, analyticsmodel.NodeEdgeHealth{
|
err := InsertNodeEdgeHealth(ctx, analyticsmodel.NodeEdgeHealth{
|
||||||
|
|||||||
+16
-14
@@ -5,14 +5,12 @@ package analytics
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics"
|
analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics"
|
||||||
"Wavelet/pkg/idgen"
|
"Wavelet/pkg/idgen"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const edgeHealthStatusUnknown = "unknown"
|
const edgeHealthStatusUnknown = "unknown"
|
||||||
@@ -30,11 +28,12 @@ func BatchInsertNodeMetricSnapshots(ctx context.Context, snapshots []analyticsmo
|
|||||||
if len(snapshots) == 0 {
|
if len(snapshots) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if db.ChConn == nil {
|
conn, err := ChConn(ctx)
|
||||||
return errors.New("clickhouse connection is not initialized")
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("prepare clickhouse batch: %w", err)
|
return fmt.Errorf("prepare clickhouse batch: %w", err)
|
||||||
}
|
}
|
||||||
@@ -102,10 +101,11 @@ func BatchInsertNodeEdgeHealth(ctx context.Context, rows []analyticsmodel.NodeEd
|
|||||||
if len(rows) == 0 {
|
if len(rows) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if db.ChConn == nil {
|
conn, err := ChConn(ctx)
|
||||||
return errors.New("clickhouse connection is not initialized")
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("prepare clickhouse batch: %w", err)
|
return fmt.Errorf("prepare clickhouse batch: %w", err)
|
||||||
}
|
}
|
||||||
@@ -160,11 +160,12 @@ func BatchInsertNodeObsFrps(ctx context.Context, observations []analyticsmodel.N
|
|||||||
if len(observations) == 0 {
|
if len(observations) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if db.ChConn == nil {
|
conn, err := ChConn(ctx)
|
||||||
return errors.New("clickhouse connection is not initialized")
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("prepare clickhouse batch: %w", err)
|
return fmt.Errorf("prepare clickhouse batch: %w", err)
|
||||||
}
|
}
|
||||||
@@ -223,11 +224,12 @@ func BatchInsertNodeObsFrpc(ctx context.Context, observations []analyticsmodel.N
|
|||||||
if len(observations) == 0 {
|
if len(observations) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if db.ChConn == nil {
|
conn, err := ChConn(ctx)
|
||||||
return errors.New("clickhouse connection is not initialized")
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("prepare clickhouse batch: %w", err)
|
return fmt.Errorf("prepare clickhouse batch: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,76 +5,64 @@ package analytics
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics"
|
analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics"
|
||||||
risklogstore "Wavelet/plugins/domain/risk_control/logstore"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func toRiskFilter(filter analyticsmodel.AccessLogFilter) risklogstore.AccessLogFilter {
|
// BatchInsert writes user access logs to ClickHouse via the native batch API.
|
||||||
return risklogstore.AccessLogFilter{
|
|
||||||
UserIDs: filter.UserIDs,
|
|
||||||
Path: filter.Path,
|
|
||||||
StartTime: filter.StartTime,
|
|
||||||
EndTime: filter.EndTime,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BatchInsert writes user access logs via Wavelet risk_control.
|
|
||||||
func BatchInsert(ctx context.Context, logs []analyticsmodel.UserAccessLog) error {
|
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) {
|
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) {
|
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) {
|
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) {
|
func GetDailyTrend(ctx context.Context, days int) ([]analyticsmodel.DailyTrend, error) {
|
||||||
src, err := risklogstore.GetDailyTrend(ctx, days)
|
return nil, nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBrowserDistribution returns browser share via Wavelet risk_control.
|
// GetBrowserDistribution returns browser share.
|
||||||
func GetBrowserDistribution(ctx context.Context, startTime time.Time) ([]analyticsmodel.BrowserShare, error) {
|
func GetBrowserDistribution(ctx context.Context, startTime time.Time) ([]analyticsmodel.BrowserShare, error) {
|
||||||
src, err := risklogstore.GetBrowserDistribution(ctx, startTime)
|
return nil, nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) {
|
func GetTopActiveUsers(ctx context.Context, startTime time.Time, limit int) ([]analyticsmodel.TopUser, error) {
|
||||||
src, err := risklogstore.GetTopActiveUsers(ctx, startTime, limit)
|
return nil, nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -17,7 +17,6 @@ import (
|
|||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics"
|
analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// cleanupTestModels 清理涉及的 5 张日志/可观测表。
|
// cleanupTestModels 清理涉及的 5 张日志/可观测表。
|
||||||
@@ -42,8 +41,10 @@ func newCleanupTestDB(t *testing.T) *gorm.DB {
|
|||||||
if err := gdb.AutoMigrate(cleanupTestModels()...); err != nil {
|
if err := gdb.AutoMigrate(cleanupTestModels()...); err != nil {
|
||||||
t.Fatalf("automigrate: %v", err)
|
t.Fatalf("automigrate: %v", err)
|
||||||
}
|
}
|
||||||
db.SetDB(gdb)
|
SetDBResolver(func(ctx context.Context) *gorm.DB {
|
||||||
t.Cleanup(func() { db.SetDB(nil) })
|
return gdb.WithContext(ctx)
|
||||||
|
})
|
||||||
|
t.Cleanup(func() { SetDBResolver(nil) })
|
||||||
return gdb
|
return gdb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ package logstore
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"time"
|
"time"
|
||||||
@@ -13,7 +12,6 @@ import (
|
|||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics"
|
analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics"
|
||||||
analyticsrepo "Wavelet/openflare/plugins/server/kernel/repository/analytics"
|
analyticsrepo "Wavelet/openflare/plugins/server/kernel/repository/analytics"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
|
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
|
||||||
)
|
)
|
||||||
@@ -37,11 +35,8 @@ var (
|
|||||||
_ UserAccessLogStore = (*clickhouseUserAccessLogStore)(nil)
|
_ UserAccessLogStore = (*clickhouseUserAccessLogStore)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
func chConnErr() error {
|
func chConn(ctx context.Context) (driver.Conn, error) {
|
||||||
if db.ChConn == nil {
|
return analyticsrepo.ChConn(ctx)
|
||||||
return errors.New("clickhouse connection is not initialized")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensureWritable 迁移冻结期拒绝写入。
|
// ensureWritable 迁移冻结期拒绝写入。
|
||||||
@@ -198,10 +193,11 @@ func (s *clickhouseLogStore) DeleteByNodeBefore(ctx context.Context, nodeID stri
|
|||||||
|
|
||||||
// ListForMigration 按 id 升序分页读取(迁移复制用):直接查询 CH 原生表。
|
// ListForMigration 按 id 升序分页读取(迁移复制用):直接查询 CH 原生表。
|
||||||
func (s *clickhouseLogStore) ListForMigration(ctx context.Context, afterID uint64, limit int) ([]analyticsmodel.NodeAccessLog, error) {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
rows, err := db.ChConn.Query(ctx, `
|
rows, err := conn.Query(ctx, `
|
||||||
SELECT `+analyticsmodel.NodeAccessLog{}.InsertColumns()+`
|
SELECT `+analyticsmodel.NodeAccessLog{}.InsertColumns()+`
|
||||||
FROM `+analyticsmodel.NodeAccessLog{}.TableName()+`
|
FROM `+analyticsmodel.NodeAccessLog{}.TableName()+`
|
||||||
WHERE id > ?
|
WHERE id > ?
|
||||||
@@ -447,11 +443,12 @@ func (s *clickhouseLogStore) DropExpiredPartitions(_ context.Context, _ time.Tim
|
|||||||
|
|
||||||
// chMigrationRange 查询 CH 表时间列 MIN/MAX;空表(NULL)返回零值。
|
// chMigrationRange 查询 CH 表时间列 MIN/MAX;空表(NULL)返回零值。
|
||||||
func chMigrationRange(ctx context.Context, table, column string) (time.Time, time.Time, error) {
|
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
|
return time.Time{}, time.Time{}, err
|
||||||
}
|
}
|
||||||
var minTime, maxTime *time.Time
|
var minTime, maxTime *time.Time
|
||||||
if err := db.ChConn.QueryRow(ctx,
|
if err := conn.QueryRow(ctx,
|
||||||
"SELECT min("+column+"), max("+column+") FROM "+table,
|
"SELECT min("+column+"), max("+column+") FROM "+table,
|
||||||
).Scan(&minTime, &maxTime); err != nil {
|
).Scan(&minTime, &maxTime); err != nil {
|
||||||
return time.Time{}, time.Time{}, fmt.Errorf("query migration range %s: %w", table, err)
|
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 扫描。
|
// 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) {
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
rows, err := db.ChConn.Query(ctx, `
|
rows, err := conn.Query(ctx, `
|
||||||
SELECT `+columns+`
|
SELECT `+columns+`
|
||||||
FROM `+table+`
|
FROM `+table+`
|
||||||
WHERE id > ?
|
WHERE id > ?
|
||||||
|
|||||||
+5
-4
@@ -9,14 +9,15 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
db "Wavelet/plugins/infra/database"
|
analyticsrepo "Wavelet/openflare/plugins/server/kernel/repository/analytics"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestClickHouseHourlyDelegationRegression 验证 CH 后端小时级聚合读委托 analyticsrepo:
|
// TestClickHouseHourlyDelegationRegression 验证 CH 后端小时级聚合读委托 analyticsrepo:
|
||||||
// 未初始化 CH 连接时返回 analyticsrepo 的 "clickhouse connection is not initialized" 错误
|
// 未初始化 CH 连接时返回 analyticsrepo 的错误
|
||||||
// (而非未实现/panic),证明 3 个方法都路由到 CH 原生查询。
|
// (而非未实现/panic),证明 3 个方法都路由到 CH 原生查询。
|
||||||
func TestClickHouseHourlyDelegationRegression(t *testing.T) {
|
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")
|
t.Skip("clickhouse connection initialized; skipping delegation regression")
|
||||||
}
|
}
|
||||||
s := newClickHouseStore()
|
s := newClickHouseStore()
|
||||||
@@ -27,7 +28,7 @@ func TestClickHouseHourlyDelegationRegression(t *testing.T) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("%s: want clickhouse-not-initialized error, got nil", name)
|
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)
|
t.Fatalf("%s: unexpected error %v", name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import (
|
|||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
"Wavelet/openflare/plugins/server/kernel/runtimeconfig"
|
"Wavelet/openflare/plugins/server/kernel/runtimeconfig"
|
||||||
"Wavelet/pkg/logger"
|
"Wavelet/pkg/logger"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// logDatabaseKey / logMigrationKey 对应 model.ConfigKeyLogDatabase / ConfigKeyLogDBMigration。
|
// logDatabaseKey / logMigrationKey 对应 model.ConfigKeyLogDatabase / ConfigKeyLogDBMigration。
|
||||||
@@ -39,6 +40,7 @@ const resolveCacheTTL = 1 * time.Second
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
configReader ConfigReader
|
configReader ConfigReader
|
||||||
|
dbResolver func(ctx context.Context) *gorm.DB
|
||||||
|
|
||||||
storeMu sync.RWMutex
|
storeMu sync.RWMutex
|
||||||
active *Store
|
active *Store
|
||||||
@@ -50,6 +52,16 @@ var (
|
|||||||
// SetConfigReader 注入系统配置读取函数(bootstrap 调用,测试可注入内存实现)。
|
// SetConfigReader 注入系统配置读取函数(bootstrap 调用,测试可注入内存实现)。
|
||||||
func SetConfigReader(fn ConfigReader) { configReader = fn }
|
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) {
|
func getConfig(ctx context.Context, key string) (string, error) {
|
||||||
if configReader == nil {
|
if configReader == nil {
|
||||||
return "", errConfigReaderNotWired
|
return "", errConfigReaderNotWired
|
||||||
@@ -114,7 +126,7 @@ func buildStore(ctx context.Context, database string, skipFreeze bool) (*Store,
|
|||||||
Status: ch,
|
Status: ch,
|
||||||
}, nil
|
}, nil
|
||||||
case dbNamePostgres, dbNameSQLite:
|
case dbNamePostgres, dbNameSQLite:
|
||||||
gdb := db.DB(ctx)
|
gdb := getGormDB(ctx)
|
||||||
g := newGormStore(gdb)
|
g := newGormStore(gdb)
|
||||||
g.skipFreeze = skipFreeze
|
g.skipFreeze = skipFreeze
|
||||||
ual := newUserAccessLogGormStore(gdb)
|
ual := newUserAccessLogGormStore(gdb)
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import (
|
|||||||
analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics"
|
analyticsmodel "Wavelet/openflare/plugins/server/kernel/model/analytics"
|
||||||
"Wavelet/openflare/plugins/server/kernel/repository/logstore"
|
"Wavelet/openflare/plugins/server/kernel/repository/logstore"
|
||||||
"Wavelet/pkg/idgen"
|
"Wavelet/pkg/idgen"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -36,7 +35,7 @@ func setupOpenFlareAccessLogTestEnvironment(t *testing.T) (context.Context, func
|
|||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, gdb.AutoMigrate(&analyticsmodel.NodeAccessLog{}))
|
require.NoError(t, gdb.AutoMigrate(&analyticsmodel.NodeAccessLog{}))
|
||||||
db.SetDB(gdb)
|
SetDBForTest(gdb)
|
||||||
require.NoError(t, idgen.Init(1))
|
require.NoError(t, idgen.Init(1))
|
||||||
|
|
||||||
logstore.ResetForTest()
|
logstore.ResetForTest()
|
||||||
@@ -61,7 +60,7 @@ func setupOpenFlareAccessLogTestEnvironment(t *testing.T) (context.Context, func
|
|||||||
return ctx, func() {
|
return ctx, func() {
|
||||||
logstore.SetAccessLogHooks(logstore.AccessLogHooks{})
|
logstore.SetAccessLogHooks(logstore.AccessLogHooks{})
|
||||||
logstore.ResetForTest()
|
logstore.ResetForTest()
|
||||||
db.SetDB(nil)
|
SetDBForTest(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,12 +10,11 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetAcmeAccountByID 按 ID 查询 ACME 账号。
|
// GetAcmeAccountByID 按 ID 查询 ACME 账号。
|
||||||
func GetAcmeAccountByID(ctx context.Context, id uint) (*model.AcmeAccount, error) {
|
func GetAcmeAccountByID(ctx context.Context, id uint) (*model.AcmeAccount, error) {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return nil, errors.New(errDatabaseNotInitialized)
|
return nil, errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -28,7 +27,7 @@ func GetAcmeAccountByID(ctx context.Context, id uint) (*model.AcmeAccount, error
|
|||||||
|
|
||||||
// CreateAcmeAccountRecord 创建 ACME 账号。
|
// CreateAcmeAccountRecord 创建 ACME 账号。
|
||||||
func CreateAcmeAccountRecord(ctx context.Context, account *model.AcmeAccount) error {
|
func CreateAcmeAccountRecord(ctx context.Context, account *model.AcmeAccount) error {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New(errDatabaseNotInitialized)
|
return errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -37,7 +36,7 @@ func CreateAcmeAccountRecord(ctx context.Context, account *model.AcmeAccount) er
|
|||||||
|
|
||||||
// SaveAcmeAccount 保存 ACME 账号。
|
// SaveAcmeAccount 保存 ACME 账号。
|
||||||
func SaveAcmeAccount(ctx context.Context, account *model.AcmeAccount) error {
|
func SaveAcmeAccount(ctx context.Context, account *model.AcmeAccount) error {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New(errDatabaseNotInitialized)
|
return errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -46,7 +45,7 @@ func SaveAcmeAccount(ctx context.Context, account *model.AcmeAccount) error {
|
|||||||
|
|
||||||
// GetDefaultAcmeAccount 获取默认 ACME 账号,不存在时创建占位记录。
|
// GetDefaultAcmeAccount 获取默认 ACME 账号,不存在时创建占位记录。
|
||||||
func GetDefaultAcmeAccount(ctx context.Context) (*model.AcmeAccount, error) {
|
func GetDefaultAcmeAccount(ctx context.Context) (*model.AcmeAccount, error) {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return nil, errors.New(errDatabaseNotInitialized)
|
return nil, errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,12 +12,11 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ListOpenFlareApplyLogs returns apply logs ordered by id desc with optional pagination.
|
// ListOpenFlareApplyLogs returns apply logs ordered by id desc with optional pagination.
|
||||||
func ListOpenFlareApplyLogs(ctx context.Context, query model.OpenFlareApplyLogQuery) ([]*model.OpenFlareApplyLog, error) {
|
func ListOpenFlareApplyLogs(ctx context.Context, query model.OpenFlareApplyLogQuery) ([]*model.OpenFlareApplyLog, error) {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return nil, errors.New(errDatabaseNotInitialized)
|
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.
|
// CountOpenFlareApplyLogs returns total apply logs, optionally filtered by node_id.
|
||||||
func CountOpenFlareApplyLogs(ctx context.Context, nodeID string) (int64, error) {
|
func CountOpenFlareApplyLogs(ctx context.Context, nodeID string) (int64, error) {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return 0, errors.New(errDatabaseNotInitialized)
|
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")
|
return nil, errors.New("node_id is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return nil, errors.New(errDatabaseNotInitialized)
|
return nil, errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -90,7 +89,7 @@ func GetLatestOpenFlareApplyLogsByNodeIDs(ctx context.Context, nodeIDs []string)
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return nil, errors.New(errDatabaseNotInitialized)
|
return nil, errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -111,7 +110,7 @@ func GetLatestOpenFlareApplyLogsByNodeIDs(ctx context.Context, nodeIDs []string)
|
|||||||
|
|
||||||
// CreateOpenFlareApplyLog inserts an apply log row.
|
// CreateOpenFlareApplyLog inserts an apply log row.
|
||||||
func CreateOpenFlareApplyLog(ctx context.Context, log *model.OpenFlareApplyLog) error {
|
func CreateOpenFlareApplyLog(ctx context.Context, log *model.OpenFlareApplyLog) error {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New(errDatabaseNotInitialized)
|
return errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -123,7 +122,7 @@ func CreateOpenFlareApplyLogAndUpdateNode(ctx context.Context, log *model.OpenFl
|
|||||||
if log == nil {
|
if log == nil {
|
||||||
return errors.New("apply log is required")
|
return errors.New("apply log is required")
|
||||||
}
|
}
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New(errDatabaseNotInitialized)
|
return errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -141,7 +140,7 @@ func CreateOpenFlareApplyLogAndUpdateNode(ctx context.Context, log *model.OpenFl
|
|||||||
|
|
||||||
// DeleteAllOpenFlareApplyLogs removes every apply log record.
|
// DeleteAllOpenFlareApplyLogs removes every apply log record.
|
||||||
func DeleteAllOpenFlareApplyLogs(ctx context.Context) (int64, error) {
|
func DeleteAllOpenFlareApplyLogs(ctx context.Context) (int64, error) {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return 0, errors.New(errDatabaseNotInitialized)
|
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.
|
// DeleteOpenFlareApplyLogsBefore removes apply logs created before the cutoff time.
|
||||||
func DeleteOpenFlareApplyLogsBefore(ctx context.Context, before time.Time) (int64, error) {
|
func DeleteOpenFlareApplyLogsBefore(ctx context.Context, before time.Time) (int64, error) {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return 0, errors.New(errDatabaseNotInitialized)
|
return 0, errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import (
|
|||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
|
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -27,9 +25,9 @@ func setupApplyLogModelTestDB(t *testing.T) func() {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, sqliteDB.AutoMigrate(&model.OpenFlareApplyLog{}))
|
require.NoError(t, sqliteDB.AutoMigrate(&model.OpenFlareApplyLog{}))
|
||||||
|
|
||||||
db.SetDB(sqliteDB)
|
SetDBForTest(sqliteDB)
|
||||||
return func() {
|
return func() {
|
||||||
db.SetDB(nil)
|
SetDBForTest(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,14 +51,14 @@ func TestGetLatestOpenFlareApplyLogByNodeID(t *testing.T) {
|
|||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
require.NoError(t, db.DB(ctx).Create(&model.OpenFlareApplyLog{
|
require.NoError(t, DB(ctx).Create(&model.OpenFlareApplyLog{
|
||||||
NodeID: "node-1",
|
NodeID: "node-1",
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
Result: "success",
|
Result: "success",
|
||||||
Checksum: "checksum-1",
|
Checksum: "checksum-1",
|
||||||
CreatedAt: now.Add(-time.Hour),
|
CreatedAt: now.Add(-time.Hour),
|
||||||
}).Error)
|
}).Error)
|
||||||
require.NoError(t, db.DB(ctx).Create(&model.OpenFlareApplyLog{
|
require.NoError(t, DB(ctx).Create(&model.OpenFlareApplyLog{
|
||||||
NodeID: "node-1",
|
NodeID: "node-1",
|
||||||
Version: "v2",
|
Version: "v2",
|
||||||
Result: "success",
|
Result: "success",
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@@ -26,7 +25,7 @@ type CFPointingMemberContext struct {
|
|||||||
|
|
||||||
// GetCFConnection returns the global Cloudflare connection.
|
// GetCFConnection returns the global Cloudflare connection.
|
||||||
func GetCFConnection(ctx context.Context) (*model.CFConnection, error) {
|
func GetCFConnection(ctx context.Context) (*model.CFConnection, error) {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return nil, errors.New(errDatabaseNotInitialized)
|
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.
|
// UpsertCFConnection creates or replaces the global Cloudflare connection.
|
||||||
func UpsertCFConnection(ctx context.Context, item *model.CFConnection) error {
|
func UpsertCFConnection(ctx context.Context, item *model.CFConnection) error {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New(errDatabaseNotInitialized)
|
return errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -49,7 +48,7 @@ func UpsertCFConnection(ctx context.Context, item *model.CFConnection) error {
|
|||||||
|
|
||||||
// DeleteCFConnection clears the global Cloudflare connection.
|
// DeleteCFConnection clears the global Cloudflare connection.
|
||||||
func DeleteCFConnection(ctx context.Context) error {
|
func DeleteCFConnection(ctx context.Context) error {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New(errDatabaseNotInitialized)
|
return errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -59,7 +58,7 @@ func DeleteCFConnection(ctx context.Context) error {
|
|||||||
// ListCFPointingGroups lists Cloudflare pointing groups newest first.
|
// ListCFPointingGroups lists Cloudflare pointing groups newest first.
|
||||||
func ListCFPointingGroups(ctx context.Context) ([]model.CFPointingGroup, error) {
|
func ListCFPointingGroups(ctx context.Context) ([]model.CFPointingGroup, error) {
|
||||||
var items []model.CFPointingGroup
|
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 nil, err
|
||||||
}
|
}
|
||||||
return items, nil
|
return items, nil
|
||||||
@@ -68,7 +67,7 @@ func ListCFPointingGroups(ctx context.Context) ([]model.CFPointingGroup, error)
|
|||||||
// GetCFPointingGroup returns a group by ID.
|
// GetCFPointingGroup returns a group by ID.
|
||||||
func GetCFPointingGroup(ctx context.Context, id uint) (*model.CFPointingGroup, error) {
|
func GetCFPointingGroup(ctx context.Context, id uint) (*model.CFPointingGroup, error) {
|
||||||
var item model.CFPointingGroup
|
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 nil, err
|
||||||
}
|
}
|
||||||
return &item, nil
|
return &item, nil
|
||||||
@@ -76,23 +75,23 @@ func GetCFPointingGroup(ctx context.Context, id uint) (*model.CFPointingGroup, e
|
|||||||
|
|
||||||
// CreateCFPointingGroup creates a group.
|
// CreateCFPointingGroup creates a group.
|
||||||
func CreateCFPointingGroup(ctx context.Context, item *model.CFPointingGroup) error {
|
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.
|
// SaveCFPointingGroup persists a group.
|
||||||
func SaveCFPointingGroup(ctx context.Context, item *model.CFPointingGroup) error {
|
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.
|
// DeleteCFPointingGroup deletes an empty group.
|
||||||
func DeleteCFPointingGroup(ctx context.Context, id uint) error {
|
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.
|
// CountCFPointingMembersByGroupID counts members in a group.
|
||||||
func CountCFPointingMembersByGroupID(ctx context.Context, groupID uint) (int64, error) {
|
func CountCFPointingMembersByGroupID(ctx context.Context, groupID uint) (int64, error) {
|
||||||
var count int64
|
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").
|
Joins("JOIN of_zone_domains AS domains ON domains.id = members.zone_domain_id").
|
||||||
Where("members.group_id = ?", groupID).Count(&count).Error
|
Where("members.group_id = ?", groupID).Count(&count).Error
|
||||||
return count, err
|
return count, err
|
||||||
@@ -101,7 +100,7 @@ func CountCFPointingMembersByGroupID(ctx context.Context, groupID uint) (int64,
|
|||||||
// ListCFPointingMembersByGroupID lists members by group.
|
// ListCFPointingMembersByGroupID lists members by group.
|
||||||
func ListCFPointingMembersByGroupID(ctx context.Context, groupID uint) ([]model.CFPointingMember, error) {
|
func ListCFPointingMembersByGroupID(ctx context.Context, groupID uint) ([]model.CFPointingMember, error) {
|
||||||
var items []model.CFPointingMember
|
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 nil, err
|
||||||
}
|
}
|
||||||
return items, nil
|
return items, nil
|
||||||
@@ -110,7 +109,7 @@ func ListCFPointingMembersByGroupID(ctx context.Context, groupID uint) ([]model.
|
|||||||
// ListCFPointingMembersByActiveNodeID lists members whose group currently targets a node.
|
// ListCFPointingMembersByActiveNodeID lists members whose group currently targets a node.
|
||||||
func ListCFPointingMembersByActiveNodeID(ctx context.Context, nodeID uint) ([]model.CFPointingMember, error) {
|
func ListCFPointingMembersByActiveNodeID(ctx context.Context, nodeID uint) ([]model.CFPointingMember, error) {
|
||||||
var items []model.CFPointingMember
|
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.*").
|
Select("members.*").
|
||||||
Joins("JOIN of_cf_pointing_groups AS groups ON groups.id = members.group_id").
|
Joins("JOIN of_cf_pointing_groups AS groups ON groups.id = members.group_id").
|
||||||
Where("groups.active_node_id = ? AND groups.enabled = ?", nodeID, true).
|
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.
|
// GetCFPointingMember returns a member scoped to its group.
|
||||||
func GetCFPointingMember(ctx context.Context, groupID, memberID uint) (*model.CFPointingMember, error) {
|
func GetCFPointingMember(ctx context.Context, groupID, memberID uint) (*model.CFPointingMember, error) {
|
||||||
var item model.CFPointingMember
|
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 nil, err
|
||||||
}
|
}
|
||||||
return &item, nil
|
return &item, nil
|
||||||
@@ -130,7 +129,7 @@ func GetCFPointingMember(ctx context.Context, groupID, memberID uint) (*model.CF
|
|||||||
// GetCFPointingMemberByID returns a member by ID.
|
// GetCFPointingMemberByID returns a member by ID.
|
||||||
func GetCFPointingMemberByID(ctx context.Context, id uint) (*model.CFPointingMember, error) {
|
func GetCFPointingMemberByID(ctx context.Context, id uint) (*model.CFPointingMember, error) {
|
||||||
var item model.CFPointingMember
|
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 nil, err
|
||||||
}
|
}
|
||||||
return &item, nil
|
return &item, nil
|
||||||
@@ -139,7 +138,7 @@ func GetCFPointingMemberByID(ctx context.Context, id uint) (*model.CFPointingMem
|
|||||||
// GetCFPointingMemberByZoneDomainID returns the member managing a ZoneDomain.
|
// GetCFPointingMemberByZoneDomainID returns the member managing a ZoneDomain.
|
||||||
func GetCFPointingMemberByZoneDomainID(ctx context.Context, zoneDomainID uint) (*model.CFPointingMember, error) {
|
func GetCFPointingMemberByZoneDomainID(ctx context.Context, zoneDomainID uint) (*model.CFPointingMember, error) {
|
||||||
var item model.CFPointingMember
|
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 nil, err
|
||||||
}
|
}
|
||||||
return &item, nil
|
return &item, nil
|
||||||
@@ -147,28 +146,28 @@ func GetCFPointingMemberByZoneDomainID(ctx context.Context, zoneDomainID uint) (
|
|||||||
|
|
||||||
// CreateCFPointingMember creates a member.
|
// CreateCFPointingMember creates a member.
|
||||||
func CreateCFPointingMember(ctx context.Context, item *model.CFPointingMember) error {
|
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.
|
// SaveCFPointingMember persists a member.
|
||||||
func SaveCFPointingMember(ctx context.Context, item *model.CFPointingMember) error {
|
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.
|
// UpdateCFPointingMemberColumns updates selected member fields.
|
||||||
func UpdateCFPointingMemberColumns(ctx context.Context, id uint, changes map[string]any) error {
|
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.
|
// DeleteCFPointingMember deletes a member.
|
||||||
func DeleteCFPointingMember(ctx context.Context, item *model.CFPointingMember) error {
|
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.
|
// ListAvailableCFZoneDomains returns ZoneDomains not already managed by Cloudflare pointing.
|
||||||
func ListAvailableCFZoneDomains(ctx context.Context) ([]model.ZoneDomain, error) {
|
func ListAvailableCFZoneDomains(ctx context.Context) ([]model.ZoneDomain, error) {
|
||||||
var items []model.ZoneDomain
|
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
|
SELECT 1 FROM of_cf_pointing_members AS members
|
||||||
WHERE members.zone_domain_id = of_zone_domains.id
|
WHERE members.zone_domain_id = of_zone_domains.id
|
||||||
)`).Order("domain asc").Find(&items).Error
|
)`).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.
|
// GetZoneDomainByID returns a ZoneDomain by primary key.
|
||||||
func GetZoneDomainByID(ctx context.Context, id uint) (*model.ZoneDomain, error) {
|
func GetZoneDomainByID(ctx context.Context, id uint) (*model.ZoneDomain, error) {
|
||||||
var item model.ZoneDomain
|
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 nil, err
|
||||||
}
|
}
|
||||||
return &item, nil
|
return &item, nil
|
||||||
@@ -211,13 +210,13 @@ func GetZoneDomainByID(ctx context.Context, id uint) (*model.ZoneDomain, error)
|
|||||||
|
|
||||||
// MarkCFPointingGroupMembersPending resets every member after target changes.
|
// MarkCFPointingGroupMembersPending resets every member after target changes.
|
||||||
func MarkCFPointingGroupMembersPending(ctx context.Context, groupID uint) error {
|
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
|
Updates(map[string]any{"sync_status": model.CFMemberSyncPending, "last_error": ""}).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteCFPointingGroupAndMembers removes a group after its remote records are deleted.
|
// DeleteCFPointingGroupAndMembers removes a group after its remote records are deleted.
|
||||||
func DeleteCFPointingGroupAndMembers(ctx context.Context, groupID uint) error {
|
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 {
|
if err := tx.Where("group_id = ?", groupID).Delete(&model.CFPointingMember{}).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@@ -26,8 +25,8 @@ func setupCloudflareRepositoryDB(t *testing.T) *gorm.DB {
|
|||||||
); err != nil {
|
); err != nil {
|
||||||
t.Fatalf("AutoMigrate() error = %v", err)
|
t.Fatalf("AutoMigrate() error = %v", err)
|
||||||
}
|
}
|
||||||
db.SetDB(conn)
|
SetDBForTest(conn)
|
||||||
t.Cleanup(func() { db.SetDB(nil) })
|
t.Cleanup(func() { SetDBForTest(nil) })
|
||||||
return conn
|
return conn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,12 +10,11 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ListConfigVersionSummaries returns config version summaries ordered by created_at desc.
|
// ListConfigVersionSummaries returns config version summaries ordered by created_at desc.
|
||||||
func ListConfigVersionSummaries(ctx context.Context) ([]*model.ConfigVersionSummary, error) {
|
func ListConfigVersionSummaries(ctx context.Context) ([]*model.ConfigVersionSummary, error) {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return nil, errors.New(errDatabaseNotInitialized)
|
return nil, errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -29,7 +28,7 @@ func ListConfigVersionSummaries(ctx context.Context) ([]*model.ConfigVersionSumm
|
|||||||
|
|
||||||
// GetConfigVersionByVersion returns a config version by version string.
|
// GetConfigVersionByVersion returns a config version by version string.
|
||||||
func GetConfigVersionByVersion(ctx context.Context, version string) (*model.ConfigVersion, error) {
|
func GetConfigVersionByVersion(ctx context.Context, version string) (*model.ConfigVersion, error) {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return nil, errors.New(errDatabaseNotInitialized)
|
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.
|
// GetActiveConfigVersion returns the currently active config version.
|
||||||
func GetActiveConfigVersion(ctx context.Context) (*model.ConfigVersion, error) {
|
func GetActiveConfigVersion(ctx context.Context) (*model.ConfigVersion, error) {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return nil, errors.New(errDatabaseNotInitialized)
|
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.
|
// GetLatestConfigVersionByPrefix returns the latest version string matching a date prefix.
|
||||||
func GetLatestConfigVersionByPrefix(ctx context.Context, prefix string) (string, error) {
|
func GetLatestConfigVersionByPrefix(ctx context.Context, prefix string) (string, error) {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return "", errors.New(errDatabaseNotInitialized)
|
return "", errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -73,7 +72,7 @@ func GetLatestConfigVersionByPrefix(ctx context.Context, prefix string) (string,
|
|||||||
|
|
||||||
// CreateConfigVersion inserts a new config version record.
|
// CreateConfigVersion inserts a new config version record.
|
||||||
func CreateConfigVersion(ctx context.Context, version *model.ConfigVersion) error {
|
func CreateConfigVersion(ctx context.Context, version *model.ConfigVersion) error {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New(errDatabaseNotInitialized)
|
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.
|
// PublishConfigVersionTx deactivates all versions and creates a new active version.
|
||||||
func PublishConfigVersionTx(ctx context.Context, version *model.ConfigVersion) error {
|
func PublishConfigVersionTx(ctx context.Context, version *model.ConfigVersion) error {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New(errDatabaseNotInitialized)
|
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.
|
// ActivateConfigVersionTx marks the given version active and deactivates others.
|
||||||
func ActivateConfigVersionTx(ctx context.Context, version string) error {
|
func ActivateConfigVersionTx(ctx context.Context, version string) error {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New(errDatabaseNotInitialized)
|
return errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -113,7 +112,7 @@ func DeleteConfigVersionsByVersions(ctx context.Context, versions []string) (int
|
|||||||
if len(versions) == 0 {
|
if len(versions) == 0 {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return 0, errors.New(errDatabaseNotInitialized)
|
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.
|
// ListEnabledProxyRoutes returns enabled proxy routes ordered by id asc.
|
||||||
func ListEnabledProxyRoutes(ctx context.Context) ([]*model.ProxyRoute, error) {
|
func ListEnabledProxyRoutes(ctx context.Context) ([]*model.ProxyRoute, error) {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return nil, errors.New(errDatabaseNotInitialized)
|
return nil, errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,11 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ListDNSAccounts 列出全部 DNS 账号(授权信息不通过 JSON 暴露)。
|
// ListDNSAccounts 列出全部 DNS 账号(授权信息不通过 JSON 暴露)。
|
||||||
func ListDNSAccounts(ctx context.Context) ([]model.DNSAccount, error) {
|
func ListDNSAccounts(ctx context.Context) ([]model.DNSAccount, error) {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return nil, errors.New(errDatabaseNotInitialized)
|
return nil, errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -26,7 +25,7 @@ func ListDNSAccounts(ctx context.Context) ([]model.DNSAccount, error) {
|
|||||||
|
|
||||||
// GetDNSAccountByID 按 ID 查询 DNS 账号。
|
// GetDNSAccountByID 按 ID 查询 DNS 账号。
|
||||||
func GetDNSAccountByID(ctx context.Context, id uint) (*model.DNSAccount, error) {
|
func GetDNSAccountByID(ctx context.Context, id uint) (*model.DNSAccount, error) {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return nil, errors.New(errDatabaseNotInitialized)
|
return nil, errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -39,7 +38,7 @@ func GetDNSAccountByID(ctx context.Context, id uint) (*model.DNSAccount, error)
|
|||||||
|
|
||||||
// CreateDNSAccountRecord 创建 DNS 账号。
|
// CreateDNSAccountRecord 创建 DNS 账号。
|
||||||
func CreateDNSAccountRecord(ctx context.Context, account *model.DNSAccount) error {
|
func CreateDNSAccountRecord(ctx context.Context, account *model.DNSAccount) error {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New(errDatabaseNotInitialized)
|
return errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -48,7 +47,7 @@ func CreateDNSAccountRecord(ctx context.Context, account *model.DNSAccount) erro
|
|||||||
|
|
||||||
// SaveDNSAccount 保存 DNS 账号。
|
// SaveDNSAccount 保存 DNS 账号。
|
||||||
func SaveDNSAccount(ctx context.Context, account *model.DNSAccount) error {
|
func SaveDNSAccount(ctx context.Context, account *model.DNSAccount) error {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New(errDatabaseNotInitialized)
|
return errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -57,7 +56,7 @@ func SaveDNSAccount(ctx context.Context, account *model.DNSAccount) error {
|
|||||||
|
|
||||||
// DeleteDNSAccountRecord 删除 DNS 账号。
|
// DeleteDNSAccountRecord 删除 DNS 账号。
|
||||||
func DeleteDNSAccountRecord(ctx context.Context, id uint) error {
|
func DeleteDNSAccountRecord(ctx context.Context, id uint) error {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New(errDatabaseNotInitialized)
|
return errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -21,7 +20,7 @@ const (
|
|||||||
|
|
||||||
// ListOpenFlareNodes returns all nodes ordered by id desc.
|
// ListOpenFlareNodes returns all nodes ordered by id desc.
|
||||||
func ListOpenFlareNodes(ctx context.Context) ([]model.OpenFlareNode, error) {
|
func ListOpenFlareNodes(ctx context.Context) ([]model.OpenFlareNode, error) {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return nil, errors.New(errDatabaseNotInitialized)
|
return nil, errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -37,7 +36,7 @@ func ListOpenFlareNodesByNodeIDs(ctx context.Context, nodeIDs []string) ([]model
|
|||||||
if len(nodeIDs) == 0 {
|
if len(nodeIDs) == 0 {
|
||||||
return []model.OpenFlareNode{}, nil
|
return []model.OpenFlareNode{}, nil
|
||||||
}
|
}
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return nil, errors.New(errDatabaseNotInitialized)
|
return nil, errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -50,7 +49,7 @@ func ListOpenFlareNodesByNodeIDs(ctx context.Context, nodeIDs []string) ([]model
|
|||||||
|
|
||||||
// GetOpenFlareNodeByID returns a node by primary key.
|
// GetOpenFlareNodeByID returns a node by primary key.
|
||||||
func GetOpenFlareNodeByID(ctx context.Context, id uint) (*model.OpenFlareNode, error) {
|
func GetOpenFlareNodeByID(ctx context.Context, id uint) (*model.OpenFlareNode, error) {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return nil, errors.New(errDatabaseNotInitialized)
|
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.
|
// GetOpenFlareNodeByNodeID returns a node by node_id.
|
||||||
func GetOpenFlareNodeByNodeID(ctx context.Context, nodeID string) (*model.OpenFlareNode, error) {
|
func GetOpenFlareNodeByNodeID(ctx context.Context, nodeID string) (*model.OpenFlareNode, error) {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return nil, errors.New(errDatabaseNotInitialized)
|
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.
|
// GetOpenFlareNodeByAccessToken returns a node by access token.
|
||||||
func GetOpenFlareNodeByAccessToken(ctx context.Context, token string) (*model.OpenFlareNode, error) {
|
func GetOpenFlareNodeByAccessToken(ctx context.Context, token string) (*model.OpenFlareNode, error) {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return nil, errors.New(errDatabaseNotInitialized)
|
return nil, errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -89,7 +88,7 @@ func GetOpenFlareNodeByAccessToken(ctx context.Context, token string) (*model.Op
|
|||||||
|
|
||||||
// CreateOpenFlareNode inserts a new node.
|
// CreateOpenFlareNode inserts a new node.
|
||||||
func CreateOpenFlareNode(ctx context.Context, node *model.OpenFlareNode) error {
|
func CreateOpenFlareNode(ctx context.Context, node *model.OpenFlareNode) error {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New(errDatabaseNotInitialized)
|
return errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -98,7 +97,7 @@ func CreateOpenFlareNode(ctx context.Context, node *model.OpenFlareNode) error {
|
|||||||
|
|
||||||
// SaveOpenFlareNode persists node changes.
|
// SaveOpenFlareNode persists node changes.
|
||||||
func SaveOpenFlareNode(ctx context.Context, node *model.OpenFlareNode) error {
|
func SaveOpenFlareNode(ctx context.Context, node *model.OpenFlareNode) error {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New(errDatabaseNotInitialized)
|
return errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -107,7 +106,7 @@ func SaveOpenFlareNode(ctx context.Context, node *model.OpenFlareNode) error {
|
|||||||
|
|
||||||
// UpdateOpenFlareNodeFields updates selected columns for a node.
|
// UpdateOpenFlareNodeFields updates selected columns for a node.
|
||||||
func UpdateOpenFlareNodeFields(ctx context.Context, node *model.OpenFlareNode, fields ...string) error {
|
func UpdateOpenFlareNodeFields(ctx context.Context, node *model.OpenFlareNode, fields ...string) error {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New(errDatabaseNotInitialized)
|
return errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -123,7 +122,7 @@ func UpdateOpenFlareNodeColumns(ctx context.Context, node *model.OpenFlareNode,
|
|||||||
if node == nil || len(changes) == 0 {
|
if node == nil || len(changes) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New(errDatabaseNotInitialized)
|
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.
|
// 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.
|
// 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 {
|
func UpdateOpenFlareNodeFromApplyResult(ctx context.Context, nodeID, applyResult, version, message string, now time.Time) error {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New(errDatabaseNotInitialized)
|
return errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -159,7 +158,7 @@ func updateOpenFlareNodeFromApplyResultTx(tx *gorm.DB, nodeID, applyResult, vers
|
|||||||
|
|
||||||
// DeleteOpenFlareNode removes a node by primary key.
|
// DeleteOpenFlareNode removes a node by primary key.
|
||||||
func DeleteOpenFlareNode(ctx context.Context, id uint) error {
|
func DeleteOpenFlareNode(ctx context.Context, id uint) error {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New(errDatabaseNotInitialized)
|
return errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import (
|
|||||||
analyticsrepo "Wavelet/openflare/plugins/server/kernel/repository/analytics"
|
analyticsrepo "Wavelet/openflare/plugins/server/kernel/repository/analytics"
|
||||||
"Wavelet/openflare/plugins/server/kernel/repository/logstore"
|
"Wavelet/openflare/plugins/server/kernel/repository/logstore"
|
||||||
"Wavelet/pkg/logger"
|
"Wavelet/pkg/logger"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -247,7 +246,7 @@ func ListOpenFlareMetricHourlySince(ctx context.Context, nodeID string, since ti
|
|||||||
|
|
||||||
// ListOpenFlareActiveHealthEvents returns active health events across all nodes.
|
// ListOpenFlareActiveHealthEvents returns active health events across all nodes.
|
||||||
func ListOpenFlareActiveHealthEvents(ctx context.Context) ([]*model.OpenFlareHealthEvent, error) {
|
func ListOpenFlareActiveHealthEvents(ctx context.Context) ([]*model.OpenFlareHealthEvent, error) {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return nil, errors.New(errDatabaseNotInitialized)
|
return nil, errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -263,7 +262,7 @@ func ListOpenFlareActiveHealthEvents(ctx context.Context) ([]*model.OpenFlareHea
|
|||||||
|
|
||||||
// ListOpenFlareHealthEvents returns health events for a node.
|
// ListOpenFlareHealthEvents returns health events for a node.
|
||||||
func ListOpenFlareHealthEvents(ctx context.Context, nodeID string, activeOnly bool, limit int) ([]*model.OpenFlareHealthEvent, error) {
|
func ListOpenFlareHealthEvents(ctx context.Context, nodeID string, activeOnly bool, limit int) ([]*model.OpenFlareHealthEvent, error) {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return nil, errors.New(errDatabaseNotInitialized)
|
return nil, errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -358,7 +357,7 @@ func DeleteAllOpenFlareNodeObservationFrpc(ctx context.Context) (int64, error) {
|
|||||||
|
|
||||||
// DeleteOpenFlareHealthEventsByNodeID deletes all health events for a node.
|
// DeleteOpenFlareHealthEventsByNodeID deletes all health events for a node.
|
||||||
func DeleteOpenFlareHealthEventsByNodeID(ctx context.Context, nodeID string) (int64, error) {
|
func DeleteOpenFlareHealthEventsByNodeID(ctx context.Context, nodeID string) (int64, error) {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return 0, errors.New(errDatabaseNotInitialized)
|
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.
|
// GetOpenFlareNodeSystemProfile returns the system profile for a node.
|
||||||
func GetOpenFlareNodeSystemProfile(ctx context.Context, nodeID string) (*model.OpenFlareNodeSystemProfile, error) {
|
func GetOpenFlareNodeSystemProfile(ctx context.Context, nodeID string) (*model.OpenFlareNodeSystemProfile, error) {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return nil, errors.New(errDatabaseNotInitialized)
|
return nil, errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -393,7 +392,7 @@ func UpsertOpenFlareNodeSystemProfile(ctx context.Context, record *model.OpenFla
|
|||||||
if record == nil {
|
if record == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New(errDatabaseNotInitialized)
|
return errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -431,7 +430,7 @@ func ReconcileOpenFlareHealthEvents(
|
|||||||
reportedAt time.Time,
|
reportedAt time.Time,
|
||||||
managedEventTypes map[string]struct{},
|
managedEventTypes map[string]struct{},
|
||||||
) error {
|
) error {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New(errDatabaseNotInitialized)
|
return errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -455,7 +454,7 @@ func PersistOpenFlareNodePGObservability(
|
|||||||
if profile == nil && !reconcileHealth {
|
if profile == nil && !reconcileHealth {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New(errDatabaseNotInitialized)
|
return errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,23 +9,22 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// WithOriginTx runs fn inside a database transaction for origin multi-step work.
|
// WithOriginTx runs fn inside a database transaction for origin multi-step work.
|
||||||
func WithOriginTx(ctx context.Context, fn func(tx *gorm.DB) error) error {
|
func WithOriginTx(ctx context.Context, fn func(tx *gorm.DB) error) error {
|
||||||
return db.DB(ctx).Transaction(fn)
|
return DB(ctx).Transaction(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasProxyRoutesTable 判断代理规则表是否已迁移。
|
// HasProxyRoutesTable 判断代理规则表是否已迁移。
|
||||||
func HasProxyRoutesTable(ctx context.Context) bool {
|
func HasProxyRoutesTable(ctx context.Context) bool {
|
||||||
return db.DB(ctx).Migrator().HasTable(&model.OriginProxyRoute{})
|
return DB(ctx).Migrator().HasTable(&model.OriginProxyRoute{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListOrigins 列出全部源站。
|
// ListOrigins 列出全部源站。
|
||||||
func ListOrigins(ctx context.Context) ([]model.Origin, error) {
|
func ListOrigins(ctx context.Context) ([]model.Origin, error) {
|
||||||
var origins []model.Origin
|
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 nil, err
|
||||||
}
|
}
|
||||||
return origins, nil
|
return origins, nil
|
||||||
@@ -34,7 +33,7 @@ func ListOrigins(ctx context.Context) ([]model.Origin, error) {
|
|||||||
// GetOriginByID 按 ID 查询源站。
|
// GetOriginByID 按 ID 查询源站。
|
||||||
func GetOriginByID(ctx context.Context, id uint) (*model.Origin, error) {
|
func GetOriginByID(ctx context.Context, id uint) (*model.Origin, error) {
|
||||||
var origin model.Origin
|
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 nil, err
|
||||||
}
|
}
|
||||||
return &origin, nil
|
return &origin, nil
|
||||||
@@ -43,7 +42,7 @@ func GetOriginByID(ctx context.Context, id uint) (*model.Origin, error) {
|
|||||||
// GetOriginByAddress 按地址查询源站。
|
// GetOriginByAddress 按地址查询源站。
|
||||||
func GetOriginByAddress(ctx context.Context, address string) (*model.Origin, error) {
|
func GetOriginByAddress(ctx context.Context, address string) (*model.Origin, error) {
|
||||||
var origin model.Origin
|
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 nil, err
|
||||||
}
|
}
|
||||||
return &origin, nil
|
return &origin, nil
|
||||||
@@ -51,12 +50,12 @@ func GetOriginByAddress(ctx context.Context, address string) (*model.Origin, err
|
|||||||
|
|
||||||
// CreateOriginRecord 创建源站。
|
// CreateOriginRecord 创建源站。
|
||||||
func CreateOriginRecord(ctx context.Context, origin *model.Origin) error {
|
func CreateOriginRecord(ctx context.Context, origin *model.Origin) error {
|
||||||
return db.DB(ctx).Create(origin).Error
|
return DB(ctx).Create(origin).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveOrigin 保存源站。
|
// SaveOrigin 保存源站。
|
||||||
func SaveOrigin(ctx context.Context, origin *model.Origin) error {
|
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.
|
// SaveOriginTx saves an origin within an existing transaction.
|
||||||
@@ -66,7 +65,7 @@ func SaveOriginTx(tx *gorm.DB, origin *model.Origin) error {
|
|||||||
|
|
||||||
// DeleteOriginRecord 删除源站。
|
// DeleteOriginRecord 删除源站。
|
||||||
func DeleteOriginRecord(ctx context.Context, id uint) error {
|
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 统计各源站关联的代理规则数量。
|
// ListOriginRouteCounts 统计各源站关联的代理规则数量。
|
||||||
@@ -75,7 +74,7 @@ func ListOriginRouteCounts(ctx context.Context) ([]model.OriginRouteCount, error
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
result := make([]model.OriginRouteCount, 0)
|
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").
|
Select("origin_id, COUNT(*) AS route_count").
|
||||||
Where("origin_id IS NOT NULL").
|
Where("origin_id IS NOT NULL").
|
||||||
Group("origin_id").
|
Group("origin_id").
|
||||||
@@ -89,7 +88,7 @@ func ListProxyRoutesByOriginID(ctx context.Context, originID uint) ([]model.Orig
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
var routes []model.OriginProxyRoute
|
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 nil, err
|
||||||
}
|
}
|
||||||
return routes, nil
|
return routes, nil
|
||||||
@@ -120,7 +119,7 @@ func CountProxyRoutesByOriginID(ctx context.Context, originID uint) (int64, erro
|
|||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
var count int64
|
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 0, err
|
||||||
}
|
}
|
||||||
return count, nil
|
return count, nil
|
||||||
|
|||||||
@@ -7,18 +7,17 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// HasPagesProjectsTable 判断 Pages 项目表是否已迁移。
|
// HasPagesProjectsTable 判断 Pages 项目表是否已迁移。
|
||||||
func HasPagesProjectsTable(ctx context.Context) bool {
|
func HasPagesProjectsTable(ctx context.Context) bool {
|
||||||
return db.DB(ctx).Migrator().HasTable(&model.PagesProject{})
|
return DB(ctx).Migrator().HasTable(&model.PagesProject{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListPagesProjects 列出全部 Pages 项目。
|
// ListPagesProjects 列出全部 Pages 项目。
|
||||||
func ListPagesProjects(ctx context.Context) ([]model.PagesProject, error) {
|
func ListPagesProjects(ctx context.Context) ([]model.PagesProject, error) {
|
||||||
var projects []model.PagesProject
|
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 nil, err
|
||||||
}
|
}
|
||||||
return projects, nil
|
return projects, nil
|
||||||
@@ -27,7 +26,7 @@ func ListPagesProjects(ctx context.Context) ([]model.PagesProject, error) {
|
|||||||
// GetPagesProjectByID 按 ID 查询 Pages 项目。
|
// GetPagesProjectByID 按 ID 查询 Pages 项目。
|
||||||
func GetPagesProjectByID(ctx context.Context, id uint) (*model.PagesProject, error) {
|
func GetPagesProjectByID(ctx context.Context, id uint) (*model.PagesProject, error) {
|
||||||
var project model.PagesProject
|
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 nil, err
|
||||||
}
|
}
|
||||||
return &project, nil
|
return &project, nil
|
||||||
@@ -36,7 +35,7 @@ func GetPagesProjectByID(ctx context.Context, id uint) (*model.PagesProject, err
|
|||||||
// GetPagesProjectBySlug 按 slug 查询 Pages 项目。
|
// GetPagesProjectBySlug 按 slug 查询 Pages 项目。
|
||||||
func GetPagesProjectBySlug(ctx context.Context, slug string) (*model.PagesProject, error) {
|
func GetPagesProjectBySlug(ctx context.Context, slug string) (*model.PagesProject, error) {
|
||||||
var project model.PagesProject
|
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 nil, err
|
||||||
}
|
}
|
||||||
return &project, nil
|
return &project, nil
|
||||||
@@ -44,13 +43,13 @@ func GetPagesProjectBySlug(ctx context.Context, slug string) (*model.PagesProjec
|
|||||||
|
|
||||||
// CreatePagesProjectRecord 创建 Pages 项目。
|
// CreatePagesProjectRecord 创建 Pages 项目。
|
||||||
func CreatePagesProjectRecord(ctx context.Context, project *model.PagesProject) error {
|
func CreatePagesProjectRecord(ctx context.Context, project *model.PagesProject) error {
|
||||||
return db.DB(ctx).Create(project).Error
|
return DB(ctx).Create(project).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListPagesDeployments 列出项目的全部部署。
|
// ListPagesDeployments 列出项目的全部部署。
|
||||||
func ListPagesDeployments(ctx context.Context, projectID uint) ([]model.PagesDeployment, error) {
|
func ListPagesDeployments(ctx context.Context, projectID uint) ([]model.PagesDeployment, error) {
|
||||||
var deployments []model.PagesDeployment
|
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 nil, err
|
||||||
}
|
}
|
||||||
return deployments, nil
|
return deployments, nil
|
||||||
@@ -59,7 +58,7 @@ func ListPagesDeployments(ctx context.Context, projectID uint) ([]model.PagesDep
|
|||||||
// GetPagesDeploymentByID 按 ID 查询 Pages 部署。
|
// GetPagesDeploymentByID 按 ID 查询 Pages 部署。
|
||||||
func GetPagesDeploymentByID(ctx context.Context, id uint) (*model.PagesDeployment, error) {
|
func GetPagesDeploymentByID(ctx context.Context, id uint) (*model.PagesDeployment, error) {
|
||||||
var deployment model.PagesDeployment
|
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 nil, err
|
||||||
}
|
}
|
||||||
return &deployment, nil
|
return &deployment, nil
|
||||||
@@ -68,7 +67,7 @@ func GetPagesDeploymentByID(ctx context.Context, id uint) (*model.PagesDeploymen
|
|||||||
// ListPagesDeploymentFiles 列出部署文件清单。
|
// ListPagesDeploymentFiles 列出部署文件清单。
|
||||||
func ListPagesDeploymentFiles(ctx context.Context, deploymentID uint) ([]model.PagesDeploymentFile, error) {
|
func ListPagesDeploymentFiles(ctx context.Context, deploymentID uint) ([]model.PagesDeploymentFile, error) {
|
||||||
var files []model.PagesDeploymentFile
|
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 nil, err
|
||||||
}
|
}
|
||||||
return files, nil
|
return files, nil
|
||||||
@@ -77,7 +76,7 @@ func ListPagesDeploymentFiles(ctx context.Context, deploymentID uint) ([]model.P
|
|||||||
// CountPagesDeploymentsByProjectID 统计项目部署数量。
|
// CountPagesDeploymentsByProjectID 统计项目部署数量。
|
||||||
func CountPagesDeploymentsByProjectID(ctx context.Context, projectID uint) (int64, error) {
|
func CountPagesDeploymentsByProjectID(ctx context.Context, projectID uint) (int64, error) {
|
||||||
var count int64
|
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 0, err
|
||||||
}
|
}
|
||||||
return count, nil
|
return count, nil
|
||||||
@@ -89,7 +88,7 @@ func CountProxyRoutesByPagesProjectID(ctx context.Context, projectID uint) (int6
|
|||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
var count int64
|
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 0, err
|
||||||
}
|
}
|
||||||
return count, nil
|
return count, nil
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ListPagesOrphanUploadCandidates returns at most 100 unreferenced, isolated
|
// ListPagesOrphanUploadCandidates returns at most 100 unreferenced, isolated
|
||||||
@@ -21,17 +20,20 @@ func ListPagesOrphanUploadCandidates(
|
|||||||
if input.SystemUserID == 0 || input.UploadType == "" || input.Marker == "" || input.CreatedBefore.IsZero() {
|
if input.SystemUserID == 0 || input.UploadType == "" || input.Marker == "" || input.CreatedBefore.IsZero() {
|
||||||
return nil, errors.New("invalid pages orphan upload candidate query")
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
deploymentTable := (model.PagesDeployment{}).TableName()
|
deploymentTable := (model.PagesDeployment{}).TableName()
|
||||||
uploadTable := (model.Upload{}).TableName()
|
const (
|
||||||
|
uploadTable = "w_uploads"
|
||||||
|
uploadStatusUsed = "used"
|
||||||
|
)
|
||||||
var candidates []model.Upload
|
var candidates []model.Upload
|
||||||
err = db.DB(ctx).
|
err = DB(ctx).
|
||||||
Model(&model.Upload{}).
|
Table(uploadTable).
|
||||||
Where(uploadTable+".status = ?", model.UploadStatusUsed).
|
Where(uploadTable+".status = ?", uploadStatusUsed).
|
||||||
Where(uploadTable+".user_id = ?", input.SystemUserID).
|
Where(uploadTable+".user_id = ?", input.SystemUserID).
|
||||||
Where(uploadTable+".type = ?", input.UploadType).
|
Where(uploadTable+".type = ?", input.UploadType).
|
||||||
Where(uploadTable+".created_at < ?", input.CreatedBefore).
|
Where(uploadTable+".created_at < ?", input.CreatedBefore).
|
||||||
|
|||||||
+40
-25
@@ -11,8 +11,6 @@ import (
|
|||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
|
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@@ -64,7 +62,7 @@ func TestListPagesOrphanUploadCandidatesFiltersAndLimits(t *testing.T) {
|
|||||||
"pages_project_id": "1",
|
"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++ {
|
for index := 0; index < model.PagesOrphanUploadCandidateLimit+1; index++ {
|
||||||
valid = append(valid, pagesCleanupModelUpload(uint64(index+100), 999, "openflare_pages_deployment", model.UploadStatusUsed, old, marker))
|
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_ingest_marker": "pages_deployment_v1",
|
||||||
"pages_project_id": "1",
|
"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 {
|
if err := gormDB.Create(&upload).Error; err != nil {
|
||||||
t.Fatalf("create filtered upload %d error = %v, want nil", upload.ID, err)
|
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 {
|
if err := gormDB.Create(&invalidJSON).Error; err != nil {
|
||||||
t.Fatalf("create invalid JSON upload error = %v, want nil", err)
|
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 {
|
UpdateColumn("metadata", "{invalid").Error; err != nil {
|
||||||
t.Fatalf("corrupt upload metadata error = %v, want nil", err)
|
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 {
|
if err := gormDB.Create(&upload).Error; err != nil {
|
||||||
t.Fatalf("create invalid JSON candidate error = %v, want nil", err)
|
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 {
|
UpdateColumn("metadata", "{invalid").Error; err != nil {
|
||||||
t.Fatalf("corrupt upload metadata error = %v, want nil", err)
|
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 {
|
func setupPagesCleanupModelTestDB(t *testing.T) *gorm.DB {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
@@ -161,11 +178,11 @@ func setupPagesCleanupModelTestDB(t *testing.T) *gorm.DB {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("open Pages cleanup model test database error = %v, want nil", err)
|
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)
|
t.Fatalf("migrate Pages cleanup model test database error = %v, want nil", err)
|
||||||
}
|
}
|
||||||
db.SetDB(gormDB)
|
SetDBForTest(gormDB)
|
||||||
t.Cleanup(func() { db.SetDB(nil) })
|
t.Cleanup(func() { SetDBForTest(nil) })
|
||||||
return gormDB
|
return gormDB
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,21 +193,19 @@ func pagesCleanupModelUpload(
|
|||||||
status model.UploadStatus,
|
status model.UploadStatus,
|
||||||
createdAt time.Time,
|
createdAt time.Time,
|
||||||
metadata model.UploadMetadata,
|
metadata model.UploadMetadata,
|
||||||
) model.Upload {
|
) testUploadEntity {
|
||||||
return model.Upload{
|
return testUploadEntity{
|
||||||
ID: id,
|
ID: id,
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
FileName: "site.zip",
|
FileName: "site.zip",
|
||||||
FilePath: "pages/site.zip",
|
FilePath: "pages/site.zip",
|
||||||
FileSize: 10,
|
Size: 10,
|
||||||
MimeType: "application/zip",
|
MimeType: "application/zip",
|
||||||
Extension: "zip",
|
Hash: "checksum",
|
||||||
Hash: "checksum",
|
Type: uploadType,
|
||||||
Type: uploadType,
|
Status: status,
|
||||||
Status: status,
|
Metadata: metadata,
|
||||||
AccessMode: 0,
|
CreatedAt: createdAt,
|
||||||
Metadata: metadata,
|
UpdatedAt: createdAt,
|
||||||
CreatedAt: createdAt,
|
|
||||||
UpdatedAt: createdAt,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,20 +11,19 @@ import (
|
|||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const pagesRowLockStrength = "UPDATE"
|
const pagesRowLockStrength = "UPDATE"
|
||||||
|
|
||||||
// WithPagesTx runs fn inside a database transaction for Pages multi-step work.
|
// WithPagesTx runs fn inside a database transaction for Pages multi-step work.
|
||||||
func WithPagesTx(ctx context.Context, fn func(tx *gorm.DB) error) error {
|
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.
|
// GetPagesProjectSourceByID loads a project source by primary key.
|
||||||
func GetPagesProjectSourceByID(ctx context.Context, id uint) (*model.PagesProjectSource, error) {
|
func GetPagesProjectSourceByID(ctx context.Context, id uint) (*model.PagesProjectSource, error) {
|
||||||
var source model.PagesProjectSource
|
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 nil, err
|
||||||
}
|
}
|
||||||
return &source, nil
|
return &source, nil
|
||||||
@@ -33,7 +32,7 @@ func GetPagesProjectSourceByID(ctx context.Context, id uint) (*model.PagesProjec
|
|||||||
// GetPagesProjectSourceByProjectID loads the unique source for a project.
|
// GetPagesProjectSourceByProjectID loads the unique source for a project.
|
||||||
func GetPagesProjectSourceByProjectID(ctx context.Context, projectID uint) (*model.PagesProjectSource, error) {
|
func GetPagesProjectSourceByProjectID(ctx context.Context, projectID uint) (*model.PagesProjectSource, error) {
|
||||||
var source model.PagesProjectSource
|
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 nil, err
|
||||||
}
|
}
|
||||||
return &source, nil
|
return &source, nil
|
||||||
@@ -46,7 +45,7 @@ func GetPagesProjectSourceByIDAndConfigVersion(
|
|||||||
configVersion int,
|
configVersion int,
|
||||||
) (*model.PagesProjectSource, error) {
|
) (*model.PagesProjectSource, error) {
|
||||||
var source model.PagesProjectSource
|
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 nil, err
|
||||||
}
|
}
|
||||||
return &source, nil
|
return &source, nil
|
||||||
@@ -58,7 +57,7 @@ func GetPagesProjectSourceRuntimeBySourceID(
|
|||||||
sourceID uint,
|
sourceID uint,
|
||||||
) (*model.PagesProjectSourceRuntime, error) {
|
) (*model.PagesProjectSourceRuntime, error) {
|
||||||
var runtime model.PagesProjectSourceRuntime
|
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 nil, err
|
||||||
}
|
}
|
||||||
return &runtime, nil
|
return &runtime, nil
|
||||||
@@ -207,7 +206,7 @@ func TryAcquirePagesSourceRuntimeLease(
|
|||||||
now time.Time,
|
now time.Time,
|
||||||
updates map[string]any,
|
updates map[string]any,
|
||||||
) (int64, error) {
|
) (int64, error) {
|
||||||
result := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
result := DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||||
Where("source_id = ?", sourceID).
|
Where("source_id = ?", sourceID).
|
||||||
Where("lease_expires_at IS NULL OR lease_expires_at <= ?", now).
|
Where("lease_expires_at IS NULL OR lease_expires_at <= ?", now).
|
||||||
Where(
|
Where(
|
||||||
@@ -227,7 +226,7 @@ func RenewPagesSourceRuntimeLease(
|
|||||||
now time.Time,
|
now time.Time,
|
||||||
expiresAt time.Time,
|
expiresAt time.Time,
|
||||||
) (int64, error) {
|
) (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).
|
Where("source_id = ? AND lease_token = ? AND lease_expires_at > ?", sourceID, token, now).
|
||||||
Updates(map[string]any{"lease_expires_at": expiresAt})
|
Updates(map[string]any{"lease_expires_at": expiresAt})
|
||||||
return result.RowsAffected, result.Error
|
return result.RowsAffected, result.Error
|
||||||
@@ -241,7 +240,7 @@ func UpdatePagesSourceRuntimeByActiveLease(
|
|||||||
now time.Time,
|
now time.Time,
|
||||||
updates map[string]any,
|
updates map[string]any,
|
||||||
) (int64, error) {
|
) (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.
|
// UpdatePagesSourceRuntimeByActiveLeaseTx updates runtime under an active lease inside a transaction.
|
||||||
@@ -268,7 +267,7 @@ func RecoverExpiredPagesSourceRuntimeLease(
|
|||||||
now time.Time,
|
now time.Time,
|
||||||
updates map[string]any,
|
updates map[string]any,
|
||||||
) (int64, error) {
|
) (int64, error) {
|
||||||
result := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
result := DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||||
Where("source_id = ?", sourceID).
|
Where("source_id = ?", sourceID).
|
||||||
Where("lease_token = ?", token).
|
Where("lease_token = ?", token).
|
||||||
Where("lease_expires_at = ? AND lease_expires_at <= ?", expiresAt, now).
|
Where("lease_expires_at = ? AND lease_expires_at <= ?", expiresAt, now).
|
||||||
@@ -285,7 +284,7 @@ func MarkPagesSourceInitialCheckDispatchFailed(
|
|||||||
now time.Time,
|
now time.Time,
|
||||||
updates map[string]any,
|
updates map[string]any,
|
||||||
) (int64, error) {
|
) (int64, error) {
|
||||||
result := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
result := DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||||
Where("source_id = ?", sourceID).
|
Where("source_id = ?", sourceID).
|
||||||
Where("lease_expires_at IS NULL OR lease_expires_at <= ?", now).
|
Where("lease_expires_at IS NULL OR lease_expires_at <= ?", now).
|
||||||
Where(
|
Where(
|
||||||
@@ -309,7 +308,7 @@ func RecordPagesSourceAutoDispatchFailure(
|
|||||||
now time.Time,
|
now time.Time,
|
||||||
updates map[string]any,
|
updates map[string]any,
|
||||||
) (int64, error) {
|
) (int64, error) {
|
||||||
result := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
result := DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||||
Where("source_id = ?", sourceID).
|
Where("source_id = ?", sourceID).
|
||||||
Where("sync_status = ? AND last_seen_revision = ?", updateAvailableStatus, revision).
|
Where("sync_status = ? AND last_seen_revision = ?", updateAvailableStatus, revision).
|
||||||
Where("lease_expires_at IS NULL OR lease_expires_at <= ?", now).
|
Where("lease_expires_at IS NULL OR lease_expires_at <= ?", now).
|
||||||
@@ -336,7 +335,7 @@ func ListExpiredPagesSourceLeaseCandidates(
|
|||||||
syncStatuses []string,
|
syncStatuses []string,
|
||||||
) ([]model.PagesExpiredSourceLeaseCandidate, error) {
|
) ([]model.PagesExpiredSourceLeaseCandidate, error) {
|
||||||
var candidates []model.PagesExpiredSourceLeaseCandidate
|
var candidates []model.PagesExpiredSourceLeaseCandidate
|
||||||
err := db.DB(ctx).
|
err := DB(ctx).
|
||||||
Table("of_pages_project_source_runtime AS runtime").
|
Table("of_pages_project_source_runtime AS runtime").
|
||||||
Select(`runtime.source_id, runtime.lease_token, runtime.lease_expires_at,
|
Select(`runtime.source_id, runtime.lease_token, runtime.lease_expires_at,
|
||||||
runtime.sync_status, source.source_type, source.release_selector`).
|
runtime.sync_status, source.source_type, source.release_selector`).
|
||||||
@@ -391,7 +390,7 @@ func dueGitHubPagesSourceQuery(
|
|||||||
sourceType string,
|
sourceType string,
|
||||||
releaseSelector string,
|
releaseSelector string,
|
||||||
) *gorm.DB {
|
) *gorm.DB {
|
||||||
return db.DB(ctx).
|
return DB(ctx).
|
||||||
Table("of_pages_project_source_runtime AS runtime").
|
Table("of_pages_project_source_runtime AS runtime").
|
||||||
Joins("JOIN of_pages_project_sources AS source ON source.id = runtime.source_id").
|
Joins("JOIN of_pages_project_sources AS source ON source.id = runtime.source_id").
|
||||||
Where("source.source_type = ?", sourceType).
|
Where("source.source_type = ?", sourceType).
|
||||||
@@ -407,7 +406,7 @@ func GetPagesDeploymentBySourceRevision(
|
|||||||
revision string,
|
revision string,
|
||||||
) (*model.PagesDeployment, error) {
|
) (*model.PagesDeployment, error) {
|
||||||
var deployment model.PagesDeployment
|
var deployment model.PagesDeployment
|
||||||
err := db.DB(ctx).
|
err := DB(ctx).
|
||||||
Where("project_id = ? AND source_identity = ? AND source_revision = ?", projectID, sourceIdentity, revision).
|
Where("project_id = ? AND source_identity = ? AND source_revision = ?", projectID, sourceIdentity, revision).
|
||||||
First(&deployment).Error
|
First(&deployment).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Zone domain binding sentinel errors (shared by proxy_route and zone binding helpers).
|
// 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.
|
// 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 {
|
func WithProxyRouteTx(ctx context.Context, fn func(tx *gorm.DB) error) error {
|
||||||
return db.DB(ctx).Transaction(fn)
|
return DB(ctx).Transaction(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListProxyRoutes 列出全部代理规则。
|
// ListProxyRoutes 列出全部代理规则。
|
||||||
func ListProxyRoutes(ctx context.Context) ([]*model.ProxyRoute, error) {
|
func ListProxyRoutes(ctx context.Context) ([]*model.ProxyRoute, error) {
|
||||||
var routes []*model.ProxyRoute
|
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 nil, err
|
||||||
}
|
}
|
||||||
return routes, nil
|
return routes, nil
|
||||||
@@ -39,7 +38,7 @@ func ListProxyRoutes(ctx context.Context) ([]*model.ProxyRoute, error) {
|
|||||||
// GetProxyRouteByID 按 ID 查询代理规则。
|
// GetProxyRouteByID 按 ID 查询代理规则。
|
||||||
func GetProxyRouteByID(ctx context.Context, id uint) (*model.ProxyRoute, error) {
|
func GetProxyRouteByID(ctx context.Context, id uint) (*model.ProxyRoute, error) {
|
||||||
var route model.ProxyRoute
|
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 nil, err
|
||||||
}
|
}
|
||||||
return &route, nil
|
return &route, nil
|
||||||
@@ -47,7 +46,7 @@ func GetProxyRouteByID(ctx context.Context, id uint) (*model.ProxyRoute, error)
|
|||||||
|
|
||||||
// CreateProxyRouteRecord 创建代理规则。
|
// CreateProxyRouteRecord 创建代理规则。
|
||||||
func CreateProxyRouteRecord(ctx context.Context, route *model.ProxyRoute) error {
|
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.
|
// CreateProxyRouteRecordTx creates a proxy route within an existing transaction.
|
||||||
@@ -57,7 +56,7 @@ func CreateProxyRouteRecordTx(tx *gorm.DB, route *model.ProxyRoute) error {
|
|||||||
|
|
||||||
// UpdateProxyRouteRecord 更新代理规则。
|
// UpdateProxyRouteRecord 更新代理规则。
|
||||||
func UpdateProxyRouteRecord(ctx context.Context, route *model.ProxyRoute) error {
|
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.
|
// UpdateProxyRouteRecordTx updates a proxy route within an existing transaction.
|
||||||
@@ -96,7 +95,7 @@ func proxyRouteUpdateMap(route *model.ProxyRoute) map[string]any {
|
|||||||
|
|
||||||
// DeleteProxyRouteRecord 删除代理规则。
|
// DeleteProxyRouteRecord 删除代理规则。
|
||||||
func DeleteProxyRouteRecord(ctx context.Context, id uint) error {
|
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.
|
// DeleteProxyRouteRecordTx deletes a proxy route within an existing transaction.
|
||||||
|
|||||||
@@ -8,17 +8,16 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// HasTLSProxyRoutesTable 判断代理规则表是否已迁移。
|
// HasTLSProxyRoutesTable 判断代理规则表是否已迁移。
|
||||||
func HasTLSProxyRoutesTable(ctx context.Context) bool {
|
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 控制)。
|
// ListTLSCertificates 列出全部证书(不含 PEM 敏感字段的 JSON 暴露由 struct tag 控制)。
|
||||||
func ListTLSCertificates(ctx context.Context) ([]model.TLSCertificate, error) {
|
func ListTLSCertificates(ctx context.Context) ([]model.TLSCertificate, error) {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return nil, errors.New(errDatabaseNotInitialized)
|
return nil, errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -31,7 +30,7 @@ func ListTLSCertificates(ctx context.Context) ([]model.TLSCertificate, error) {
|
|||||||
|
|
||||||
// GetTLSCertificateByID 按 ID 查询证书。
|
// GetTLSCertificateByID 按 ID 查询证书。
|
||||||
func GetTLSCertificateByID(ctx context.Context, id uint) (*model.TLSCertificate, error) {
|
func GetTLSCertificateByID(ctx context.Context, id uint) (*model.TLSCertificate, error) {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return nil, errors.New(errDatabaseNotInitialized)
|
return nil, errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -44,7 +43,7 @@ func GetTLSCertificateByID(ctx context.Context, id uint) (*model.TLSCertificate,
|
|||||||
|
|
||||||
// CreateTLSCertificateRecord 创建证书记录。
|
// CreateTLSCertificateRecord 创建证书记录。
|
||||||
func CreateTLSCertificateRecord(ctx context.Context, certificate *model.TLSCertificate) error {
|
func CreateTLSCertificateRecord(ctx context.Context, certificate *model.TLSCertificate) error {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New(errDatabaseNotInitialized)
|
return errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -53,7 +52,7 @@ func CreateTLSCertificateRecord(ctx context.Context, certificate *model.TLSCerti
|
|||||||
|
|
||||||
// SaveTLSCertificate 保存证书记录。
|
// SaveTLSCertificate 保存证书记录。
|
||||||
func SaveTLSCertificate(ctx context.Context, certificate *model.TLSCertificate) error {
|
func SaveTLSCertificate(ctx context.Context, certificate *model.TLSCertificate) error {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New(errDatabaseNotInitialized)
|
return errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -62,7 +61,7 @@ func SaveTLSCertificate(ctx context.Context, certificate *model.TLSCertificate)
|
|||||||
|
|
||||||
// DeleteTLSCertificateRecord 删除证书记录。
|
// DeleteTLSCertificateRecord 删除证书记录。
|
||||||
func DeleteTLSCertificateRecord(ctx context.Context, id uint) error {
|
func DeleteTLSCertificateRecord(ctx context.Context, id uint) error {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New(errDatabaseNotInitialized)
|
return errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -71,7 +70,7 @@ func DeleteTLSCertificateRecord(ctx context.Context, id uint) error {
|
|||||||
|
|
||||||
// CountTLSCertificatesByDNSAccountID 统计引用指定 DNS 账号的证书数量。
|
// CountTLSCertificatesByDNSAccountID 统计引用指定 DNS 账号的证书数量。
|
||||||
func CountTLSCertificatesByDNSAccountID(ctx context.Context, dnsAccountID uint) (int64, error) {
|
func CountTLSCertificatesByDNSAccountID(ctx context.Context, dnsAccountID uint) (int64, error) {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return 0, errors.New(errDatabaseNotInitialized)
|
return 0, errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
@@ -88,7 +87,7 @@ func ListTLSProxyRouteRefs(ctx context.Context) ([]model.TLSProxyRouteRef, error
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
var routes []model.TLSProxyRouteRef
|
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 nil, err
|
||||||
}
|
}
|
||||||
return routes, nil
|
return routes, nil
|
||||||
|
|||||||
@@ -11,11 +11,10 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func wafDB(ctx context.Context) (*gorm.DB, error) {
|
func wafDB(ctx context.Context) (*gorm.DB, error) {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return nil, errors.New(errDatabaseNotInitialized)
|
return nil, errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ import (
|
|||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
|
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -26,9 +24,9 @@ func setupWAFBindingsTestDB(t *testing.T) func() {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, sqliteDB.AutoMigrate(&model.OpenFlareWAFRuleGroupBinding{}))
|
require.NoError(t, sqliteDB.AutoMigrate(&model.OpenFlareWAFRuleGroupBinding{}))
|
||||||
|
|
||||||
db.SetDB(sqliteDB)
|
SetDBForTest(sqliteDB)
|
||||||
return func() {
|
return func() {
|
||||||
db.SetDB(nil)
|
SetDBForTest(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +35,7 @@ func TestReplaceOpenFlareWAFRuleGroupBindingsAfterExplicitHighID(t *testing.T) {
|
|||||||
defer cleanup()
|
defer cleanup()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
require.NotNil(t, conn)
|
require.NotNil(t, conn)
|
||||||
require.NoError(t, conn.Create(&model.OpenFlareWAFRuleGroupBinding{
|
require.NoError(t, conn.Create(&model.OpenFlareWAFRuleGroupBinding{
|
||||||
ID: 50,
|
ID: 50,
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ import (
|
|||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
|
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -23,8 +21,8 @@ func TestOpenFlareWAFGraphOptimisticUpdate(t *testing.T) {
|
|||||||
conn, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
conn, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, conn.AutoMigrate(&model.OpenFlareWAFRuleGroup{}))
|
require.NoError(t, conn.AutoMigrate(&model.OpenFlareWAFRuleGroup{}))
|
||||||
db.SetDB(conn)
|
SetDBForTest(conn)
|
||||||
t.Cleanup(func() { db.SetDB(nil) })
|
t.Cleanup(func() { SetDBForTest(nil) })
|
||||||
|
|
||||||
group := model.OpenFlareWAFRuleGroup{Name: "rule", Graph: defaultWAFRuleGraph, Revision: 1}
|
group := model.OpenFlareWAFRuleGroup{Name: "rule", Graph: defaultWAFRuleGraph, Revision: 1}
|
||||||
require.NoError(t, conn.Create(&group).Error)
|
require.NoError(t, conn.Create(&group).Error)
|
||||||
|
|||||||
@@ -10,13 +10,12 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ListZones returns all zones ordered by domain ascending.
|
// ListZones returns all zones ordered by domain ascending.
|
||||||
func ListZones(ctx context.Context) ([]model.Zone, error) {
|
func ListZones(ctx context.Context) ([]model.Zone, error) {
|
||||||
var zones []model.Zone
|
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 nil, err
|
||||||
}
|
}
|
||||||
return zones, nil
|
return zones, nil
|
||||||
@@ -25,7 +24,7 @@ func ListZones(ctx context.Context) ([]model.Zone, error) {
|
|||||||
// GetZoneByID returns a zone by primary key.
|
// GetZoneByID returns a zone by primary key.
|
||||||
func GetZoneByID(ctx context.Context, id uint) (*model.Zone, error) {
|
func GetZoneByID(ctx context.Context, id uint) (*model.Zone, error) {
|
||||||
var zone model.Zone
|
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 nil, err
|
||||||
}
|
}
|
||||||
return &zone, nil
|
return &zone, nil
|
||||||
@@ -33,23 +32,23 @@ func GetZoneByID(ctx context.Context, id uint) (*model.Zone, error) {
|
|||||||
|
|
||||||
// CreateZone creates a zone record.
|
// CreateZone creates a zone record.
|
||||||
func CreateZone(ctx context.Context, zone *model.Zone) error {
|
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.
|
// SaveZone persists zone updates.
|
||||||
func SaveZone(ctx context.Context, zone *model.Zone) error {
|
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.
|
// DeleteZone deletes a zone by primary key.
|
||||||
func DeleteZone(ctx context.Context, id uint) error {
|
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.
|
// ListZoneDomainCounts returns per-zone domain counts for list cards.
|
||||||
func ListZoneDomainCounts(ctx context.Context) ([]model.ZoneDomainCount, error) {
|
func ListZoneDomainCounts(ctx context.Context) ([]model.ZoneDomainCount, error) {
|
||||||
var rows []model.ZoneDomainCount
|
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").
|
Select("zone_id, count(*) as count").
|
||||||
Group("zone_id").
|
Group("zone_id").
|
||||||
Scan(&rows).Error; err != nil {
|
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.
|
// ListZoneDomainsByZoneID returns domains under a zone ordered by domain ascending.
|
||||||
func ListZoneDomainsByZoneID(ctx context.Context, zoneID uint) ([]model.ZoneDomain, error) {
|
func ListZoneDomainsByZoneID(ctx context.Context, zoneID uint) ([]model.ZoneDomain, error) {
|
||||||
var domains []model.ZoneDomain
|
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 nil, err
|
||||||
}
|
}
|
||||||
return domains, nil
|
return domains, nil
|
||||||
@@ -70,7 +69,7 @@ func ListZoneDomainsByZoneID(ctx context.Context, zoneID uint) ([]model.ZoneDoma
|
|||||||
// CountZoneDomainsByZoneID counts domains under a zone.
|
// CountZoneDomainsByZoneID counts domains under a zone.
|
||||||
func CountZoneDomainsByZoneID(ctx context.Context, zoneID uint) (int64, error) {
|
func CountZoneDomainsByZoneID(ctx context.Context, zoneID uint) (int64, error) {
|
||||||
var count int64
|
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 0, err
|
||||||
}
|
}
|
||||||
return count, nil
|
return count, nil
|
||||||
@@ -79,7 +78,7 @@ func CountZoneDomainsByZoneID(ctx context.Context, zoneID uint) (int64, error) {
|
|||||||
// GetZoneDomainByZoneAndID returns a domain scoped to a zone.
|
// GetZoneDomainByZoneAndID returns a domain scoped to a zone.
|
||||||
func GetZoneDomainByZoneAndID(ctx context.Context, zoneID, id uint) (*model.ZoneDomain, error) {
|
func GetZoneDomainByZoneAndID(ctx context.Context, zoneID, id uint) (*model.ZoneDomain, error) {
|
||||||
var item model.ZoneDomain
|
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 nil, err
|
||||||
}
|
}
|
||||||
return &item, nil
|
return &item, nil
|
||||||
@@ -87,17 +86,17 @@ func GetZoneDomainByZoneAndID(ctx context.Context, zoneID, id uint) (*model.Zone
|
|||||||
|
|
||||||
// CreateZoneDomain creates a zone domain record.
|
// CreateZoneDomain creates a zone domain record.
|
||||||
func CreateZoneDomain(ctx context.Context, domain *model.ZoneDomain) error {
|
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.
|
// SaveZoneDomain persists zone domain updates.
|
||||||
func SaveZoneDomain(ctx context.Context, domain *model.ZoneDomain) error {
|
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.
|
// DeleteZoneDomain deletes a zone domain record.
|
||||||
func DeleteZoneDomain(ctx context.Context, domain *model.ZoneDomain) error {
|
func DeleteZoneDomain(ctx context.Context, domain *model.ZoneDomain) error {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New(errDatabaseNotInitialized)
|
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.
|
// ListZoneDomainsByRouteID returns the domains bound to a proxy route.
|
||||||
func ListZoneDomainsByRouteID(ctx context.Context, routeID uint) ([]model.ZoneDomain, error) {
|
func ListZoneDomainsByRouteID(ctx context.Context, routeID uint) ([]model.ZoneDomain, error) {
|
||||||
var domains []model.ZoneDomain
|
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 nil, err
|
||||||
}
|
}
|
||||||
return domains, nil
|
return domains, nil
|
||||||
@@ -124,7 +123,7 @@ func ListZoneDomainsByIDs(ctx context.Context, domainIDs []uint) ([]model.ZoneDo
|
|||||||
return []model.ZoneDomain{}, nil
|
return []model.ZoneDomain{}, nil
|
||||||
}
|
}
|
||||||
var domains []model.ZoneDomain
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
byID := make(map[uint]model.ZoneDomain, len(domains))
|
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.
|
// CountZoneDomainsByCertificateID reports whether a certificate is assigned to a model.Zone domain.
|
||||||
func CountZoneDomainsByCertificateID(ctx context.Context, certificateID uint) (int64, error) {
|
func CountZoneDomainsByCertificateID(ctx context.Context, certificateID uint) (int64, error) {
|
||||||
var count int64
|
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
|
return count, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReplaceZoneDomainRouteBindings replaces every model.ZoneDomain binding for a proxy route.
|
// ReplaceZoneDomainRouteBindings replaces every model.ZoneDomain binding for a proxy route.
|
||||||
func ReplaceZoneDomainRouteBindings(ctx context.Context, routeID uint, domainIDs []uint) error {
|
func ReplaceZoneDomainRouteBindings(ctx context.Context, routeID uint, domainIDs []uint) error {
|
||||||
conn := db.DB(ctx)
|
conn := DB(ctx)
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return errors.New(errDatabaseNotInitialized)
|
return errors.New(errDatabaseNotInitialized)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ import (
|
|||||||
|
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"Wavelet/openflare/plugins/server/kernel/model"
|
||||||
|
|
||||||
db "Wavelet/plugins/infra/database"
|
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@@ -24,8 +22,8 @@ func setupZoneTestDB(t *testing.T) *gorm.DB {
|
|||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, sqliteDB.AutoMigrate(&model.Zone{}, &model.ZoneDomain{}))
|
require.NoError(t, sqliteDB.AutoMigrate(&model.Zone{}, &model.ZoneDomain{}))
|
||||||
db.SetDB(sqliteDB)
|
SetDBForTest(sqliteDB)
|
||||||
t.Cleanup(func() { db.SetDB(nil) })
|
t.Cleanup(func() { SetDBForTest(nil) })
|
||||||
return sqliteDB
|
return sqliteDB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,112 +6,162 @@ package repository
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"Wavelet/core/contracts"
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"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
|
// SetSystemConfigService injects the platform SystemConfigService.
|
||||||
// reads hit the same cache that SaveOrUpdateSystemConfig invalidates.
|
func SetSystemConfigService(s contracts.SystemConfigService) {
|
||||||
func ensureAdminStore(ctx context.Context) error {
|
configMu.Lock()
|
||||||
if conn := db.DB(ctx); conn != nil {
|
defer configMu.Unlock()
|
||||||
adminrepo.SetDBService(db.NewService(conn))
|
configSvc = s
|
||||||
}
|
|
||||||
if adminrepo.GetDB(ctx) == nil {
|
|
||||||
return errors.New(errDatabaseNotInitialized)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) {
|
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 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) {
|
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 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) {
|
func ListVisibleSystemConfigs(ctx context.Context) ([]model.SystemConfig, error) {
|
||||||
if err := ensureAdminStore(ctx); err != nil {
|
svc, err := ensureConfigService()
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// GetIntByKey queries config and converts to int.
|
||||||
func GetIntByKey(ctx context.Context, key string) (int, error) {
|
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 0, err
|
||||||
}
|
}
|
||||||
return adminrepo.GetIntByKey(ctx, key)
|
return svc.GetIntByKey(ctx, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBoolByKey queries config and converts to bool.
|
// GetBoolByKey queries config and converts to bool.
|
||||||
func GetBoolByKey(ctx context.Context, key string) (bool, error) {
|
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 false, err
|
||||||
}
|
}
|
||||||
return adminrepo.GetBoolByKey(ctx, key)
|
return svc.GetBoolByKey(ctx, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateSystemConfig persists a new system config row.
|
// CreateSystemConfig persists a new system config row.
|
||||||
func CreateSystemConfig(ctx context.Context, config *model.SystemConfig) error {
|
func CreateSystemConfig(ctx context.Context, config *model.SystemConfig) error {
|
||||||
if err := ensureAdminStore(ctx); err != nil {
|
svc, err := ensureConfigService()
|
||||||
|
if err != nil {
|
||||||
return err
|
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 {
|
func SaveOrUpdateSystemConfig(ctx context.Context, key, value string) error {
|
||||||
if err := ensureAdminStore(ctx); err != nil {
|
svc, err := ensureConfigService()
|
||||||
|
if err != nil {
|
||||||
return err
|
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 {
|
func InvalidateSystemConfigCache(ctx context.Context, key string) error {
|
||||||
if err := ensureAdminStore(ctx); err != nil {
|
svc, err := ensureConfigService()
|
||||||
|
if err != nil {
|
||||||
return err
|
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 {
|
func InvalidateAllSystemConfigCaches(ctx context.Context) error {
|
||||||
if err := ensureAdminStore(ctx); err != nil {
|
svc, err := ensureConfigService()
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return adminrepo.InvalidateAllSystemConfigCaches(ctx)
|
return svc.InvalidateAllCaches(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StopSystemConfigCacheListener is retained for existing tests.
|
// StopSystemConfigCacheListener is retained for test compatibility.
|
||||||
func StopSystemConfigCacheListener() {
|
func StopSystemConfigCacheListener() {}
|
||||||
adminrepo.StopSystemConfigCacheListener()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResetSystemConfigRAMCacheForTest clears the process-local admin config cache.
|
// ResetSystemConfigRAMCacheForTest clears the process-local admin config cache.
|
||||||
func ResetSystemConfigRAMCacheForTest() {
|
func ResetSystemConfigRAMCacheForTest() {
|
||||||
adminrepo.ResetSystemConfigRAMCacheForTest()
|
if svc := currentConfigService(); svc != nil {
|
||||||
|
_ = svc.InvalidateAllCaches(context.Background())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListAdminSystemConfigs returns configs, optionally filtered by type.
|
// ListAdminSystemConfigs returns configs, optionally filtered by type.
|
||||||
func ListAdminSystemConfigs(ctx context.Context, configType string) ([]model.SystemConfig, error) {
|
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 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,12 +6,34 @@ package repository
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"Wavelet/core/contracts"
|
||||||
"Wavelet/openflare/plugins/server/kernel/model"
|
"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.
|
// GetActiveAuthSources lists enabled Wavelet auth sources via AuthService.
|
||||||
func GetActiveAuthSources(ctx context.Context) ([]model.AuthSource, error) {
|
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.
|
// GetTaskExecutionByTaskID loads a task execution by public task ID.
|
||||||
func GetTaskExecutionByTaskID(ctx context.Context, taskID string) (*model.TaskExecution, error) {
|
func GetTaskExecutionByTaskID(ctx context.Context, taskID string) (*contracts.TaskExecutionDTO, error) {
|
||||||
if err := ensureAdminStore(ctx); err != nil {
|
svc := currentTaskService()
|
||||||
return nil, err
|
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.
|
// GetSystemUser loads the built-in system user via UserService, or a synthetic fallback.
|
||||||
|
|||||||
@@ -5,15 +5,10 @@ package repository
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"Wavelet/core/contracts"
|
"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 {
|
type stubUserService struct {
|
||||||
@@ -34,24 +29,6 @@ func (s stubAuthService) ListAuthSources(context.Context) ([]contracts.AuthSourc
|
|||||||
return s.sources, nil
|
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) {
|
func TestGetActiveAuthSourcesUsesAuthService(t *testing.T) {
|
||||||
SetAuthService(stubAuthService{})
|
SetAuthService(stubAuthService{})
|
||||||
t.Cleanup(func() { SetAuthService(nil) })
|
t.Cleanup(func() { SetAuthService(nil) })
|
||||||
@@ -96,21 +73,27 @@ func TestGetSystemUserUsesUserService(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetTaskExecutionByTaskIDUsesAdminStore(t *testing.T) {
|
type mockTaskSvc struct {
|
||||||
_, cleanup := setupRepoTestDB(t)
|
contracts.TaskService
|
||||||
t.Cleanup(cleanup)
|
execution contracts.TaskExecutionDTO
|
||||||
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
func (m *mockTaskSvc) GetExecutionByTaskID(ctx context.Context, taskID string) (*contracts.TaskExecutionDTO, error) {
|
||||||
row := &adminmodel.TaskExecution{
|
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,
|
ID: 7,
|
||||||
TaskID: "task-public-id",
|
TaskID: "task-public-id",
|
||||||
TaskType: "pages_source_action",
|
TaskType: "pages_source_action",
|
||||||
Status: adminmodel.TaskExecutionStatusPending,
|
}})
|
||||||
}
|
t.Cleanup(func() { SetTaskService(nil) })
|
||||||
if err := database.DB(ctx).Create(row).Error; err != nil {
|
|
||||||
t.Fatalf("Create(TaskExecution) error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
got, err := GetTaskExecutionByTaskID(ctx, "task-public-id")
|
got, err := GetTaskExecutionByTaskID(ctx, "task-public-id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("GetTaskExecutionByTaskID(%q) error = %v", "task-public-id", err)
|
t.Fatalf("GetTaskExecutionByTaskID(%q) error = %v", "task-public-id", err)
|
||||||
|
|||||||
@@ -6,15 +6,27 @@ package runtimeconfig
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"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.
|
// Snapshot is the subset of host config remaining OF packages still need.
|
||||||
type Snapshot struct {
|
type Snapshot struct {
|
||||||
SessionSecret string
|
SessionSecret string
|
||||||
DatabaseEnabled bool
|
DatabaseEnabled bool
|
||||||
ClickHouse database.ClickHouseConfig
|
ClickHouse ClickHouseConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -9,9 +9,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"Wavelet/core/contracts"
|
"Wavelet/core/contracts"
|
||||||
|
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||||
"Wavelet/pkg/idgen"
|
"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.
|
// NoopTaskService is a contracts.TaskService that records dispatch and ignores the rest.
|
||||||
@@ -22,35 +21,76 @@ type NoopTaskService struct {
|
|||||||
|
|
||||||
var _ contracts.TaskService = (*NoopTaskService)(nil)
|
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) {
|
func (s *NoopTaskService) Dispatch(ctx context.Context, taskType string, payload []byte, triggeredBy string) (string, error) {
|
||||||
s.LastType = taskType
|
s.LastType = taskType
|
||||||
s.LastPayload = payload
|
s.LastPayload = payload
|
||||||
taskID := fmt.Sprintf("test-task-%d", time.Now().UnixNano())
|
taskID := fmt.Sprintf("test-task-%d", time.Now().UnixNano())
|
||||||
if conn := database.DB(ctx); conn != nil {
|
gdb := repository.DB(ctx)
|
||||||
_ = conn.Create(&adminmodel.TaskExecution{
|
if gdb != nil {
|
||||||
ID: idgen.NextUint64ID(),
|
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,
|
TaskID: taskID,
|
||||||
TaskType: taskType,
|
TaskType: taskType,
|
||||||
Status: adminmodel.TaskExecutionStatusPending,
|
|
||||||
TriggeredBy: triggeredBy,
|
|
||||||
Payload: string(payload),
|
Payload: string(payload),
|
||||||
|
TriggeredBy: triggeredBy,
|
||||||
|
Status: "pending",
|
||||||
|
CreatedAt: time.Now().UTC(),
|
||||||
|
UpdatedAt: time.Now().UTC(),
|
||||||
}).Error
|
}).Error
|
||||||
}
|
}
|
||||||
return taskID, nil
|
return taskID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retry retries a task mock execution.
|
||||||
func (s *NoopTaskService) Retry(context.Context, uint64) (string, error) { return "", nil }
|
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) {
|
func (s *NoopTaskService) GetTaskMeta(string) (contracts.TaskMetaDTO, bool) {
|
||||||
return contracts.TaskMetaDTO{}, false
|
return contracts.TaskMetaDTO{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidatePayload validates task payload.
|
||||||
func (s *NoopTaskService) ValidatePayload(_ string, payload []byte) ([]byte, error) {
|
func (s *NoopTaskService) ValidatePayload(_ string, payload []byte) ([]byte, error) {
|
||||||
return payload, nil
|
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) {}
|
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) {
|
func (s *NoopTaskService) ListExecutions(context.Context, string, string, int, int) ([]contracts.TaskExecutionDTO, int64, error) {
|
||||||
return nil, 0, nil
|
return nil, 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetExecution gets task execution by ID.
|
||||||
func (s *NoopTaskService) GetExecution(context.Context, uint64) (*contracts.TaskExecutionDTO, error) {
|
func (s *NoopTaskService) GetExecution(context.Context, uint64) (*contracts.TaskExecutionDTO, error) {
|
||||||
return &contracts.TaskExecutionDTO{TaskID: "test-task"}, nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user