mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-28 05:46:36 +08:00
Merge branch 'main' into cordis
This commit is contained in:
@@ -13,102 +13,38 @@ description: "Wavelet 项目专用:当新增或修改自定义业务 API、新
|
||||
|
||||
Wavelet 后端路由采用了**严格的框架层与业务层隔离机制**。请牢记以下开发原则:
|
||||
|
||||
1. **禁止修改框架级路由文件**:
|
||||
- 以下文件属于系统框架/平台级接口,**禁止为了添加自定义业务接口而进行任何修改**:
|
||||
- `internal/router/router.go`(核心入口委派)
|
||||
- `internal/router/root/default.go`(公开文件服务、robots.txt、Swagger 及 /api/health 路由)
|
||||
- `internal/router/root/frontend.go`(前端静态服务)
|
||||
- `internal/router/v1/v1.go`(V1 分发层协调器)
|
||||
- `internal/router/v1/admin.go`(框架管理员端管理接口)
|
||||
- `internal/router/v1/user.go`(框架普通用户端基础接口、OAuth及公开接口)
|
||||
2. **仅允许在 `custom.go` 中注册业务接口**:
|
||||
- 所有的自定义/业务相关接口注册,有且仅有以下两个合法的承载点:
|
||||
- [internal/router/root/custom.go](file:///Users/ryan/DEV/Go/Wavelet/internal/router/root/custom.go)(用于挂载到根路径的特殊业务接口)
|
||||
- [internal/router/v1/custom.go](file:///Users/ryan/DEV/Go/Wavelet/internal/router/v1/custom.go)(用于挂载在 API V1 下的标准自定义业务接口)
|
||||
### 插件目录标准结构 (`backend/openflare/plugins/<name>/` 或 `backend/plugins/domain/<name>/`)
|
||||
|
||||
---
|
||||
|
||||
## 路由归属判定表 (Where should I register my new API?)
|
||||
|
||||
根据接口的**访问路径特征**和**访问身份/限制条件**,决定将新开发的 API 挂载至何处:
|
||||
|
||||
| 目标 API 路径特征 | 访问身份/条件限制 | 对应的路由注册入口 | 是否允许修改 |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **`/my-custom-path`** (挂载在根路径下的特殊业务接口) | 自定义控制 | `root/custom.go` 中的 `RegisterCustomRootRoutes` | **允许修改 (业务自定义入口)** |
|
||||
| **`/api/v1/custom/...`** (API v1 下的定制业务接口) | 自定义控制 | `v1/custom.go` 中的 `RegisterCustomRoutes` | **允许修改 (业务自定义入口)** |
|
||||
| **`/api/v1/admin/...`** (系统管理员管理端接口) | 需要管理员登录 (`admin.LoginAdminRequired()`) | `v1/admin.go` | **禁止修改 (仅限系统框架路由)** |
|
||||
| **`/api/v1/user/...`** (框架普通用户基础接口) | 需要普通用户登录 (`oauth.LoginRequired()`) | `v1/user.go` | **禁止修改 (仅限系统框架路由)** |
|
||||
| **`/api/v1/public/...`** (Captcha、Config 等系统公开接口) | 所有人 (无条件 / 公开) | `v1/user.go` | **禁止修改 (仅限系统框架路由)** |
|
||||
| **`GET /f/:id`**, **`GET /robots.txt`**, **`GET /api/health`** (系统级默认及公开接口) | 所有人 (无条件 / 公开) | `root/default.go` | **禁止修改 (仅限系统框架路由)** |
|
||||
|
||||
---
|
||||
|
||||
## 两个自定义路由包的用法与区别 (Root Custom vs V1 Custom)
|
||||
|
||||
### 1. 根路径自定义包:`root/custom.go`
|
||||
|
||||
* **适用场景**:适用于需要**直接挂载在主域名根路径下**的特殊自定义业务接口(如第三方 Webhook 回调、特定的短链接重定向、外部数据接口等,不需要 `/api/v1` 前缀)。
|
||||
* **用法示例**:
|
||||
在 [root/custom.go](file:///Users/ryan/DEV/Go/Wavelet/internal/router/root/custom.go) 中实现:
|
||||
```go
|
||||
package root
|
||||
|
||||
import (
|
||||
"OpenFlare/internal/apps/custom"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// RegisterCustomRootRoutes registers custom business routes that belong to the root path.
|
||||
func RegisterCustomRootRoutes(r *gin.Engine) {
|
||||
// 挂载到根路径下,如 GET /my-custom-webhook
|
||||
r.GET("/my-custom-webhook", custom.HandleRootWebhook)
|
||||
}
|
||||
```
|
||||
*(注:该函数已由 `root.go` 自动加载,你无需修改任何其他核心文件。)*
|
||||
|
||||
### 2. V1 API 自定义包:`v1/custom.go`
|
||||
|
||||
* **适用场景**:适用于普通的**自定义业务 API**,需要规范挂载在标准 API V1 路径下(即自动带有 `/api/v1/custom/...` 前缀,可选择性配置用户/管理员登录中间件)。
|
||||
* **用法示例**:
|
||||
在 [v1/custom.go](file:///Users/ryan/DEV/Go/Wavelet/internal/router/v1/custom.go) 中实现:
|
||||
```go
|
||||
package v1
|
||||
|
||||
import (
|
||||
"OpenFlare/internal/apps/custom"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// RegisterCustomRoutes registers standard custom API routes under /api/v1.
|
||||
func RegisterCustomRoutes(apiV1Router *gin.RouterGroup) {
|
||||
customRouter := apiV1Router.Group("/custom")
|
||||
{
|
||||
// 挂载到 /api/v1/custom 下,例如:POST /api/v1/custom/action
|
||||
customRouter.POST("/action", custom.DoActionHandler)
|
||||
}
|
||||
}
|
||||
```
|
||||
*(注:该函数已由 `v1/v1.go` 自动加载,你无需修改任何其他核心文件。)*
|
||||
|
||||
---
|
||||
|
||||
## 建议创建/修改的文件结构 (Recommended Directory Structure)
|
||||
|
||||
当新增一套定制的业务接口(例如名为 `custom` 的业务模块)时,建议采用以下标准文件结构:
|
||||
所有标准插件与下游定制插件,**统一以 `backend/downstream/plugins/custom_example` 为基准模板**,严格采用物理子包隔离的分层架构:
|
||||
|
||||
```text
|
||||
internal/
|
||||
├── router/
|
||||
│ ├── root/
|
||||
│ │ └── custom.go # [修改] 若为根路径 API,在此处注册,将路由委派给 apps/custom
|
||||
│ └── v1/
|
||||
│ └── custom.go # [修改] 若为 v1 API,在此处注册,将路由委派给 apps/custom
|
||||
└── apps/
|
||||
└── custom/
|
||||
├── routers.go # [新建] HTTP Handlers (Gin),负责参数绑定、校验与响应
|
||||
├── logics.go # [新建] 业务逻辑层:承载模块内闭环的纯 Go 业务逻辑,不依赖 gin.Context
|
||||
└── errs.go # [新建] 存放模块特有的业务错误常量定义(可选)
|
||||
backend/openflare/plugins/<name>/ (或 backend/plugins/domain/<name>/)
|
||||
├── plugin.go # 插件根入口:实现 core.Plugin,装配各子包并向 Cordis 注册
|
||||
│
|
||||
├── consts/ # package consts:常量、配置键名与错误码定义
|
||||
│ └── consts.go
|
||||
│
|
||||
├── controller/ # package controller:HTTP 控制器与路由声明 (参数绑定、会话获取、信封响应)
|
||||
│ └── hello/ # 业务分组/实体子包
|
||||
│ └── hello.go # 接口处理 Handler(直接以业务命名,禁止 controller_hello.go)
|
||||
│
|
||||
├── service/ # package service:业务逻辑层(用例编排、事务控制、事件发布)
|
||||
│ └── order.go # 订单业务用例实现(纯 Go 逻辑,禁止依赖 *gin.Context)
|
||||
│
|
||||
├── dao/ # package dao:数据访问持久化层 DAL (GORM CRUD、SQL 转义防注入)
|
||||
│ └── order.go # 订单数据访问实现(直接以业务命名,禁止 dao_order.go)
|
||||
│
|
||||
├── model/ # package model:纯数据实体与 DTO(无外部依赖)
|
||||
│ ├── entity/ # 数据库映射实体 (TableName() 带插件专属前缀)
|
||||
│ │ └── order.go
|
||||
│ └── do/ # 请求 Request DTO 与响应 Response DTO、领域对象
|
||||
│ └── order.go
|
||||
│
|
||||
└── migrations/ # 专属嵌入式 Goose SQL 双方言迁移脚本 (//go:embed)
|
||||
├── postgres/ # PostgreSQL 迁移脚本
|
||||
└── sqlite/ # SQLite 迁移脚本
|
||||
```
|
||||
> ⚠️ **严禁**:严禁在根目录平铺 `handlers_*.go`、`service_*.go`、`dao_*.go` 等前缀文件,子包内文件直接按业务实体命名。严格约束 `controller -> service -> dao -> model` 单向依赖。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -14,10 +14,9 @@ description: "Wavelet 项目专用:当需要开发或接入新的系统通知
|
||||
Wavelet 的消息推送机制采用了**元数据驱动 + 统一触发器 + 异步任务派发**的解耦设计,其分层及职责划分如下:
|
||||
|
||||
| 目录/包名 | 职责定位 | 包含内容与设计细节 |
|
||||
| :--- | :--- | :--- |
|
||||
| **`pkg/push/`** | 推送基础设施层 | 静态定义、不依赖系统数据库和任何框架。定义了统一接口 `Pusher`、单例 `PusherPool` 和多实现(Lark, Webhook, Email 等),提供配置验证及发送功能。 |
|
||||
| **`internal/apps/admin/push/`** | 通知服务与后台任务层 | 包含以下核心文件:<br>1. [events.go](file:///Users/ryan/DEV/Go/Wavelet/internal/apps/admin/push/events.go):定义通知事件的结构模型(`NotificationMessage`, `EventMetadata`)、内置事件的动态注册中心(`BuiltInEvents` 及 `RegisterBuiltInEvent` 函数)以及统一触发器类 `EventTrigger`(包括其底层的派发引擎逻辑)。<br>2. [tasks.go](file:///Users/ryan/DEV/Go/Wavelet/internal/apps/admin/push/tasks.go):定义 Asynq 后台异步发送任务、处理器 `PushHandler` 及其校验逻辑,并记录推送历史审计。<br>3. [routers.go](file:///Users/ryan/DEV/Go/Wavelet/internal/apps/admin/push/routers.go):管理端接口,负责获取事件配置列表和更新配置。 |
|
||||
| **`internal/apps/admin/push/custom_events/`** | 自定义通知事件包 | 事件元数据定义与 push 侧处理逻辑;**一个 Go 文件代表一个事件**。在 [register.go](file:///Users/ryan/DEV/Go/Wavelet/internal/apps/admin/push/custom_events/register.go) 统一装配,禁止 `init()` 副作用。 |
|
||||
| **`backend/plugins/domain/msg_gateway/push/`** | 推送基础设施层 | 静态定义、不依赖系统数据库和任何框架。定义了统一接口 `Pusher` 和多实现(Lark, Webhook, Email 等),提供配置验证及发送功能。 |
|
||||
| **`internal/apps/admin/push/`** | 通知服务与后台任务层 | 包含以下核心文件:<br>1. `events.go`:定义通知事件的结构模型(`NotificationMessage`, `EventMetadata`)、内置事件的动态注册中心(`BuiltInEvents` 及 `RegisterBuiltInEvent` 函数)以及统一触发器类 `EventTrigger`(包括其底层的派发引擎逻辑)。<br>2. `tasks.go`:定义 Asynq 后台异步发送任务、处理器 `PushHandler` 及其校验逻辑,并记录推送历史审计。<br>3. `routers.go`:管理端接口,负责获取事件配置列表和更新配置。 |
|
||||
| **`internal/apps/admin/push/custom_events/`** | 自定义通知事件包 | 事件元数据定义与 push 侧处理逻辑;**一个 Go 文件代表一个事件**。在 `register.go` 统一装配,禁止 `init()` 副作用。 |
|
||||
| **`internal/listener/`** | 域事件分发层 | 核心域发射事件(如 `EmitAdminLoggedIn`),push 在 bootstrap 阶段通过 `OnAdminLoggedIn` 订阅,避免 auth/user 直接依赖 push。 |
|
||||
| **`internal/platform/bootstrap/`** | 应用装配根 | `RegisterPushDomainEvents()` 调用 `custom_events.Register()`;`Init` 中执行 `SyncEvents` 将内置事件元数据同步到数据库。 |
|
||||
| **数据库审计表** | 状态与历史审计 | `w_push_events` 存放每个通知事件的启用状态、启用渠道、发送目标和自定义渲染模板。<br>`w_push_histories` 存放消息发送记录用于审计。 |
|
||||
|
||||
@@ -18,7 +18,7 @@ permissions:
|
||||
|
||||
env:
|
||||
IMAGE_NAME: openflare-agent
|
||||
DOCKERFILE: docker/Dockerfile.agent
|
||||
DOCKERFILE: manifest/docker/Dockerfile.agent
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
@@ -18,7 +18,7 @@ permissions:
|
||||
|
||||
env:
|
||||
IMAGE_NAME: openflare-relay
|
||||
DOCKERFILE: docker/Dockerfile.relay
|
||||
DOCKERFILE: manifest/docker/Dockerfile.relay
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
@@ -24,7 +24,7 @@ permissions:
|
||||
|
||||
env:
|
||||
IMAGE_NAME: openflare
|
||||
DOCKERFILE: docker/Dockerfile
|
||||
DOCKERFILE: manifest/docker/Dockerfile
|
||||
|
||||
jobs:
|
||||
# Resolve version / registries once. No checkout: triggers alone determine the tag.
|
||||
|
||||
@@ -18,7 +18,7 @@ permissions:
|
||||
|
||||
env:
|
||||
IMAGE_NAME: openflared
|
||||
DOCKERFILE: docker/Dockerfile.flared
|
||||
DOCKERFILE: manifest/docker/Dockerfile.flared
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
@@ -26,7 +26,7 @@ env:
|
||||
LICENSE
|
||||
README.md
|
||||
README_zh.md
|
||||
config.example.yaml
|
||||
manifest/config/config.default.yaml
|
||||
DEPLOYMENT_zh.md
|
||||
|
||||
permissions:
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
# config
|
||||
config.yaml
|
||||
manifest/config/config.yaml
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
@@ -94,3 +95,8 @@ backend/plugins/**/dist/
|
||||
backend/core/**/dist/
|
||||
backend/pkg/**/uploads/
|
||||
/backend/openflare/plugins/server/upload/filesrv/uploads/
|
||||
/backend/plugins/domain/upload/filesrv/uploads/
|
||||
/backend/plugins/domain/upload/task/uploads/
|
||||
/backend/data/
|
||||
/backend/plugins/drivers/driver_http/dist/
|
||||
/backend/uploads/
|
||||
|
||||
@@ -94,6 +94,41 @@ Strong success criteria let you loop independently. Weak criteria ("make it work
|
||||
- 上游暂缺而确属通用能力时,可先在本仓库实现并登记到 `backend/openflare/upstream-patches.md`
|
||||
(merge 上游后请确认补丁仍在),回流 Wavelet 后删除登记并重新 merge。
|
||||
|
||||
### Cordis 架构核心防线与分层规范
|
||||
- **微内核 (`backend/core/`)**:
|
||||
- 上下文总线(`Context`)、泛型依赖注入(`Container`)、生命周期编排(`Lifecycle`)、扩展点定义(`extpoints/`)与领域事件总线(`EventBus`)。
|
||||
- **严禁**包含任何具体业务逻辑,**严禁** import `gin`、`gorm`、`asynq` 等具体运行时依赖。
|
||||
- **服务契约 (`backend/core/contracts/`)**:
|
||||
- 跨插件通信的统一公开 Go Interface(如 `AuthService`、`UserService`、`CacheService`、`DBService`、`StorageService`)与公共 DTO。
|
||||
- **严禁**包含任何具体业务实现或 SQL 操作。
|
||||
- **自包含插件 (`backend/plugins/`)**:
|
||||
- 所有业务功能与驱动实现均以插件形式存在(`backend/plugins/drivers/`、`backend/plugins/infra/`、`backend/plugins/domain/` 或下游 `backend/openflare/plugins/`)。
|
||||
- 每个插件实现 `core.Plugin`(`Name() string` 与 `Apply(ctx *core.Context) error`)。
|
||||
- **统一插件分层架构与标准模板**:
|
||||
- **开发模板唯一基准**:所有插件统一以 `backend/downstream/plugins/custom_example` 为基准模板构建。
|
||||
- **物理子包隔离规范**:统一采用物理子包结构(`plugin.go`, `consts/`, `controller/`, `service/`, `dao/`, `model/` [含 `entity/`, `do/`], `migrations/` [含 `postgres/`, `sqlite/`])。**严禁在根包平铺 `handlers_*`、`service_*`、`dao_*` 等前缀文件**,子包内文件直接按业务实体命名(如 `hello.go`, `user.go`),严格约束 `controller -> service -> dao -> model` 单向依赖。
|
||||
- **插件通信与依赖隔离**:
|
||||
- **严禁跨包 import internal/私有实现**:插件之间严禁直接 import 对方具体实现包代码。
|
||||
- **单向服务契约调用**:调用方仅面向 `backend/core/contracts` 编程,在 `Apply` 中通过 `core.Provide[contracts.XxxService](ctx, svc)` 注册服务,通过 `core.Inject[contracts.XxxService](ctx)` 或 `ctx.Using(func(svc contracts.XxxService) { ... })` 声明式解析。
|
||||
- **事件总线广播**:状态联动与解耦通信统一通过强类型事件 `ctx.Events().Emit()` 广播,由感兴趣的插件通过 `ctx.Events().On()` 订阅,消除双向依赖与循环引用。
|
||||
- **扩展点自包含注册**:
|
||||
- **HTTP 路由与白名单机制**:
|
||||
- 插件自包含在 `Apply` 中通过 `ctx.Router().Group(...)` 挂载路由与中间件,禁止跨插件散落注册。
|
||||
- **白名单机制**:`driver_http` 与微内核扩展点提供路由白名单支持(`ctx.Router().RegisterWhitelist(patterns...)`),支持精确路径与通配符(如 `/api/v1/oauth/*`)。
|
||||
- **所有权主动声明**:认证域(`auth` 插件)与各业务插件必须在 `Apply` 中主动注册其公开/免鉴权接口(如 `/api/v1/user/login`、`/api/v1/oauth/callback`、`/api/v1/cap/*` 等)。
|
||||
- **鉴权中间件放行防线**:`auth` 提供的登录鉴权中间件(`LoginRequired`)必须先执行白名单匹配并自动放行,彻底杜绝免鉴权接口被全局或组级鉴权中间件误拦截(返回 401 Unauthorized)。
|
||||
- **异步与定时任务**:插件自包含在 `Apply` 中通过 `ctx.Task().Register(...)` 与 `ctx.Schedule().RegisterCron(...)` 声明。
|
||||
- **静态启动配置**:插件自包含在 `Apply` 中通过 `ctx.Config().Bind("<prefix>", &cfg)` 读取**自己声明**的配置,字段以 tag 表达来源:`config`(yaml 路径)、`env`(覆盖变量名)、`default`、`autoEnable`(该变量存在即置真)、`secret`(导出脱敏)。需要在 `Apply` 之前被门禁求值的键,必须在 `DeclareConfig()` 中提前声明并实现 `core.ConfigGatedPlugin`。新增基础设施 key 保持顶层命名(`redis.*`),插件私有配置归 `plugins.<name>.*`。**严禁**再造全局配置单例或在 `backend/pkg/` 读取配置。
|
||||
- **动态设置**:插件自包含在 `Apply` 中通过 `ctx.Settings().Register(core.SettingSchema{...})` 声明可热更新的管理台设置模式(与上面的静态启动配置分属两层)。
|
||||
- **数据迁移**:插件自包含在内部维护 `migrations/*.sql`,通过 `//go:embed` 打包并在 `Apply` 中通过 `ctx.Migrations().Register(pluginID, embedFS)` 注入。
|
||||
- **表单一所有者原则 (Single Owner Principle)**:
|
||||
- 每张数据表有且仅由一个所有者插件声明与维护(表名使用插件前缀如 `w_order_*`)。
|
||||
- 严禁插件 B 跨过所有者插件 A 直接 DDL/DML 旁路读写表 A,必须调用插件 A 暴露的 `contracts` 接口或订阅事件。
|
||||
- **平台服务复用**:
|
||||
- 文件摄取统一使用 `upload.Ingest` / `contracts.StorageService`,禁止绕过存储域直接操作底层 Bucket 或直写文件表。
|
||||
- 业务缓存统一使用 `ctx.Cache()`(`contracts.CacheService`)或标准缓存框架,禁止自研不带失效广播的本地 map。
|
||||
- 数据库操作通过 `ctx.DB()`(`contracts.DBService`)获取受事务与 Trace 保护的连接。
|
||||
|
||||
- 禁止删除 `frontend/node_modules`。
|
||||
- `backend/pkg/util/` 保持纯净:禁止导入 Gin、GORM、sessions 等 HTTP/Web/DB 框架(会话选项在 `backend/openflare/plugins/server/oauth/session.go`)。
|
||||
- 测试临时目录只用 `t.TempDir()`,禁止硬编码相对路径写源码树。
|
||||
|
||||
@@ -40,7 +40,7 @@ build-embedded:
|
||||
|
||||
code-check:
|
||||
@echo "==> Architecture guards..."
|
||||
@command -v rg >/dev/null 2>&1 || { echo 'error: rg (ripgrep) is required for architecture guards' >&2; exit 1; }
|
||||
scripts/check_cordis_architecture.sh
|
||||
@if rg -n 'db\.DB\(|db\.Redis' backend/openflare/plugins/server/kernel/model --glob '*.go' -g '!*_test.go' ; then \
|
||||
echo 'error: internal/model must not access db.DB or db.Redis (non-test code)' >&2; \
|
||||
exit 1; \
|
||||
@@ -108,7 +108,7 @@ cross-build:
|
||||
(version=$(or $(VERSION),dev))..."
|
||||
@mkdir -p bin
|
||||
docker build \
|
||||
--file docker/Dockerfile.cross \
|
||||
--file manifest/docker/Dockerfile.cross \
|
||||
--target export \
|
||||
--build-arg VERSION=$(or $(VERSION),dev) \
|
||||
--build-arg BUILD_DATE="$(shell date -u +'%Y-%m-%dT%H:%M:%SZ')" \
|
||||
|
||||
+4
-5
@@ -95,10 +95,10 @@ cd refreshing
|
||||
### 2. 配置环境
|
||||
|
||||
```bash
|
||||
cp config.example.yaml config.yaml
|
||||
cp manifest/config/config.default.yaml manifest/config/config.yaml
|
||||
```
|
||||
|
||||
编辑 `config.yaml`,配置数据库和 Redis。OIDC 认证源统一在管理后台的系统设置页面运行时配置。
|
||||
编辑 `manifest/config/config.yaml`,配置数据库和 Redis。OIDC 认证源统一在管理后台的系统设置页面运行时配置。
|
||||
|
||||
### 3. 初始化数据库
|
||||
|
||||
@@ -156,7 +156,7 @@ pnpm dev
|
||||
|
||||
## ⚙️ 配置说明
|
||||
|
||||
主要配置项(完整说明请参考 `config.example.yaml`):
|
||||
主要配置项(完整说明请参考 `manifest/config/config.default.yaml`):
|
||||
|
||||
| 配置项 | 说明 | 示例 |
|
||||
|--------|------|------|
|
||||
@@ -211,9 +211,8 @@ pnpm format
|
||||
```
|
||||
wavelet/
|
||||
├── main.go # 程序入口(委托给 internal/cmd)
|
||||
├── config.example.yaml # 配置模板
|
||||
├── Makefile # 常用命令(swagger、tidy、license、cross-build)
|
||||
├── docker/ # Docker 镜像构建文件(集成/前端/后端)
|
||||
├── manifest/ # 项目清单与编排:docker 镜像构建、deploy (k8s)、config 配置(默认/覆盖)
|
||||
├── docs/ # Swagger 自动生成文档
|
||||
├── frontend/ # Next.js 前端应用
|
||||
│ ├── app/ # App Router 页面
|
||||
|
||||
+3
-5
@@ -10,8 +10,7 @@ import (
|
||||
"Wavelet/openflare/plugins/server/migrate"
|
||||
"Wavelet/plugins/domain/admin"
|
||||
"Wavelet/plugins/domain/auth"
|
||||
"Wavelet/plugins/domain/cap"
|
||||
"Wavelet/plugins/domain/message_gateway"
|
||||
"Wavelet/plugins/domain/msg_gateway"
|
||||
"Wavelet/plugins/domain/risk_control"
|
||||
"Wavelet/plugins/domain/system"
|
||||
"Wavelet/plugins/domain/upload"
|
||||
@@ -104,15 +103,14 @@ func newOpenFlareApp(profile core.Profile, opts ...core.AppOption) *core.App {
|
||||
driver_inproc_cron.New(),
|
||||
)
|
||||
|
||||
// 3. Register all 8 domain business plugins (admin first to ensure schema and base config tables exist)
|
||||
// 3. Register all 7 domain business plugins (admin first to ensure schema and base config tables exist)
|
||||
app.Use(
|
||||
admin.New(),
|
||||
user.New(),
|
||||
auth.New(),
|
||||
message_gateway.New(),
|
||||
msg_gateway.New(),
|
||||
risk_control.New(),
|
||||
upload.New(),
|
||||
cap.New(),
|
||||
system.New(),
|
||||
)
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ func TestNewOpenFlareAppRegistersServerAndWaveletUser(t *testing.T) {
|
||||
for _, p := range app.Plugins() {
|
||||
names[p.Name()] = true
|
||||
}
|
||||
for _, n := range []string{"user", "auth", "cap", "admin", "server"} {
|
||||
for _, n := range []string{"user", "auth", "admin", "server"} {
|
||||
if !names[n] {
|
||||
t.Errorf("missing plugin %s", n)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package core
|
||||
|
||||
import "context"
|
||||
|
||||
type appContextKey struct{}
|
||||
|
||||
// WithAppContext attaches the micro-kernel Context to a standard context.Context
|
||||
// so request and worker handlers can Inject services without package-level setters.
|
||||
func WithAppContext(ctx context.Context, app *Context) context.Context {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
if app == nil {
|
||||
return ctx
|
||||
}
|
||||
return context.WithValue(ctx, appContextKey{}, app.Root())
|
||||
}
|
||||
|
||||
// AppContext extracts the micro-kernel Context from ctx, if present.
|
||||
func AppContext(ctx context.Context) *Context {
|
||||
if ctx == nil {
|
||||
return nil
|
||||
}
|
||||
if c, ok := ctx.(*Context); ok {
|
||||
return c
|
||||
}
|
||||
app, _ := ctx.Value(appContextKey{}).(*Context)
|
||||
return app
|
||||
}
|
||||
|
||||
// InjectFrom resolves T from ctx when it carries a micro-kernel Context
|
||||
// (*Context itself, or a value attached by WithAppContext).
|
||||
func InjectFrom[T any](ctx context.Context) (T, error) {
|
||||
var zero T
|
||||
app := AppContext(ctx)
|
||||
if app == nil {
|
||||
return zero, ErrNilContext
|
||||
}
|
||||
return Inject[T](app)
|
||||
}
|
||||
+55
-15
@@ -13,18 +13,20 @@ import (
|
||||
|
||||
// Container manages service registration and resolution using Go reflection and generics.
|
||||
type Container struct {
|
||||
mu sync.RWMutex
|
||||
parent *Container
|
||||
services map[reflect.Type]any
|
||||
listeners map[reflect.Type][]func(any)
|
||||
mu sync.RWMutex
|
||||
parent *Container
|
||||
services map[reflect.Type]any
|
||||
interfaceCache map[reflect.Type]any
|
||||
listeners map[reflect.Type][]func(any)
|
||||
}
|
||||
|
||||
// NewContainer creates a new IoC container instance with an optional parent container.
|
||||
func NewContainer(parent *Container) *Container {
|
||||
return &Container{
|
||||
parent: parent,
|
||||
services: make(map[reflect.Type]any),
|
||||
listeners: make(map[reflect.Type][]func(any)),
|
||||
parent: parent,
|
||||
services: make(map[reflect.Type]any),
|
||||
interfaceCache: make(map[reflect.Type]any),
|
||||
listeners: make(map[reflect.Type][]func(any)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +47,7 @@ func (c *Container) remove(targetType reflect.Type) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
delete(c.services, targetType)
|
||||
c.interfaceCache = make(map[reflect.Type]any)
|
||||
}
|
||||
|
||||
// Provide registers a typed service implementation into the Context hierarchy's root IoC container.
|
||||
@@ -88,6 +91,7 @@ func ProvideScoped[T any](ctx *Context, service T) {
|
||||
func (c *Container) provide(targetType reflect.Type, service any) {
|
||||
c.mu.Lock()
|
||||
c.services[targetType] = service
|
||||
c.interfaceCache = make(map[reflect.Type]any)
|
||||
|
||||
// Collect any matching listeners to invoke outside the lock
|
||||
var callbacks []func(any)
|
||||
@@ -131,17 +135,14 @@ func (c *Container) resolve(targetType reflect.Type) (any, error) {
|
||||
c.mu.RUnlock()
|
||||
return val, nil
|
||||
}
|
||||
c.mu.RUnlock()
|
||||
|
||||
// 2. Interface assignment scan
|
||||
// 2. Interface assignment scan & cache
|
||||
if targetType.Kind() == reflect.Interface {
|
||||
for _, val := range c.services {
|
||||
if reflect.TypeOf(val).Implements(targetType) {
|
||||
c.mu.RUnlock()
|
||||
return val, nil
|
||||
}
|
||||
if val, found := c.resolveInterface(targetType); found {
|
||||
return val, nil
|
||||
}
|
||||
}
|
||||
c.mu.RUnlock()
|
||||
|
||||
// 3. Fallback to parent container
|
||||
if c.parent != nil {
|
||||
@@ -151,6 +152,35 @@ func (c *Container) resolve(targetType reflect.Type) (any, error) {
|
||||
return nil, fmt.Errorf("%w: %v", ErrServiceNotFound, targetType)
|
||||
}
|
||||
|
||||
func (c *Container) resolveInterface(targetType reflect.Type) (any, bool) {
|
||||
c.mu.RLock()
|
||||
if val, ok := c.interfaceCache[targetType]; ok {
|
||||
c.mu.RUnlock()
|
||||
return val, true
|
||||
}
|
||||
|
||||
var matched any
|
||||
for _, val := range c.services {
|
||||
if reflect.TypeOf(val).Implements(targetType) {
|
||||
matched = val
|
||||
break
|
||||
}
|
||||
}
|
||||
c.mu.RUnlock()
|
||||
|
||||
if matched == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
if c.interfaceCache == nil {
|
||||
c.interfaceCache = make(map[reflect.Type]any)
|
||||
}
|
||||
c.interfaceCache[targetType] = matched
|
||||
c.mu.Unlock()
|
||||
return matched, true
|
||||
}
|
||||
|
||||
// MustInject resolves a service of type T or panics if the service is not found.
|
||||
func MustInject[T any](ctx *Context) T {
|
||||
s, err := Inject[T](ctx)
|
||||
@@ -201,13 +231,17 @@ func Using3[T1, T2, T3 any](ctx *Context, fn func(s1 T1, s2 T2, s3 T3)) error {
|
||||
|
||||
// When registers a reactive hook that is called immediately if T is already provided,
|
||||
// or called as soon as T is provided in the future.
|
||||
//
|
||||
// Listeners are stored on the root container so they observe core.Provide, which
|
||||
// always writes to the root. Registering on a Fiber child container would miss
|
||||
// services provided by plugins that load later.
|
||||
func When[T any](ctx *Context, fn func(s T)) {
|
||||
if ctx == nil {
|
||||
panic("core: nil context provided to When")
|
||||
}
|
||||
|
||||
targetType := reflect.TypeFor[T]()
|
||||
c := ctx.Container()
|
||||
c := ctx.Root().Container()
|
||||
|
||||
// If already ready, execute immediately
|
||||
if s, err := Inject[T](ctx); err == nil {
|
||||
@@ -223,3 +257,9 @@ func When[T any](ctx *Context, fn func(s T)) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Bind is When with a name that matches plugin wiring: fill a dependency as
|
||||
// soon as the root container provides it.
|
||||
func Bind[T any](ctx *Context, fn func(s T)) {
|
||||
When(ctx, fn)
|
||||
}
|
||||
|
||||
@@ -313,6 +313,46 @@ func TestContextReactiveWhen(t *testing.T) {
|
||||
assert.True(t, immediateCalled)
|
||||
}
|
||||
|
||||
func TestWhenObservesProvideFromForkedFiberContext(t *testing.T) {
|
||||
root := core.NewContext(context.Background())
|
||||
adminFiber := root.Fork()
|
||||
lateFiber := root.Fork()
|
||||
|
||||
var got atomic.Bool
|
||||
core.When[SampleService](adminFiber, func(s SampleService) {
|
||||
if s != nil {
|
||||
got.Store(true)
|
||||
}
|
||||
})
|
||||
assert.False(t, got.Load())
|
||||
|
||||
core.Provide[SampleService](lateFiber, &sampleServiceImpl{})
|
||||
assert.True(t, got.Load(), "When on a Fiber child must observe Provide on the root")
|
||||
}
|
||||
|
||||
func TestBindIsWhen(t *testing.T) {
|
||||
ctx := core.NewContext(context.Background())
|
||||
var called atomic.Bool
|
||||
core.Bind[SampleService](ctx, func(s SampleService) {
|
||||
called.Store(true)
|
||||
})
|
||||
core.Provide[SampleService](ctx, &sampleServiceImpl{})
|
||||
assert.True(t, called.Load())
|
||||
}
|
||||
|
||||
func TestInjectFromAppContext(t *testing.T) {
|
||||
app := core.NewContext(context.Background())
|
||||
core.Provide[SampleService](app, &sampleServiceImpl{prefix: "Hi:"})
|
||||
|
||||
req := core.WithAppContext(context.Background(), app)
|
||||
svc, err := core.InjectFrom[SampleService](req)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Hi: Ada", svc.Greet("Ada"))
|
||||
|
||||
_, err = core.InjectFrom[SampleService](context.Background())
|
||||
assert.ErrorIs(t, err, core.ErrNilContext)
|
||||
}
|
||||
|
||||
func TestContextDisposerLifecycle(t *testing.T) {
|
||||
parent := core.NewContext(context.Background())
|
||||
child := parent.Fork()
|
||||
@@ -531,10 +571,16 @@ func TestContext_ScopedExtpoints_RevertibleEffects(t *testing.T) {
|
||||
root := core.NewContext(context.Background())
|
||||
child := root.Fork()
|
||||
|
||||
// Register route, task, schedule, setting, event on child
|
||||
// Register route, task, schedule, setting, event, middleware, whitelist on child
|
||||
child.Router().GET("/test-route", func() {})
|
||||
assert.Equal(t, 1, len(root.Router().Routes()))
|
||||
|
||||
child.Router().Use("scoped_middleware")
|
||||
assert.Equal(t, 1, len(root.Router().Middlewares()))
|
||||
|
||||
child.Router().RegisterWhitelist("/api/v1/scoped/*")
|
||||
assert.True(t, root.Router().IsWhitelisted("/api/v1/scoped/test"))
|
||||
|
||||
child.Tasks().Register("test:task", func() {})
|
||||
assert.Equal(t, 1, len(root.Tasks().Tasks()))
|
||||
|
||||
@@ -553,8 +599,48 @@ func TestContext_ScopedExtpoints_RevertibleEffects(t *testing.T) {
|
||||
|
||||
// All child effects should be cleanly revoked in LIFO order
|
||||
assert.Equal(t, 0, len(root.Router().Routes()))
|
||||
assert.Equal(t, 0, len(root.Router().Middlewares()))
|
||||
assert.False(t, root.Router().IsWhitelisted("/api/v1/scoped/test"))
|
||||
assert.Equal(t, 0, len(root.Tasks().Tasks()))
|
||||
assert.Equal(t, 0, len(root.Schedules().Schedules()))
|
||||
assert.Equal(t, 0, len(root.Settings().Schemas()))
|
||||
assert.Equal(t, 0, root.Events().Listeners("test:event"))
|
||||
}
|
||||
|
||||
func TestContainer_InterfaceResolutionCache(t *testing.T) {
|
||||
ctx := core.NewContext(context.Background())
|
||||
svc := &sampleServiceImpl{prefix: "Cached:"}
|
||||
|
||||
core.Provide[SampleService](ctx, svc)
|
||||
|
||||
// 1. Initial resolution populates interfaceCache
|
||||
res1, err := core.Inject[SampleService](ctx)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Cached: Alice", res1.Greet("Alice"))
|
||||
|
||||
// 2. Subsequent resolutions hit interfaceCache
|
||||
res2, err := core.Inject[SampleService](ctx)
|
||||
require.NoError(t, err)
|
||||
assert.Same(t, res1, res2)
|
||||
|
||||
// 3. Concurrent lookups
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 20; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
r, e := core.Inject[SampleService](ctx)
|
||||
assert.NoError(t, e)
|
||||
assert.Equal(t, "Cached: Bob", r.Greet("Bob"))
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// 4. Overriding/providing another service invalidates cache
|
||||
svc2 := &sampleServiceImpl{prefix: "Updated:"}
|
||||
core.Provide[SampleService](ctx, svc2)
|
||||
|
||||
res3, err := core.Inject[SampleService](ctx)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Updated: Alice", res3.Greet("Alice"))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package contracts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SystemConfigDTO represents a system configuration key-value entry.
|
||||
type SystemConfigDTO struct {
|
||||
Key string `json:"key"`
|
||||
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"`
|
||||
}
|
||||
|
||||
// SystemConfigService defines the unified contract for querying and mutating system configurations.
|
||||
type SystemConfigService interface {
|
||||
GetByKey(ctx context.Context, key string) (SystemConfigDTO, error)
|
||||
ListByKeys(ctx context.Context, keys []string) (map[string]SystemConfigDTO, error)
|
||||
ListVisible(ctx context.Context) ([]SystemConfigDTO, error)
|
||||
ListByType(ctx context.Context, configType string) ([]SystemConfigDTO, error)
|
||||
GetIntByKey(ctx context.Context, key string) (int, error)
|
||||
GetBoolByKey(ctx context.Context, key string) (bool, error)
|
||||
SaveOrUpdate(ctx context.Context, key, value string) error
|
||||
InvalidateCache(ctx context.Context, key string) error
|
||||
InvalidateAllCaches(ctx context.Context) error
|
||||
}
|
||||
@@ -5,8 +5,10 @@ package contracts
|
||||
|
||||
import "context"
|
||||
|
||||
// PublicConfigProvider supplies the payload for GET /api/v1/config/public
|
||||
// when a downstream plugin replaces Wavelet's default {configs, app} JSON.
|
||||
// PublicConfigProvider supplies GET /api/v1/config/public.
|
||||
// The owner of w_system_configs (admin) must provide this. The payload is a
|
||||
// flat key/value map of visibility=1 rows; the frontend reads keys such as
|
||||
// cap_login_enabled directly off data.
|
||||
type PublicConfigProvider interface {
|
||||
PublicConfig(ctx context.Context) (any, error)
|
||||
PublicConfig(ctx context.Context) (map[string]string, error)
|
||||
}
|
||||
|
||||
@@ -151,3 +151,8 @@ type UserDeletedEvent struct {
|
||||
CurrentUserID uint64 `json:"current_user_id,string"`
|
||||
TargetUserID uint64 `json:"target_user_id,string"`
|
||||
}
|
||||
|
||||
// SystemCleanupEvent fires when a periodic system cleanup is triggered.
|
||||
type SystemCleanupEvent struct {
|
||||
TriggeredAt string `json:"triggered_at"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package contracts defines unified service interfaces and DTOs for cross-plugin communication.
|
||||
package contracts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Rate specifies a rate limit of Limit events permitted within a Period.
|
||||
type Rate struct {
|
||||
Limit int `json:"limit"`
|
||||
Period time.Duration `json:"period"`
|
||||
}
|
||||
|
||||
// RateLimitResult holds the outcome of a rate limit check.
|
||||
type RateLimitResult struct {
|
||||
Allowed bool `json:"allowed"`
|
||||
Remaining int `json:"remaining"`
|
||||
ResetAfter time.Duration `json:"reset_after"`
|
||||
RetryAfter time.Duration `json:"retry_after"`
|
||||
}
|
||||
|
||||
// LimiterService defines the rate limiting service contract for cross-plugin communication.
|
||||
type LimiterService interface {
|
||||
// Allow checks whether 1 event for the given key is permitted under the specified rate.
|
||||
Allow(ctx context.Context, key string, rate Rate) (*RateLimitResult, error)
|
||||
|
||||
// AllowN checks whether n events for the given key are permitted under the specified rate.
|
||||
AllowN(ctx context.Context, key string, rate Rate, n int) (*RateLimitResult, error)
|
||||
|
||||
// Reset clears the rate limit state for the given key.
|
||||
Reset(ctx context.Context, key string) error
|
||||
}
|
||||
@@ -6,6 +6,7 @@ package contracts
|
||||
|
||||
import "context"
|
||||
|
||||
// PushNotificationTemplate defines notification message template payload.
|
||||
type PushNotificationTemplate struct {
|
||||
Title string
|
||||
Content string
|
||||
@@ -13,6 +14,7 @@ type PushNotificationTemplate struct {
|
||||
Ext map[string]any
|
||||
}
|
||||
|
||||
// PushEventMeta defines metadata for a system push event.
|
||||
type PushEventMeta struct {
|
||||
Key string
|
||||
Name string
|
||||
@@ -20,6 +22,7 @@ type PushEventMeta struct {
|
||||
DefaultTemplate PushNotificationTemplate
|
||||
}
|
||||
|
||||
// PushRegistry defines the interface for registering built-in events.
|
||||
type PushRegistry interface {
|
||||
RegisterBuiltInEvent(meta PushEventMeta)
|
||||
SyncEvents(ctx context.Context) error
|
||||
|
||||
@@ -43,6 +43,12 @@ type TaskResultDTO struct {
|
||||
Detail any `json:"detail,omitempty"`
|
||||
}
|
||||
|
||||
// TaskHandler is the preferred background task handler. Drivers invoke Execute
|
||||
// and persist Message/Detail onto the execution record.
|
||||
type TaskHandler interface {
|
||||
Execute(ctx context.Context, payload []byte) (*TaskResultDTO, error)
|
||||
}
|
||||
|
||||
// TaskExecutionDTO represents a single task execution record.
|
||||
type TaskExecutionDTO struct {
|
||||
ID uint64 `json:"id,string"`
|
||||
@@ -65,6 +71,14 @@ type TaskExecutionDTO struct {
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// Canonical triggered_by values persisted on task executions and shown in admin UI.
|
||||
const (
|
||||
TaskTriggerSystem = "system"
|
||||
TaskTriggerManual = "manual"
|
||||
TaskTriggerRetry = "retry"
|
||||
TaskTriggerSchedule = "schedule"
|
||||
)
|
||||
|
||||
// TaskService defines the unified contract for dispatching and tracking background tasks.
|
||||
type TaskService interface {
|
||||
Dispatch(ctx context.Context, taskType string, payload []byte, triggeredBy string) (string, error)
|
||||
@@ -76,4 +90,5 @@ type TaskService interface {
|
||||
AppendLog(ctx context.Context, format string, args ...any)
|
||||
ListExecutions(ctx context.Context, taskType, status string, page, pageSize int) ([]TaskExecutionDTO, int64, error)
|
||||
GetExecution(ctx context.Context, id uint64) (*TaskExecutionDTO, error)
|
||||
GetExecutionByTaskID(ctx context.Context, taskID string) (*TaskExecutionDTO, error)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package contracts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
// UploadMetadataDTO represents upload metadata JSON.
|
||||
type UploadMetadataDTO struct {
|
||||
Width int `json:"width,omitempty"`
|
||||
Height int `json:"height,omitempty"`
|
||||
Duration float64 `json:"duration,omitempty"`
|
||||
OriginalMime string `json:"original_mime,omitempty"`
|
||||
UserAgent string `json:"user_agent,omitempty"`
|
||||
ClientIP string `json:"client_ip,omitempty"`
|
||||
Bucket string `json:"bucket,omitempty"`
|
||||
Extra map[string]any `json:"extra,omitempty"`
|
||||
}
|
||||
|
||||
// Value implements the driver.Valuer interface for database serialization.
|
||||
func (m UploadMetadataDTO) Value() (driver.Value, error) {
|
||||
return json.Marshal(m)
|
||||
}
|
||||
|
||||
// Scan implements the sql.Scanner interface for database deserialization.
|
||||
func (m *UploadMetadataDTO) Scan(value any) error {
|
||||
if value == nil {
|
||||
*m = UploadMetadataDTO{}
|
||||
return nil
|
||||
}
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
return json.Unmarshal(v, m)
|
||||
case string:
|
||||
return json.Unmarshal([]byte(v), m)
|
||||
default:
|
||||
return fmt.Errorf("cannot scan type %T into UploadMetadataDTO", value)
|
||||
}
|
||||
}
|
||||
|
||||
// UploadDTO represents an uploaded file record.
|
||||
type UploadDTO struct {
|
||||
ID uint64 `json:"id"`
|
||||
UserID uint64 `json:"user_id"`
|
||||
FileName string `json:"file_name"`
|
||||
FilePath string `json:"file_path"`
|
||||
MimeType string `json:"mime_type"`
|
||||
Size int64 `json:"size"`
|
||||
Hash string `json:"hash"`
|
||||
Status string `json:"status"`
|
||||
Type string `json:"type"`
|
||||
Metadata UploadMetadataDTO `json:"metadata"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// OpenedUploadDTO encapsulates the retrieved object stream and its metadata.
|
||||
type OpenedUploadDTO struct {
|
||||
Upload UploadDTO
|
||||
Body io.ReadCloser
|
||||
ContentType string
|
||||
ContentLength int64
|
||||
}
|
||||
|
||||
// UploadService defines the unified contract for managed file uploads and media entities.
|
||||
type UploadService interface {
|
||||
GetByID(ctx context.Context, id uint64) (*UploadDTO, error)
|
||||
OpenStoredUpload(ctx context.Context, id uint64) (*OpenedUploadDTO, error)
|
||||
Remove(ctx context.Context, id uint64) error
|
||||
RemoveOwned(ctx context.Context, id uint64, userID uint64) error
|
||||
FindByHash(ctx context.Context, hash string, size int64) (*UploadDTO, error)
|
||||
RebuildStats(ctx context.Context) error
|
||||
}
|
||||
+18
-9
@@ -283,7 +283,8 @@ func (b *EventBus) Waterfall(ctx context.Context, topic string, initialPayload a
|
||||
}
|
||||
|
||||
// Parallel executes all subscribers of the topic concurrently in separate goroutines.
|
||||
// It waits for all handlers to complete and collects any errors via errors.Join.
|
||||
// It waits for all handlers to complete or returns immediately if ctx is cancelled/timed out,
|
||||
// collecting any handler errors via errors.Join.
|
||||
//
|
||||
//nolint:contextcheck
|
||||
func (b *EventBus) Parallel(ctx context.Context, topic string, payload any) error {
|
||||
@@ -325,17 +326,25 @@ func (b *EventBus) Parallel(ctx context.Context, topic string, payload any) erro
|
||||
}(l)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(errCh)
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(done)
|
||||
}()
|
||||
|
||||
var errs []error
|
||||
for err := range errCh {
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
select {
|
||||
case <-done:
|
||||
close(errCh)
|
||||
var errs []error
|
||||
for err := range errCh {
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// Serial executes subscribers strictly in sequence.
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -368,6 +369,25 @@ func TestEventBusParallel(t *testing.T) {
|
||||
assert.Equal(t, int64(20), counter.Load())
|
||||
}
|
||||
|
||||
func TestEventBusParallelContextTimeout(t *testing.T) {
|
||||
bus := core.NewEventBus()
|
||||
|
||||
bus.On("test:timeout", func(ctx context.Context) error {
|
||||
select {
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
err := bus.Parallel(ctx, "test:timeout", nil)
|
||||
assert.ErrorIs(t, err, context.DeadlineExceeded)
|
||||
}
|
||||
|
||||
func TestEventBusSerial(t *testing.T) {
|
||||
bus := core.NewEventBus()
|
||||
|
||||
|
||||
@@ -181,7 +181,7 @@ func formatEntryValue(value any, secret bool) string {
|
||||
return ""
|
||||
}
|
||||
rv := reflect.ValueOf(value)
|
||||
if rv.Kind() == reflect.Ptr {
|
||||
if rv.Kind() == reflect.Pointer {
|
||||
if rv.IsNil() {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ func convertValue(raw any, typ reflect.Type) (any, error) {
|
||||
return convertSlice(raw, typ)
|
||||
case reflect.Struct:
|
||||
return convertStruct(raw, typ)
|
||||
case reflect.Ptr:
|
||||
case reflect.Pointer:
|
||||
return convertPointer(raw, typ)
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s is not a supported configuration type", ErrConfigType, typ)
|
||||
@@ -44,7 +44,7 @@ func convertValue(raw any, typ reflect.Type) (any, error) {
|
||||
// Nested pointers are rejected so configuration tags stay one level deep.
|
||||
func convertPointer(raw any, typ reflect.Type) (any, error) {
|
||||
elemType := typ.Elem()
|
||||
if elemType.Kind() == reflect.Ptr {
|
||||
if elemType.Kind() == reflect.Pointer {
|
||||
return nil, fmt.Errorf("%w: %s is not a supported configuration type", ErrConfigType, typ)
|
||||
}
|
||||
elem, err := convertValue(raw, elemType)
|
||||
|
||||
@@ -80,12 +80,12 @@ func TestRouterExtension(t *testing.T) {
|
||||
for _, route := range routes {
|
||||
if route.Method == "GET" && route.Path == "/api/v1/orders" {
|
||||
foundOrderGet = true
|
||||
assert.Equal(t, []any{mGlobal, mAPI, "api_extra_middleware"}, route.Middlewares)
|
||||
assert.Equal(t, []any{mAPI, "api_extra_middleware"}, route.Middlewares)
|
||||
assert.Equal(t, []any{hList}, route.Handlers)
|
||||
}
|
||||
if route.Method == "PUT" && route.Path == "/api/v1/admin/users/:id" {
|
||||
foundUserPut = true
|
||||
assert.Equal(t, []any{mGlobal, mAPI, "api_extra_middleware", mAdmin}, route.Middlewares)
|
||||
assert.Equal(t, []any{mAPI, "api_extra_middleware", mAdmin}, route.Middlewares)
|
||||
assert.Equal(t, []any{hUserPut}, route.Handlers)
|
||||
}
|
||||
}
|
||||
@@ -93,6 +93,21 @@ func TestRouterExtension(t *testing.T) {
|
||||
assert.True(t, foundUserPut)
|
||||
}
|
||||
|
||||
func TestRouterGlobalMiddlewareIsNotSnapshottedOntoRoutes(t *testing.T) {
|
||||
r := extpoints.NewRouterRegistry()
|
||||
r.GET("/before", "handler")
|
||||
r.Use("late_global")
|
||||
r.GET("/after", "handler")
|
||||
|
||||
for _, route := range r.Routes() {
|
||||
if len(route.Middlewares) != 0 {
|
||||
t.Errorf("route %s %s Middlewares = %v, want none (globals live on Router.Middlewares)",
|
||||
route.Method, route.Path, route.Middlewares)
|
||||
}
|
||||
}
|
||||
assert.Equal(t, []any{"late_global"}, r.Middlewares())
|
||||
}
|
||||
|
||||
func TestRouterWhitelist(t *testing.T) {
|
||||
r := extpoints.NewRouterRegistry()
|
||||
require.NotNil(t, r)
|
||||
@@ -221,10 +236,30 @@ func TestTaskExtension(t *testing.T) {
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, "order:cancel_timeout", task.Pattern)
|
||||
|
||||
byType, ok := tr.Get("cancel_timeout")
|
||||
assert.True(t, ok, "Get should resolve admin type identifier")
|
||||
assert.Equal(t, "order:cancel_timeout", byType.Pattern)
|
||||
|
||||
_, ok = tr.Get("unknown")
|
||||
assert.False(t, ok)
|
||||
}
|
||||
|
||||
func TestTaskRegisterRejectsNilHandler(t *testing.T) {
|
||||
tr := extpoints.NewTaskRegistry()
|
||||
assert.Panics(t, func() {
|
||||
tr.Register("broken:task", nil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTaskRegisterRejectsDuplicateType(t *testing.T) {
|
||||
tr := extpoints.NewTaskRegistry()
|
||||
handler := func(ctx context.Context, payload []byte) error { return nil }
|
||||
tr.Register("system:cleanup", handler, extpoints.WithTaskType("system_cleanup"))
|
||||
assert.Panics(t, func() {
|
||||
tr.Register("admin:system_cleanup", handler, extpoints.WithTaskType("system_cleanup"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestScheduleExtension(t *testing.T) {
|
||||
sr := extpoints.NewScheduleRegistry()
|
||||
require.NotNil(t, sr)
|
||||
@@ -344,7 +379,7 @@ func TestContextExtensionPointsIntegration(t *testing.T) {
|
||||
func TestExtensionPointsUnregister(t *testing.T) {
|
||||
ctx := core.NewContext(context.Background())
|
||||
|
||||
// 1. Router unregister
|
||||
// 1. Router unregister (routes, middlewares, whitelist)
|
||||
rd := ctx.Router().GET("/temp", "temp_handler")
|
||||
assert.Greater(t, rd.ID, uint64(0))
|
||||
assert.Len(t, ctx.Router().Routes(), 1)
|
||||
@@ -356,6 +391,20 @@ func TestExtensionPointsUnregister(t *testing.T) {
|
||||
assert.True(t, ctx.Router().UnregisterByID(rd2.ID))
|
||||
assert.Len(t, ctx.Router().Routes(), 0)
|
||||
|
||||
ctx.Router().Use("mw1")
|
||||
assert.Len(t, ctx.Router().Middlewares(), 1)
|
||||
if reg, ok := ctx.Router().(*extpoints.RouterRegistry); ok {
|
||||
ids := reg.UseWithID("mw2")
|
||||
assert.Len(t, ctx.Router().Middlewares(), 2)
|
||||
assert.True(t, ctx.Router().UnregisterMiddlewareByID(ids[0]))
|
||||
assert.Len(t, ctx.Router().Middlewares(), 1)
|
||||
}
|
||||
|
||||
ctx.Router().RegisterWhitelist("/api/v1/temp/*")
|
||||
assert.True(t, ctx.Router().IsWhitelisted("/api/v1/temp/item"))
|
||||
ctx.Router().UnregisterWhitelist("/api/v1/temp/*")
|
||||
assert.False(t, ctx.Router().IsWhitelisted("/api/v1/temp/item"))
|
||||
|
||||
// 2. Task unregister
|
||||
ctx.Task().Register("temp:task", "handler")
|
||||
assert.Len(t, ctx.Task().Tasks(), 1)
|
||||
|
||||
@@ -39,17 +39,26 @@ type RouterExtension interface {
|
||||
Middlewares() []any
|
||||
Unregister(method, path string) bool
|
||||
UnregisterByID(id uint64) bool
|
||||
UnregisterMiddlewareByID(id uint64) bool
|
||||
RegisterWhitelist(patterns ...string)
|
||||
UnregisterWhitelist(patterns ...string)
|
||||
Whitelist() []string
|
||||
IsWhitelisted(path string) bool
|
||||
}
|
||||
|
||||
// middlewareDefinition holds an assigned ID and handler for registered middleware.
|
||||
type middlewareDefinition struct {
|
||||
ID uint64
|
||||
Handler any
|
||||
}
|
||||
|
||||
// RouterRegistry implements RouterExtension as the root route and middleware collector.
|
||||
type RouterRegistry struct {
|
||||
mu sync.RWMutex
|
||||
nextID uint64
|
||||
nextMWID uint64
|
||||
routes []RouteDefinition
|
||||
middlewares []any
|
||||
middlewares []middlewareDefinition
|
||||
whitelist PathWhitelist
|
||||
}
|
||||
|
||||
@@ -60,17 +69,48 @@ func NewRouterRegistry() *RouterRegistry {
|
||||
|
||||
// Use registers global middlewares to the router.
|
||||
func (r *RouterRegistry) Use(middlewares ...any) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.middlewares = append(r.middlewares, middlewares...)
|
||||
r.UseWithID(middlewares...)
|
||||
}
|
||||
|
||||
// Middlewares returns a copy of registered root middlewares.
|
||||
// UseWithID registers global middlewares to the router and returns their assigned IDs.
|
||||
func (r *RouterRegistry) UseWithID(middlewares ...any) []uint64 {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
ids := make([]uint64, 0, len(middlewares))
|
||||
for _, mw := range middlewares {
|
||||
r.nextMWID++
|
||||
r.middlewares = append(r.middlewares, middlewareDefinition{
|
||||
ID: r.nextMWID,
|
||||
Handler: mw,
|
||||
})
|
||||
ids = append(ids, r.nextMWID)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// UnregisterMiddlewareByID removes a registered global middleware by its unique ID.
|
||||
func (r *RouterRegistry) UnregisterMiddlewareByID(id uint64) bool {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
for i, mw := range r.middlewares {
|
||||
if mw.ID == id {
|
||||
r.middlewares = append(r.middlewares[:i], r.middlewares[i+1:]...)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Middlewares returns a copy of registered root middleware handlers.
|
||||
func (r *RouterRegistry) Middlewares() []any {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
res := make([]any, len(r.middlewares))
|
||||
copy(res, r.middlewares)
|
||||
for i, mw := range r.middlewares {
|
||||
res[i] = mw.Handler
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -95,11 +135,12 @@ func (r *RouterRegistry) addRoute(method, fullPath string, handlers ...any) Rout
|
||||
|
||||
r.nextID++
|
||||
rd := RouteDefinition{
|
||||
ID: r.nextID,
|
||||
Method: strings.ToUpper(method),
|
||||
Path: fullPath,
|
||||
Handlers: handlers,
|
||||
Middlewares: append([]any(nil), r.middlewares...),
|
||||
ID: r.nextID,
|
||||
Method: strings.ToUpper(method),
|
||||
Path: fullPath,
|
||||
Handlers: handlers,
|
||||
// Global Router.Use middlewares are applied at HTTP Start from
|
||||
// Router.Middlewares(), so late-registered plugins still wrap earlier routes.
|
||||
}
|
||||
r.routes = append(r.routes, rd)
|
||||
return rd
|
||||
@@ -195,6 +236,11 @@ func (r *RouterRegistry) RegisterWhitelist(patterns ...string) {
|
||||
r.whitelist.Add(patterns...)
|
||||
}
|
||||
|
||||
// UnregisterWhitelist removes path patterns from the whitelist.
|
||||
func (r *RouterRegistry) UnregisterWhitelist(patterns ...string) {
|
||||
r.whitelist.Remove(patterns...)
|
||||
}
|
||||
|
||||
// Whitelist returns a copy of all registered whitelist path patterns.
|
||||
func (r *RouterRegistry) Whitelist() []string {
|
||||
return r.whitelist.Patterns()
|
||||
@@ -241,9 +287,7 @@ func (g *RouterGroup) addRoute(method, fullPath string, handlers ...any) RouteDe
|
||||
g.registry.mu.Lock()
|
||||
defer g.registry.mu.Unlock()
|
||||
|
||||
allMiddlewares := make([]any, 0, len(g.registry.middlewares)+len(g.middlewares))
|
||||
allMiddlewares = append(allMiddlewares, g.registry.middlewares...)
|
||||
allMiddlewares = append(allMiddlewares, g.middlewares...)
|
||||
allMiddlewares := append([]any(nil), g.middlewares...)
|
||||
|
||||
g.registry.nextID++
|
||||
rd := RouteDefinition{
|
||||
@@ -268,6 +312,11 @@ func (g *RouterGroup) UnregisterByID(id uint64) bool {
|
||||
return g.registry.UnregisterByID(id)
|
||||
}
|
||||
|
||||
// UnregisterMiddlewareByID removes a middleware by ID via the root registry.
|
||||
func (g *RouterGroup) UnregisterMiddlewareByID(id uint64) bool {
|
||||
return g.registry.UnregisterMiddlewareByID(id)
|
||||
}
|
||||
|
||||
// GET registers a GET route in this group.
|
||||
func (g *RouterGroup) GET(path string, handlers ...any) RouteDefinition {
|
||||
return g.Handle("GET", path, handlers...)
|
||||
@@ -332,6 +381,13 @@ func (g *RouterGroup) RegisterWhitelist(patterns ...string) {
|
||||
}
|
||||
}
|
||||
|
||||
// UnregisterWhitelist removes path patterns under this group prefix from the whitelist.
|
||||
func (g *RouterGroup) UnregisterWhitelist(patterns ...string) {
|
||||
for _, p := range patterns {
|
||||
g.registry.UnregisterWhitelist(joinPaths(g.prefix, p))
|
||||
}
|
||||
}
|
||||
|
||||
// Whitelist returns a copy of all registered whitelist path patterns.
|
||||
func (g *RouterGroup) Whitelist() []string {
|
||||
return g.registry.Whitelist()
|
||||
@@ -466,6 +522,27 @@ func (w *PathWhitelist) Replace(patterns ...string) {
|
||||
w.patterns = compiled
|
||||
}
|
||||
|
||||
// Remove removes matching patterns from the whitelist.
|
||||
func (w *PathWhitelist) Remove(patterns ...string) {
|
||||
if len(patterns) == 0 {
|
||||
return
|
||||
}
|
||||
targets := make(map[string]struct{}, len(patterns))
|
||||
for _, p := range patterns {
|
||||
targets[cleanPath(p)] = struct{}{}
|
||||
}
|
||||
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
filtered := w.patterns[:0]
|
||||
for _, p := range w.patterns {
|
||||
if _, remove := targets[p.raw]; !remove {
|
||||
filtered = append(filtered, p)
|
||||
}
|
||||
}
|
||||
w.patterns = filtered
|
||||
}
|
||||
|
||||
// Match reports whether path matches any registered pattern. Equivalent to calling
|
||||
// MatchPathPattern for every pattern, except the path is normalised and split once.
|
||||
func (w *PathWhitelist) Match(path string) bool {
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package extpoints
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestHandleRawPreservesTrailingSlash 验证 HandleRaw 能表达 /x 与 /x/ 两条不同路由,
|
||||
// 而 Handle 会归一化掉尾部斜杠(server 插件的 list 端点历史行为依赖这一点)。
|
||||
func TestHandleRawPreservesTrailingSlash(t *testing.T) {
|
||||
r := &RouterRegistry{}
|
||||
g := r.Group("/api/v1/nodes")
|
||||
@@ -19,9 +24,29 @@ func TestHandleRawPreservesTrailingSlash(t *testing.T) {
|
||||
t.Errorf("HandleRaw(\"/\") path = %q, want %q", slashed.Path, "/api/v1/nodes/")
|
||||
}
|
||||
if slashed.ID == slashless.ID {
|
||||
t.Error("HandleRaw must allocate its own route ID")
|
||||
t.Error("HandleRaw must allocate its own route ID so scoped teardown can unregister both")
|
||||
}
|
||||
if got := len(r.Routes()); got != 2 {
|
||||
t.Errorf("registry routes = %d, want 2", got)
|
||||
}
|
||||
if !r.UnregisterByID(slashed.ID) {
|
||||
t.Error("UnregisterByID(HandleRaw route) = false, want true")
|
||||
}
|
||||
if got := len(r.Routes()); got != 1 {
|
||||
t.Errorf("routes after unregister = %d, want 1", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegistryHandleRawKeepsAbsolutePath 根注册表上 HandleRaw 只做绝对化处理。
|
||||
func TestRegistryHandleRawKeepsAbsolutePath(t *testing.T) {
|
||||
r := &RouterRegistry{}
|
||||
if got := r.HandleRaw("GET", "/health/").Path; got != "/health/" {
|
||||
t.Errorf("path = %q, want %q", got, "/health/")
|
||||
}
|
||||
if got := r.HandleRaw("POST", "submit").Path; got != "/submit" {
|
||||
t.Errorf("path = %q, want %q", got, "/submit")
|
||||
}
|
||||
if got := r.BasePath(); got != "" {
|
||||
t.Errorf("registry BasePath() = %q, want empty", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ package extpoints
|
||||
|
||||
import (
|
||||
"Wavelet/core/contracts"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -230,10 +232,15 @@ func NewTaskRegistry() *TaskRegistry {
|
||||
}
|
||||
|
||||
// Register registers a task pattern and its handler with optional configuration.
|
||||
// A nil handler panics. A non-empty Type that is already used by another pattern panics.
|
||||
func (t *TaskRegistry) Register(pattern string, handler any, opts ...TaskOption) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
if isNilTaskHandler(handler) {
|
||||
panic(fmt.Sprintf("extpoints: nil handler for task pattern %q", pattern))
|
||||
}
|
||||
|
||||
td := TaskDefinition{
|
||||
Pattern: pattern,
|
||||
Handler: handler,
|
||||
@@ -245,8 +252,23 @@ func (t *TaskRegistry) Register(pattern string, handler any, opts ...TaskOption)
|
||||
opt(&td)
|
||||
}
|
||||
}
|
||||
if td.Type == "" {
|
||||
td.Type = pattern
|
||||
}
|
||||
|
||||
if _, exists := t.lookup[pattern]; exists {
|
||||
for _, item := range t.tasks {
|
||||
if item.Pattern == pattern {
|
||||
continue
|
||||
}
|
||||
if item.Type == td.Type {
|
||||
panic(fmt.Sprintf("extpoints: duplicate task type %q (patterns %q and %q)", td.Type, item.Pattern, pattern))
|
||||
}
|
||||
}
|
||||
|
||||
if existing, exists := t.lookup[pattern]; exists {
|
||||
if existing.Type != "" && existing.Type != pattern {
|
||||
delete(t.lookup, existing.Type)
|
||||
}
|
||||
for i, item := range t.tasks {
|
||||
if item.Pattern == pattern {
|
||||
t.tasks[i] = td
|
||||
@@ -258,13 +280,44 @@ func (t *TaskRegistry) Register(pattern string, handler any, opts ...TaskOption)
|
||||
}
|
||||
|
||||
t.lookup[pattern] = td
|
||||
if td.Type != pattern {
|
||||
t.lookup[td.Type] = td
|
||||
}
|
||||
}
|
||||
|
||||
func isNilTaskHandler(handler any) bool {
|
||||
if handler == nil {
|
||||
return true
|
||||
}
|
||||
v := reflect.ValueOf(handler)
|
||||
switch v.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer, reflect.UnsafePointer, reflect.Interface, reflect.Slice:
|
||||
return v.IsNil()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Unregister removes a registered task definition by its pattern.
|
||||
func (t *TaskRegistry) Unregister(pattern string) bool {
|
||||
return unregisterEntry(&t.mu, t.lookup, &t.tasks, pattern, func(item TaskDefinition) bool {
|
||||
return item.Pattern == pattern
|
||||
})
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
td, ok := t.lookup[pattern]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
delete(t.lookup, td.Pattern)
|
||||
if td.Type != "" && td.Type != td.Pattern {
|
||||
delete(t.lookup, td.Type)
|
||||
}
|
||||
filtered := t.tasks[:0]
|
||||
for _, item := range t.tasks {
|
||||
if item.Pattern != td.Pattern {
|
||||
filtered = append(filtered, item)
|
||||
}
|
||||
}
|
||||
t.tasks = filtered
|
||||
return true
|
||||
}
|
||||
|
||||
// Tasks returns a copy of all registered TaskDefinitions.
|
||||
@@ -276,7 +329,7 @@ func (t *TaskRegistry) Tasks() []TaskDefinition {
|
||||
return res
|
||||
}
|
||||
|
||||
// Get retrieves a task definition by its pattern.
|
||||
// Get retrieves a task definition by its pattern or admin type identifier.
|
||||
func (t *TaskRegistry) Get(pattern string) (TaskDefinition, bool) {
|
||||
t.mu.RLock()
|
||||
defer t.mu.RUnlock()
|
||||
|
||||
@@ -22,6 +22,19 @@ func newScopedRouterExtension(ctx *Context, underlying extpoints.RouterExtension
|
||||
}
|
||||
|
||||
func (s *scopedRouterExtension) Use(middlewares ...any) {
|
||||
if len(middlewares) == 0 {
|
||||
return
|
||||
}
|
||||
if reg, ok := s.underlying.(*extpoints.RouterRegistry); ok {
|
||||
ids := reg.UseWithID(middlewares...)
|
||||
s.ctx.OnDispose(func() error {
|
||||
for _, id := range ids {
|
||||
reg.UnregisterMiddlewareByID(id)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
s.underlying.Use(middlewares...)
|
||||
}
|
||||
|
||||
@@ -107,8 +120,22 @@ func (s *scopedRouterExtension) UnregisterByID(id uint64) bool {
|
||||
return s.underlying.UnregisterByID(id)
|
||||
}
|
||||
|
||||
func (s *scopedRouterExtension) UnregisterMiddlewareByID(id uint64) bool {
|
||||
return s.underlying.UnregisterMiddlewareByID(id)
|
||||
}
|
||||
|
||||
func (s *scopedRouterExtension) RegisterWhitelist(patterns ...string) {
|
||||
s.underlying.RegisterWhitelist(patterns...)
|
||||
if len(patterns) > 0 {
|
||||
s.ctx.OnDispose(func() error {
|
||||
s.underlying.UnregisterWhitelist(patterns...)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scopedRouterExtension) UnregisterWhitelist(patterns ...string) {
|
||||
s.underlying.UnregisterWhitelist(patterns...)
|
||||
}
|
||||
|
||||
func (s *scopedRouterExtension) Whitelist() []string {
|
||||
|
||||
+684
-639
File diff suppressed because it is too large
Load Diff
+684
-639
File diff suppressed because it is too large
Load Diff
+467
-437
File diff suppressed because it is too large
Load Diff
+122
-52
@@ -1,77 +1,147 @@
|
||||
# Downstream Custom Plugins
|
||||
# 下游插件开发指南 (Downstream Custom Plugins)
|
||||
|
||||
This directory is the designated location for downstream (deployment-specific) Cordis plugins.
|
||||
本目录为 Wavelet 下游业务定制插件(Deployment-specific plugins)的专属开发目录。
|
||||
|
||||
## Architecture
|
||||
所有插件开发均以标准模板 [`custom_example`](./plugins/custom_example) 为基准进行构建。
|
||||
|
||||
```
|
||||
---
|
||||
|
||||
## 目录结构
|
||||
|
||||
```text
|
||||
downstream/
|
||||
├── README.md
|
||||
└── plugins/
|
||||
└── custom_example/ # Example plugin — copy & rename to get started
|
||||
└── plugin.go
|
||||
└── custom_example/ # 标准插件开发基准模板(可直接复制并重命名开发新插件)
|
||||
├── plugin.go # 插件入口:实现 core.Plugin (Name & Apply)
|
||||
├── consts/ # 常量与错误码定义
|
||||
│ └── consts.go
|
||||
├── controller/ # 控制器层 (HTTP API 接口、参数绑定与信封响应)
|
||||
│ └── hello/
|
||||
│ └── hello.go
|
||||
├── service/ # 业务逻辑层 (用例编排、事务控制与领域事件)
|
||||
├── dao/ # 数据访问层 (GORM CRUD、SQL 防注入与转义)
|
||||
├── model/ # 数据模型与实体定义
|
||||
│ ├── do/ # Domain Object 领域对象与 DTO
|
||||
│ └── entity/ # 数据表映射实体 (TableName() 带专属前缀)
|
||||
└── migrations/ # Goose SQL 双方言独立数据库迁移
|
||||
├── postgres/ # PostgreSQL 迁移脚本
|
||||
└── sqlite/ # SQLite 迁移脚本
|
||||
```
|
||||
|
||||
Downstream plugins follow the same `core.Plugin` contract as platform plugins:
|
||||
---
|
||||
|
||||
```go
|
||||
type Plugin interface {
|
||||
Name() string
|
||||
Apply(ctx *core.Context) error
|
||||
}
|
||||
## 插件开发规范与分层职责
|
||||
|
||||
每个下游插件均需遵循标准分层架构(`controller -> service -> dao -> model`):
|
||||
|
||||
1. **`plugin.go` (插件装配入口)**:
|
||||
- 实现 `core.Plugin` 接口(`Name() string` 与 `Apply(ctx *core.Context) error`)。
|
||||
- 负责在 `Apply` 中注册路由组(`ctx.Router()`)、异步任务(`ctx.Task()`)、定时调度(`ctx.Schedule()`)与数据库迁移(`ctx.Migrations()`)。
|
||||
- 依赖注入统一使用 `core.Inject` 或 `ctx.Using` 获取平台服务(如 `contracts.AuthService`、`contracts.DBService`、`contracts.CacheService`)。
|
||||
|
||||
2. **`controller/` (控制器层 / Handler)**:
|
||||
- 负责 HTTP API 请求参数绑定(`c.ShouldBindJSON` / `c.ShouldBindQuery`)、用户会话获取(`oauth.GetCurrentUser`)。
|
||||
- 调用 Service 层处理业务,严禁直接包含复杂业务逻辑或直接执行 SQL 操作。
|
||||
- 统一使用 `response.OK` 或 `response.Abort*` 返回标准信封响应。
|
||||
|
||||
3. **`service/` (业务逻辑层)**:
|
||||
- 纯 Go 业务用例,方法入参首位统一为 `context.Context`。
|
||||
- 严禁依赖 `*gin.Context` 或 HTTP 相关对象,确保逻辑具备可移植性与可测试性。
|
||||
- 涉及数据修改可通过 `ctx.Events().Emit()` 发射强类型领域事件。
|
||||
|
||||
4. **`dao/` (数据访问层 / Repository)**:
|
||||
- 负责底层数据库交互,通过 `contracts.DBService` 获取受保护的 GORM 数据库句柄。
|
||||
- 模糊查询必须调用 `pkg/util.EscapeLike` 并显式声明 `ESCAPE '\\'` 防注入。
|
||||
- 遵循**表单一所有者原则**:严禁跨过其他所有者插件直读或修改其他插件的数据表。
|
||||
|
||||
5. **`model/` (模型与 DTO)**:
|
||||
- `model/entity/`:数据库表映射结构体,`TableName()` 必须带有插件专属前缀(如 `w_custom_*`)。
|
||||
- `model/do/`:业务领域对象、入参校验 Request DTO 与响应 Response DTO。
|
||||
|
||||
6. **`consts/` (常量与错误定义)**:
|
||||
- 定义插件内部常量、配置键名及驼峰式(camelCase)错误标识字符串。
|
||||
|
||||
7. **`migrations/` (双方言 SQL 迁移)**:
|
||||
- 包含 `postgres/` 与 `sqlite/` 双方言 Goose SQL 迁移文件,使用 `//go:embed` 打包并在 `Apply()` 中注册。
|
||||
|
||||
---
|
||||
|
||||
## 快速上手
|
||||
|
||||
### 1. 基于模板复制创建新插件
|
||||
|
||||
```bash
|
||||
cp -r backend/downstream/plugins/custom_example backend/downstream/plugins/my_plugin
|
||||
```
|
||||
|
||||
## Rules
|
||||
|
||||
1. **Naming**: Each plugin directory name becomes its import path and plugin ID (kebab-case recommended).
|
||||
2. **Dependencies**: Downstream plugins may import `core/`, `core/contracts/`, `pkg/`, and `plugins/infra/` packages from the platform. They MUST NOT import domain plugin internal packages — use `core.Inject[contracts.XxxService](ctx)` instead.
|
||||
3. **Registration**: Add your downstream plugin to `cmd/app.go` before the platform plugins or after, depending on which services it needs:
|
||||
```go
|
||||
// newWaveletApp in cmd/app.go
|
||||
app.Use(
|
||||
database.New(),
|
||||
cache.New(),
|
||||
logger.New(),
|
||||
storage.New(),
|
||||
// ... platform domain plugins ...
|
||||
custom_hello.New(), // your downstream plugin
|
||||
driver_http.New(),
|
||||
driver_asynq_worker.New(),
|
||||
driver_asynq_cron.New(),
|
||||
)
|
||||
```
|
||||
4. **Migration**: If your plugin needs database tables, embed SQL files in a `migrations/` directory and register via `ctx.Migrations().Register(...)` in `Apply()`.
|
||||
|
||||
## Quick Start
|
||||
### 2. 实现插件入口 (`plugin.go`)
|
||||
|
||||
```go
|
||||
package custom_example
|
||||
package my_plugin
|
||||
|
||||
import (
|
||||
"github.com/Rain-kl/Wavelet/core"
|
||||
"github.com/Rain-kl/Wavelet/core/contracts"
|
||||
"github.com/gin-gonic/gin"
|
||||
"Wavelet/core"
|
||||
"Wavelet/core/contracts"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Plugin struct{}
|
||||
|
||||
func New() *Plugin { return &Plugin{} }
|
||||
func New() *Plugin {
|
||||
return &Plugin{}
|
||||
}
|
||||
|
||||
func (p *Plugin) Name() string { return "custom_example" }
|
||||
func (p *Plugin) Name() string {
|
||||
return "my_plugin"
|
||||
}
|
||||
|
||||
func (p *Plugin) Apply(ctx *core.Context) error {
|
||||
// Example: register a route that uses AuthService
|
||||
var authSvc contracts.AuthService
|
||||
if err := ctx.Using(func(svc contracts.AuthService) { authSvc = svc }); err != nil {
|
||||
return err
|
||||
}
|
||||
// 通过容器解析认证服务
|
||||
var authSvc contracts.AuthService
|
||||
if err := core.Using[contracts.AuthService](ctx, func(svc contracts.AuthService) { authSvc = svc }); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
g := ctx.Router().Group("/api/v1/custom", authSvc.RequireAuthMiddleware().(gin.HandlerFunc))
|
||||
g.GET("/hello", func(c *gin.Context) {
|
||||
user, _ := authSvc.GetCurrentUser(c.Request.Context())
|
||||
c.JSON(200, gin.H{"message": "Hello " + user.Username})
|
||||
})
|
||||
// 注册带鉴权中间件的路由组
|
||||
g := ctx.Router().Group("/api/v1/my-plugin", authSvc.RequireAuthMiddleware().(gin.HandlerFunc))
|
||||
g.GET("/hello", func(c *gin.Context) {
|
||||
user, err := authSvc.GetCurrentUser(c.Request.Context())
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Hello " + user.Username})
|
||||
})
|
||||
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
### 3. 在应用装配入口注册插件 (`cmd/app.go`)
|
||||
|
||||
在 `cmd/app.go` 中的 `newWaveletApp` 函数内注册你的新插件:
|
||||
|
||||
```go
|
||||
app.Use(
|
||||
database.New(),
|
||||
cache.New(),
|
||||
logger.New(),
|
||||
storage.New(),
|
||||
// ... 官方平台插件 ...
|
||||
my_plugin.New(), // 注册下游定制插件
|
||||
driver_http.New(),
|
||||
driver_asynq_worker.New(),
|
||||
driver_asynq_cron.New(),
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 严格红线与规范 (Guardrails)
|
||||
|
||||
- **严禁跨插件直接 import**:下游插件可依赖 `core/`、`core/contracts/`、`pkg/`、`plugins/infra/`,**严禁直接 import `plugins/domain/*` 内部私有实现**,一律通过 `contracts` 接口或事件总线调用。
|
||||
- **禁止 GORM AutoMigrate**:数据表结构定义必须通过 `migrations/` 下嵌入的 Goose SQL 管理。
|
||||
- **Goroutine 并发安全**:严禁裸 `go func()`,后台并发任务统一使用 `backend/pkg/util.Go`。
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package consts defines constants and error codes for custom_example plugin.
|
||||
package consts
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package hello provides HTTP API handlers for the custom_example plugin.
|
||||
package hello
|
||||
+60
-57
@@ -1,14 +1,14 @@
|
||||
module Wavelet
|
||||
|
||||
go 1.25.7
|
||||
go 1.25.10
|
||||
|
||||
require (
|
||||
github.com/ClickHouse/clickhouse-go/v2 v2.48.0
|
||||
github.com/alicebob/miniredis/v2 v2.38.0
|
||||
github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.5.3
|
||||
github.com/aws/aws-sdk-go-v2 v1.43.4
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.35
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.34
|
||||
github.com/aws/aws-sdk-go-v2 v1.45.1
|
||||
github.com/aws/aws-sdk-go-v2/config v1.33.1
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.20.1
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.106.5
|
||||
github.com/bodgit/sevenzip v1.6.5
|
||||
github.com/bwmarrin/snowflake v0.3.0
|
||||
@@ -20,13 +20,13 @@ require (
|
||||
github.com/gin-gonic/gin v1.12.0
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/go-acme/lego/v4 v4.35.2
|
||||
github.com/go-jose/go-jose/v4 v4.1.4
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/go-redis/redis_rate/v10 v10.0.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/sessions v1.4.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/hibiken/asynq v0.26.0
|
||||
github.com/maypok86/otter/v2 v2.3.0
|
||||
github.com/nikoksr/notify v1.6.0
|
||||
github.com/oschwald/maxminddb-golang v1.13.1
|
||||
github.com/peterbourgon/diskv/v3 v3.0.1
|
||||
github.com/pressly/goose/v3 v3.27.3
|
||||
@@ -36,7 +36,7 @@ require (
|
||||
github.com/shopspring/decimal v1.4.0
|
||||
github.com/spf13/cobra v1.10.2
|
||||
github.com/spf13/viper v1.21.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/stretchr/testify v1.12.1
|
||||
github.com/studio-b12/gowebdav v0.13.0
|
||||
github.com/swaggo/files v1.0.1
|
||||
github.com/swaggo/gin-swagger v1.6.1
|
||||
@@ -44,6 +44,7 @@ require (
|
||||
github.com/tencent-connect/botgo v0.2.1
|
||||
github.com/ulikunitz/xz v0.5.16
|
||||
github.com/uptrace/opentelemetry-go-extra/otelzap v0.3.2
|
||||
github.com/wneessen/go-mail v0.8.1
|
||||
github.com/yuin/gopher-lua v1.1.2
|
||||
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.70.0
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0
|
||||
@@ -52,20 +53,19 @@ require (
|
||||
go.opentelemetry.io/otel/sdk v1.45.0
|
||||
go.opentelemetry.io/otel/trace v1.45.0
|
||||
go.uber.org/zap v1.28.0
|
||||
golang.org/x/crypto v0.54.0
|
||||
golang.org/x/crypto v0.55.0
|
||||
golang.org/x/image v0.44.0
|
||||
golang.org/x/mod v0.38.0
|
||||
golang.org/x/net v0.57.0
|
||||
golang.org/x/net v0.58.0
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
golang.org/x/sync v0.22.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/telebot.v4 v4.0.0-beta.10
|
||||
gorm.io/driver/clickhouse v0.7.0
|
||||
gorm.io/driver/postgres v1.6.2
|
||||
gorm.io/driver/sqlite v1.6.0
|
||||
gorm.io/gorm v1.31.2
|
||||
gorm.io/plugin/dbresolver v1.6.2
|
||||
gorm.io/plugin/opentelemetry v0.1.16
|
||||
gorm.io/plugin/opentelemetry v0.1.14
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -74,62 +74,67 @@ require (
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/andybalholm/brotli v1.2.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.35 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.35 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.35 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.36 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.19.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.28 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.35 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.36 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.5.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.33.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.45.4 // indirect
|
||||
github.com/aws/smithy-go v1.27.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.7.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.35.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.40.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.47.1 // indirect
|
||||
github.com/aws/smithy-go v1.28.1 // indirect
|
||||
github.com/blinkbean/dingtalk v1.1.3 // indirect
|
||||
github.com/bodgit/plumbing v1.3.0 // indirect
|
||||
github.com/bodgit/windows v1.0.1 // indirect
|
||||
github.com/boj/redistore v1.4.2 // indirect
|
||||
github.com/boj/redistore v1.4.1 // indirect
|
||||
github.com/bwmarrin/discordgo v0.29.0 // indirect
|
||||
github.com/bytedance/gopkg v0.1.4 // indirect
|
||||
github.com/bytedance/sonic v1.15.2 // indirect
|
||||
github.com/bytedance/sonic/loader v0.5.2 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.7 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.1.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.10.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.15 // indirect
|
||||
github.com/gin-contrib/sse v1.1.1 // indirect
|
||||
github.com/glebarez/go-sqlite v1.23.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
github.com/go-faster/city v1.0.1 // indirect
|
||||
github.com/go-faster/errors v0.8.0 // indirect
|
||||
github.com/go-faster/errors v0.7.1 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||
github.com/go-lark/lark v1.16.0 // indirect
|
||||
github.com/go-logr/logr v1.4.4 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v1.0.0 // indirect
|
||||
github.com/go-openapi/jsonreference v1.0.0 // indirect
|
||||
github.com/go-openapi/spec v0.22.9 // indirect
|
||||
github.com/go-openapi/swag/conv v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/jsonutils v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/loading v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/pools v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/stringutils v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.28.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.22.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.2 // indirect
|
||||
github.com/go-openapi/spec v0.22.0 // indirect
|
||||
github.com/go-openapi/swag/conv v0.25.1 // indirect
|
||||
github.com/go-openapi/swag/jsonname v0.25.1 // indirect
|
||||
github.com/go-openapi/swag/jsonutils v0.25.1 // indirect
|
||||
github.com/go-openapi/swag/loading v0.25.1 // indirect
|
||||
github.com/go-openapi/swag/stringutils v0.25.1 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.25.1 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.25.1 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.30.3 // indirect
|
||||
github.com/go-resty/resty/v2 v2.17.2 // indirect
|
||||
github.com/go-sql-driver/mysql v1.10.0 // indirect
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/goccy/go-json v0.10.6 // indirect
|
||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||
github.com/gomodule/redigo v1.9.3 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/btree v1.0.0 // indirect
|
||||
github.com/gorilla/context v1.1.2 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 // indirect
|
||||
github.com/gregdel/pushover v1.4.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect
|
||||
github.com/hashicorp/go-version v1.9.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
@@ -140,11 +145,10 @@ require (
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect
|
||||
github.com/klauspost/compress v1.19.2 // indirect
|
||||
github.com/klauspost/compress v1.19.1 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.4.0 // indirect
|
||||
github.com/leodido/go-urn v1.5.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.24 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
github.com/mfridman/interpolate v0.0.2 // indirect
|
||||
github.com/miekg/dns v1.1.72 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
@@ -152,8 +156,7 @@ require (
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/paulmach/orb v0.13.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.4.3 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.28 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.27 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.61.0 // indirect
|
||||
github.com/redis/go-redis/extra/rediscmd/v9 v9.22.0 // indirect
|
||||
@@ -161,21 +164,24 @@ require (
|
||||
github.com/sagikazarmark/locafero v0.12.0 // indirect
|
||||
github.com/segmentio/asm v1.2.1 // indirect
|
||||
github.com/sethvargo/go-retry v0.4.0 // indirect
|
||||
github.com/slack-go/slack v0.29.0 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/stangelandcl/ppmd v0.1.1 // indirect
|
||||
github.com/stretchr/objx v0.5.3 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tidwall/gjson v1.9.3 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
|
||||
github.com/tidwall/gjson v1.19.0 // indirect
|
||||
github.com/tidwall/match v1.2.0 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.2 // indirect
|
||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||
github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2 // indirect
|
||||
go.mongodb.org/mongo-driver/v2 v2.8.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 // indirect
|
||||
go.opentelemetry.io/otel/log v0.6.0 // indirect
|
||||
go.opentelemetry.io/otel/log v0.12.2 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.45.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.11.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
@@ -184,19 +190,16 @@ require (
|
||||
go4.org v0.0.0-20260112195520-a5071408f32f // indirect
|
||||
golang.org/x/arch v0.29.0 // indirect
|
||||
golang.org/x/sys v0.47.0 // indirect
|
||||
golang.org/x/text v0.40.0 // indirect
|
||||
golang.org/x/text v0.41.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
golang.org/x/tools v0.48.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d // indirect
|
||||
google.golang.org/grpc v1.83.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect
|
||||
google.golang.org/grpc v1.83.2 // indirect
|
||||
google.golang.org/protobuf v1.36.12 // indirect
|
||||
gorm.io/driver/mysql v1.6.0 // indirect
|
||||
modernc.org/libc v1.74.4 // indirect
|
||||
modernc.org/libc v1.74.3 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
modernc.org/sqlite v1.56.0 // indirect
|
||||
modernc.org/sqlite v1.54.0 // indirect
|
||||
)
|
||||
|
||||
exclude github.com/gomodule/redigo v2.0.0+incompatible
|
||||
|
||||
+125
-104
@@ -84,58 +84,62 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV
|
||||
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/aws/aws-sdk-go-v2 v1.43.4 h1:b9FTvbRwy+JCsfp2Wp6wV/KbOx3Aj7nkoFb2cRX0IhE=
|
||||
github.com/aws/aws-sdk-go-v2 v1.43.4/go.mod h1:70vwSy16txshwG+g55WkpgPKDIByzHI8ccBsOteo3bQ=
|
||||
github.com/aws/aws-sdk-go-v2 v1.45.1 h1:iIoG3NaLhV6UZpPXyPXlDj2I9oS8tV/nMcMnITCC6Ks=
|
||||
github.com/aws/aws-sdk-go-v2 v1.45.1/go.mod h1:bttEH6JqnUL8LepvDVfdrds/fZ5bCIxzpe3abyUrhDU=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.16 h1:aiuaKlDweRC5qExJondpWjOgyzMHpofpwspGXUtwn4c=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.16/go.mod h1:nG/LOlmox9BDe9HvQnXWzgcK8uKbgBMZ/Hp5pVt/21I=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.35 h1:UEzXuET8E42lxBPijuACu/tEK7v5lFPlk0Q+GT5WD9E=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.35/go.mod h1:KaMtJpFa2JlL2BStjjHQVwQpzZEmw+ND/EgVrfFoo2g=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.34 h1:y6GkSmcv5myd1ngrYbGmiLlwQqB6TQhOuN/tbSSuWDY=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.34/go.mod h1:w3dTcnDVoQIewjo7JG45hduAToikiIFLC4FIO7fndvw=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.35 h1:+S7kbJoLDDQ5tE+lHrUBgMkzC8NLgsaioS2F3dVoFAE=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.35/go.mod h1:Ak7xXviIARfFdNUJ9Etb0bdVDt/KAvKjMGJVLWXDzik=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.35 h1:kzVuGlatQtYinwBJEEyLAbggepCoavosiaHHX9+fD+c=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.35/go.mod h1:0yLx0yEI+SfqeJMPvOtIEFoZbiQYXMGszBueiutQyaI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.35 h1:WK6CjihTuLisCjSKKbildJ79sGZZgbBz3iNa7VsKIhU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.35/go.mod h1:KYleN57luLoe97R7vTnx8PMcVrr9gAcRECtOjl91DNg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.36 h1:jbGY4CXLzZElOXgGsexlC3Hi+3YM0rSmk4opFXKqg/k=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.36/go.mod h1:uBu/9aKsS/UQGc72RAt3y54kjgYQxmhut8ZD2dXCDNE=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.15 h1:JJLBQxwY+AFwuPAi5ivGc1ChnTdUt4cXMv7e76m2c/Y=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.15/go.mod h1:lQknBIe78MVL0cQOQDlag8KGflMbMEVFx9mB6O8ENvk=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.33.1 h1:bq9jze1hQ5YTCLoVxNnbp0T7rglrlOE7N9YsHqjGkEw=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.33.1/go.mod h1:2A3HQwG4zaL5Tm80rc6RZj8LmWWv4WYT5v8raSz/L7A=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.20.1 h1:Z8GRNEx0u9sDkZOq4PUnN8mjGwbUQGRzMSXpvt3d8xQ=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.20.1/go.mod h1:uBIK00kFo95dnemqfFMTWx0X8YRqsh6ecIoCjjOkZqM=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.19.1 h1:YIEBqcqRnpi4Pfv0YHImtgi6czGCwKHANC7SwmUAVD0=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.19.1/go.mod h1:imEf0oufgAo8KAkCHhrOdqGEC0YWx1PPBQH82shSxGw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.1 h1:pc138gM1CW+XPc60rEwUlwwuwWFQK16CI1T7v1F9Oec=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.1/go.mod h1:1+koxpPIbfBdfzP6vojm5/zTpTQ/micYwlxIiNB3TxI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.1 h1:K0JsbZQj+1h208Ro1zHeA4l7bMp0NvRffHQ91q8Ol1s=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.8.1/go.mod h1:W3/vL6EtCIatICGy9ab29QhMuae+cOKPWcMxv02CO+Q=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.1 h1:yhw5KD1phVyP9vijxOUzDfEtJx+bt+L63k+VfuiYFAA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.5.1/go.mod h1:ZW2e0d7DYlRxlS9hEiMXE47gTdX5KRN4byUiNbUpG+Q=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19 h1:bAdDl/HkGCcGPoe25ToSHEw23VIxt6CT5fLcg111BKg=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.19/go.mod h1:KaUzbLxv4CeSxh6ZCl9B4m7CuFenS8kUEaDs+f/DQr4=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.28 h1:Q1TF1J9jVD+vFo0LzNnmNdQ9EAt52TS+MQlq9Ir+Yxo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.28/go.mod h1:4KqXXC/p1hrotmouDFbrRoWaLy962b9PMUReCG6+uWo=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.35 h1:BBEElKh4a+rKshvjrfpajTe9CbpZvrbb4Jkg2PB7RzA=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.35/go.mod h1:zaZk983w//8beSruBVec/mr4CmDwgZitW/qzGhAAX0g=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.1 h1:RmmWQPREQdk9U+PfqeHW3MqZaBaNK7TpV9W3RY+b+7g=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.14.1/go.mod h1:0A3W4F+68ZnNk5XcNL/e9HFMwnP8RlEicFfy6eOEDyw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.36 h1:EUIwBoN+q7UmhAejxgD27APiRjh1vwCFo53gSqdT0BM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.36/go.mod h1:6u00gmlTGR6W0b2k9NBrld7MnOEmf1Spqx0VVt6AqyE=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.106.5 h1:HpN6GgZ3T8pSvRp81ZsgumNjlvRsa+9M0ZL2o6W4uLY=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.106.5/go.mod h1:5FTZoQxhmLEiCAtYVk6V+t0iS/B5yGZVLZ3Wq5FDJZI=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.5.4 h1:cOJELVNrq5Q3Udry2GLuHUM7MhwpeaQRdYaoa6GI/yI=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.5.4/go.mod h1:f4LxzKBtaTxD7xh3PiVg3CE1tchQemfmghaJr+NbK2c=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.33.4 h1:AMW7a7S8iQaHjBYZdU3PCq4GKRPijTPRAc7e6XtEThY=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.33.4/go.mod h1:QQNsFV1DVXoXcZt18FS8lI8rtUrlDyAuWZLQ5shunv4=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.4 h1:AsbZcJAQPRmHDJG8K1N0pof/1zPWjVT8TFlTWuGLSvo=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.38.4/go.mod h1:6imqztH0//t0mKbl6yWl7swSEl7F/w32oAmqB3vP1ag=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.45.4 h1:w/AryDYMjSUANSQ2uoZxJovUsMTwWJNTv3IMex30Y+4=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.45.4/go.mod h1:WeBiAa67azG7Su9Vf+ChGDBLiAozJCXzdjXiPBUwtbc=
|
||||
github.com/aws/smithy-go v1.27.6 h1:0zjT8jgK3jbrTT7JJ3EE6JsMhX8JTrZ+f1sEndYDXrA=
|
||||
github.com/aws/smithy-go v1.27.6/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.7.1 h1:mdMtSVKdQ3+mzBh+l0ogrFYZVQUCg6pJZOirA2ARsYE=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.7.1/go.mod h1:9IqUlsJDbUPcg6cgx3WEzXdjrbWzLDQrak0aaSqlTcI=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.35.1 h1:B6WFn91tobD6gG4724ONHaqrpKsoETGnv98LHe/yIGM=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.35.1/go.mod h1:tWuiVBUtPBr8/rgRiYS8Uf85sHcAN+G7XS3D3CEoUh8=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.40.1 h1:6yeYCWFvgbI2TI3K6jr9LtBNhXgJ7g4xqD+DEiaDDmM=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.40.1/go.mod h1:naFe83jSMuYkH+QjQPX8n1MLhBkeCFM5Lsnh5m5wz3c=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.47.1 h1:Sv2xPnRHlThSUtVujYuUBPI/Il8si6UPHXL8DMiB/F0=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.47.1/go.mod h1:mKo/CzaCz8qytGW70NG4vIIGAx1HXTlb5lHNkC5k3lk=
|
||||
github.com/aws/smithy-go v1.28.1 h1:R/nXH00c8qcfCzQVELtRw+eLQWtzv+VAIEFJ1/xxXlQ=
|
||||
github.com/aws/smithy-go v1.28.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/blinkbean/dingtalk v1.1.3 h1:MbidFZYom7DTFHD/YIs+eaI7kRy52kmWE/sy0xjo6E4=
|
||||
github.com/blinkbean/dingtalk v1.1.3/go.mod h1:9BaLuGSBqY3vT5hstValh48DbsKO7vaHaJnG9pXwbto=
|
||||
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
|
||||
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
|
||||
github.com/bodgit/sevenzip v1.6.5 h1:7H7BxgmeX0j6UX42lH+KXQ92WgMQJ49DoocFdfHbCng=
|
||||
github.com/bodgit/sevenzip v1.6.5/go.mod h1:GhuB6Lq1xCpP1sps+horjZ8lgiKPJcy2zUX3prla9wc=
|
||||
github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=
|
||||
github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
|
||||
github.com/boj/redistore v1.4.2 h1:44FVJnBTdzDV9VpaByCOaQs0ND8hzABD2xBHcAIbX9s=
|
||||
github.com/boj/redistore v1.4.2/go.mod h1:jjh65GXAH+5lj29pPRnQHdRNOt/lmP0LOaK+fiG2Fu8=
|
||||
github.com/boj/redistore v1.4.1 h1:lP9ZZWqKMq2RIqexlZX1w1ODSnegL+puxGIujkU5tIw=
|
||||
github.com/boj/redistore v1.4.1/go.mod h1:c0Tvw6aMjslog4jHIAcNv6EtJM849YoOAhMY7JBbWpI=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/bwmarrin/discordgo v0.29.0 h1:FmWeXFaKUwrcL3Cx65c20bTRW+vOb6k8AnaP+EgjDno=
|
||||
github.com/bwmarrin/discordgo v0.29.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
||||
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
|
||||
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
|
||||
github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
|
||||
@@ -214,8 +218,8 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
||||
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd+d3YzLmaI=
|
||||
github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
@@ -227,16 +231,16 @@ github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko
|
||||
github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s=
|
||||
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
|
||||
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
|
||||
github.com/glebarez/go-sqlite v1.23.0 h1:FyhIq4jqmgphQAUlY79zPldYGwISEZikaDfhiGWkkaI=
|
||||
github.com/glebarez/go-sqlite v1.23.0/go.mod h1:IIYrOH3L0rHY3jb4IXOHoWdklNajSGUN2eJcvK8WrnI=
|
||||
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||
github.com/go-acme/lego/v4 v4.35.2 h1:uVQg+KC/yj9R2g7Q9W5wDqhvQvxV5SMu5eqFVoN5xZU=
|
||||
github.com/go-acme/lego/v4 v4.35.2/go.mod h1:pX2jN5n8OphMGY1IaMjYm5DAEzguBaKRt8AvJAgJXpc=
|
||||
github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw=
|
||||
github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw=
|
||||
github.com/go-faster/errors v0.8.0 h1:9T9eJrM+72dFk7n4DfhuaDDe6cyuFCSW2oNUkN77Yqc=
|
||||
github.com/go-faster/errors v0.8.0/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo=
|
||||
github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg=
|
||||
github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
@@ -245,6 +249,8 @@ github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-lark/lark v1.16.0 h1:U6BwkLM9wrZedSM7cIiMofganr8PCvJN+M75w2lf2Gg=
|
||||
github.com/go-lark/lark v1.16.0/go.mod h1:6ltbSztPZRT6IaO9ZIQyVaY5pVp/KeMizDYtfZkU+vM=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
@@ -253,33 +259,29 @@ github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
|
||||
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s=
|
||||
github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y=
|
||||
github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY=
|
||||
github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0=
|
||||
github.com/go-openapi/spec v0.22.9 h1:/vKIFDcGKp0ktZWGbym/tJEWbk6/XOEmAVU0kqKMH+w=
|
||||
github.com/go-openapi/spec v0.22.9/go.mod h1:b/mNUYIOQOyIiUzUzXEE8xzyZqf93KvM9hQGP91yfl0=
|
||||
github.com/go-openapi/swag v0.28.0 h1:xkgbOSKj6DZziNpyqRRAOt3GJGtgjgsd2RoyT30VWuw=
|
||||
github.com/go-openapi/swag/conv v0.28.0 h1:GtqqbyFe7vR5Y7ehxG9W6/OvrSFdf1OLeTGp40TqxH8=
|
||||
github.com/go-openapi/swag/conv v0.28.0/go.mod h1:mbUE+mzctnhxi864m0Q07SpN8OowD9JhxmxuYvZZD/k=
|
||||
github.com/go-openapi/swag/jsonutils v0.28.0 h1:YIch6FwO7RXzeAnbO8Tu7dWBZeUEH+4nA0HXltVTnv4=
|
||||
github.com/go-openapi/swag/jsonutils v0.28.0/go.mod h1:CYM3WlTUcagR2ZoHdz54di/cbBqt82tuxuXgAjxw+mg=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0 h1:qV+VVUAx5Oro8WjVWpZeql7YReTKhT4smR4zhcOQZr0=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY=
|
||||
github.com/go-openapi/swag/loading v0.28.0 h1:td8QZdZC9MIYGGSnSPKShKiK22I2tU5UQvuUhIBPRLU=
|
||||
github.com/go-openapi/swag/loading v0.28.0/go.mod h1:rXB0QiQX5mMveXEA7ouM4KiiM9jVJe4K6BVbwhD1M4k=
|
||||
github.com/go-openapi/swag/pools v0.28.0 h1:HPMZWSAfce3rdVTFcjFiCIBtDg9h4x2QlRrHipwhxeU=
|
||||
github.com/go-openapi/swag/pools v0.28.0/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE=
|
||||
github.com/go-openapi/swag/stringutils v0.28.0 h1:ixsc9iYgDPubHL/8nSkbnryEHpD2VRlBMLKpQyPXcDU=
|
||||
github.com/go-openapi/swag/stringutils v0.28.0/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM=
|
||||
github.com/go-openapi/swag/typeutils v0.28.0 h1:nRBKSBXjDgf01VDPB3fWeD9nQuhCOVeIYAkUx2tbkyY=
|
||||
github.com/go-openapi/swag/typeutils v0.28.0/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ=
|
||||
github.com/go-openapi/swag/yamlutils v0.28.0 h1:TV3JXH6DS46KUroDtMLAYHGkdWf5VDq3wVWFirmzROY=
|
||||
github.com/go-openapi/swag/yamlutils v0.28.0/go.mod h1:x0q/yndZHEgk9Rx3DyDqzFUmHy55KTvIZldvF2dTJXs=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo=
|
||||
github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug=
|
||||
github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw=
|
||||
github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk=
|
||||
github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM=
|
||||
github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU=
|
||||
github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ=
|
||||
github.com/go-openapi/spec v0.22.0 h1:xT/EsX4frL3U09QviRIZXvkh80yibxQmtoEvyqug0Tw=
|
||||
github.com/go-openapi/spec v0.22.0/go.mod h1:K0FhKxkez8YNS94XzF8YKEMULbFrRw4m15i2YUht4L0=
|
||||
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||
github.com/go-openapi/swag/conv v0.25.1 h1:+9o8YUg6QuqqBM5X6rYL/p1dpWeZRhoIt9x7CCP+he0=
|
||||
github.com/go-openapi/swag/conv v0.25.1/go.mod h1:Z1mFEGPfyIKPu0806khI3zF+/EUXde+fdeksUl2NiDs=
|
||||
github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU=
|
||||
github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo=
|
||||
github.com/go-openapi/swag/jsonutils v0.25.1 h1:AihLHaD0brrkJoMqEZOBNzTLnk81Kg9cWr+SPtxtgl8=
|
||||
github.com/go-openapi/swag/jsonutils v0.25.1/go.mod h1:JpEkAjxQXpiaHmRO04N1zE4qbUEg3b7Udll7AMGTNOo=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1 h1:DSQGcdB6G0N9c/KhtpYc71PzzGEIc/fZ1no35x4/XBY=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1/go.mod h1:kjmweouyPwRUEYMSrbAidoLMGeJ5p6zdHi9BgZiqmsg=
|
||||
github.com/go-openapi/swag/loading v0.25.1 h1:6OruqzjWoJyanZOim58iG2vj934TysYVptyaoXS24kw=
|
||||
github.com/go-openapi/swag/loading v0.25.1/go.mod h1:xoIe2EG32NOYYbqxvXgPzne989bWvSNoWoyQVWEZicc=
|
||||
github.com/go-openapi/swag/stringutils v0.25.1 h1:Xasqgjvk30eUe8VKdmyzKtjkVjeiXx1Iz0zDfMNpPbw=
|
||||
github.com/go-openapi/swag/stringutils v0.25.1/go.mod h1:JLdSAq5169HaiDUbTvArA2yQxmgn4D6h4A+4HqVvAYg=
|
||||
github.com/go-openapi/swag/typeutils v0.25.1 h1:rD/9HsEQieewNt6/k+JBwkxuAHktFtH3I3ysiFZqukA=
|
||||
github.com/go-openapi/swag/typeutils v0.25.1/go.mod h1:9McMC/oCdS4BKwk2shEB7x17P6HmMmA6dQRtAkSnNb8=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.1 h1:mry5ez8joJwzvMbaTGLhw8pXUnhDK91oSJLDPF1bmGk=
|
||||
github.com/go-openapi/swag/yamlutils v0.25.1/go.mod h1:cm9ywbzncy3y6uPm/97ysW8+wZ09qsks+9RS8fLWKqg=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
@@ -293,6 +295,8 @@ github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn
|
||||
github.com/go-playground/validator/v10 v10.30.3 h1:4MU6YkEwx7GbcPJOZxrtbu+QfF3pJLJuaYTeAH0DYy8=
|
||||
github.com/go-playground/validator/v10 v10.30.3/go.mod h1:4Axh7oCNGcoGkqLoE4YWt6n20mcEIsPRlB7vPk3lpyc=
|
||||
github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=
|
||||
github.com/go-redis/redis_rate/v10 v10.0.1 h1:calPxi7tVlxojKunJwQ72kwfozdy25RjA0bCj1h0MUo=
|
||||
github.com/go-redis/redis_rate/v10 v10.0.1/go.mod h1:EMiuO9+cjRkR7UvdvwMO7vbgqJkltQHtwbdIQvaBKIU=
|
||||
github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q=
|
||||
github.com/go-resty/resty/v2 v2.17.2 h1:FQW5oHYcIlkCNrMD2lloGScxcHJ0gkjshV3qcQAyHQk=
|
||||
github.com/go-resty/resty/v2 v2.17.2/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA=
|
||||
@@ -300,6 +304,10 @@ github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxo
|
||||
github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
|
||||
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
||||
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
|
||||
@@ -347,9 +355,8 @@ github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
|
||||
github.com/gomodule/redigo v1.9.3 h1:dNPSXeXv6HCq2jdyWfjgmhBdqnR6PRO3m/G05nvpPC8=
|
||||
github.com/gomodule/redigo v1.9.3/go.mod h1:KsU3hiK/Ay8U42qpaJk+kuNa3C+spxapWpM+ywhcgtw=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
@@ -389,8 +396,8 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe
|
||||
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo=
|
||||
github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@@ -413,10 +420,12 @@ github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2e
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gregdel/pushover v1.4.0 h1:P77WAJ2zPG+b0mEsmMjWGrPMuvhkh9k3v7OviwsoveE=
|
||||
github.com/gregdel/pushover v1.4.0/go.mod h1:EcaO66Nn1StkpEm1iKtBTV3d2A16SoMsVER1PthX7to=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 h1:/Tnpcb2E0Pz/tN9s3bfEY2Q8ePCEX9iuS+cneUwncnw=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0/go.mod h1:zOBXOsUaBSjKgmH4OGzV1esUpR3oUSCPYVd2cUBjKYY=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs=
|
||||
github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0=
|
||||
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
@@ -468,6 +477,11 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
@@ -482,8 +496,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.19.2 h1:hMRETovs/pu/dVWN7zIT1PGG8t509MwT6bO7XSi26R8=
|
||||
github.com/klauspost/compress v1.19.2/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk=
|
||||
github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
|
||||
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
@@ -548,6 +562,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/nikoksr/notify v1.6.0 h1:H9pyvNyo4/47vn7uPTBbb+ld6Y/KmpXo9Ghf8cGqAlU=
|
||||
github.com/nikoksr/notify v1.6.0/go.mod h1:GBrx8S2GI0ZtXdobxNmXJjPJN4P6qXbjPW78zlLSg6s=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
@@ -568,16 +584,14 @@ github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdD
|
||||
github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/peterbourgon/diskv/v3 v3.0.1 h1:x06SQA46+PKIUftmEujdwSEpIx8kR+M9eLYsUxeYveU=
|
||||
github.com/peterbourgon/diskv/v3 v3.0.1/go.mod h1:kJ5Ny7vLdARGU3WUuy6uzO6T0nb/2gWcT1JiBvRmb5o=
|
||||
github.com/pierrec/lz4/v4 v4.1.28 h1:pPEPwRJ4kybBTfGt28q7lQsRJQHhC08axprdLD5Ppio=
|
||||
github.com/pierrec/lz4/v4 v4.1.28/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
||||
github.com/pierrec/lz4/v4 v4.1.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk=
|
||||
github.com/pierrec/lz4/v4 v4.1.27/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||
github.com/pressly/goose/v3 v3.27.3 h1:pIglVHjw99r4e/hDHHwbl9vfOsDMqUokfkXo6+n/RxA=
|
||||
@@ -637,6 +651,8 @@ github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+D
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/slack-go/slack v0.29.0 h1:ohhMNgp9DmPKiLhH/pNZV4NxhOXKgNy0SH8FzVHNerI=
|
||||
github.com/slack-go/slack v0.29.0/go.mod h1:UEe+jmo9WLlwHB04qsOrTDvqM7Aa4rQL3O5wF3n0hx4=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
@@ -675,8 +691,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
|
||||
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
|
||||
github.com/studio-b12/gowebdav v0.13.0 h1:OcwSg6IQHOFNdYHn3bPOHwSE8looG8N56Y5xTT1asqQ=
|
||||
github.com/studio-b12/gowebdav v0.13.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
|
||||
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||
@@ -688,25 +704,32 @@ github.com/swaggo/gin-swagger v1.6.1 h1:Ri06G4gc9N4t4k8hekMigJ9zKTFSlqj/9paAQCQs
|
||||
github.com/swaggo/gin-swagger v1.6.1/go.mod h1:LQ+hJStHakCWRiK/YNYtJOu4mR2FP+pxLnILT/qNiTw=
|
||||
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
|
||||
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
|
||||
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
|
||||
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
|
||||
github.com/tencent-connect/botgo v0.2.1 h1:+BrTt9Zh+awL28GWC4g5Na3nQaGRWb0N5IctS8WqBCk=
|
||||
github.com/tencent-connect/botgo v0.2.1/go.mod h1:oO1sG9ybhXNickvt+CVym5khwQ+uKhTR+IhTqEfOVsI=
|
||||
github.com/tidwall/gjson v1.9.3 h1:hqzS9wAHMO+KVBBkLxYdkEeeFHuqr95GfClRLKlgK0E=
|
||||
github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/gjson v1.19.0 h1:xwxm7n691Uf3u5OFjzngavjGTh55KX5q/9w9xHW88JU=
|
||||
github.com/tidwall/gjson v1.19.0/go.mod h1:V37/opeE/JbLUOfH0QTXiNez2l0RUjYUhpT4szFQAfc=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM=
|
||||
github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.3.2 h1:zkEASHHyEClGeURfgNT9PJZVfAbs9oEX9QXggwWNJbc=
|
||||
github.com/ugorji/go/codec v1.3.2/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
||||
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
github.com/ulikunitz/xz v0.5.16 h1:ld6NyySjx5lowVKwJvMRLnW5nxKX/xnpSiFYZ/Lxur0=
|
||||
github.com/ulikunitz/xz v0.5.16/go.mod h1:H9Rt/W6/Qj27PGauhQc6nfCDy7vHpzsOThBSaYDoEhw=
|
||||
github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2 h1:3/aHKUq7qaFMWxyQV0W2ryNgg8x8rVeKVA20KJUkfS0=
|
||||
github.com/uptrace/opentelemetry-go-extra/otelutil v0.3.2/go.mod h1:Zit4b8AQXaXvA68+nzmbyDzqiyFRISyw1JiD5JqUBjw=
|
||||
github.com/uptrace/opentelemetry-go-extra/otelzap v0.3.2 h1:cj/Z6FKTTYBnstI0Lni9PA+k2foounKIPUmj1LBwNiQ=
|
||||
github.com/uptrace/opentelemetry-go-extra/otelzap v0.3.2/go.mod h1:LDaXk90gKEC2nC7JH3Lpnhfu+2V7o/TsqomJJmqA39o=
|
||||
github.com/wneessen/go-mail v0.8.1 h1:tVcncj02/QySVFw3zr/kXOzZcuFQqBNT6K+Rbgm/pcM=
|
||||
github.com/wneessen/go-mail v0.8.1/go.mod h1:dWZ61zadzCIyvB4y1/YzC5O7MrbbzBfPkARmbosdf8w=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
@@ -748,8 +771,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0 h1:fG5MC
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0/go.mod h1:BmAYTn+3ysbRe+IU2msxmf5Rx3g6DHvex+tWI3LdhYI=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.45.0 h1:lsA/S1bxgdbyFGkTj+3meEdJ6ADVU7QoFstV6MXgE68=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.45.0/go.mod h1:L7u+MirGoB1bjeLH66+xDykF4RC8C3RN7lIFpBiewUo=
|
||||
go.opentelemetry.io/otel/log v0.6.0 h1:nH66tr+dmEgW5y+F9LanGJUBYPrRgP4g2EkmPE3LeK8=
|
||||
go.opentelemetry.io/otel/log v0.6.0/go.mod h1:KdySypjQHhP069JX0z/t26VHwa8vSwzgaKmXtIB3fJM=
|
||||
go.opentelemetry.io/otel/log v0.12.2 h1:yob9JVHn2ZY24byZeaXpTVoPS6l+UrrxmxmPKohXTwc=
|
||||
go.opentelemetry.io/otel/log v0.12.2/go.mod h1:ShIItIxSYxufUMt+1H5a2wbckGli3/iCfuEbVZi/98E=
|
||||
go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M=
|
||||
go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s=
|
||||
go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw=
|
||||
@@ -793,8 +816,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
|
||||
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
|
||||
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
|
||||
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -891,8 +914,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
|
||||
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
|
||||
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
|
||||
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -1038,8 +1061,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
||||
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
|
||||
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
|
||||
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -1239,8 +1262,8 @@ google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP
|
||||
google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d h1:FarXi840EJWSHYTN3ERkADbPWjl307+FGrA22KAVjjc=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d/go.mod h1:K/+WGbmBY7aNW1HDw1fJnKYo10i0DkAX6pows00dLig=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d h1:IL4hdHzcUv2l/gcg98/Rj3FbtE6axwqslOW8SW0C+S0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
@@ -1271,8 +1294,8 @@ google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ5
|
||||
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
||||
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.83.0 h1:JeNZEKJFbQxArAMl+hiytHauacDNqJUllNfmIMmpqnQ=
|
||||
google.golang.org/grpc v1.83.0/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ=
|
||||
google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU=
|
||||
google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
@@ -1288,13 +1311,12 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
|
||||
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
@@ -1314,7 +1336,6 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/clickhouse v0.7.0 h1:BCrqvgONayvZRgtuA6hdya+eAW5P2QVagV3OlEp1vtA=
|
||||
gorm.io/driver/clickhouse v0.7.0/go.mod h1:TmNo0wcVTsD4BBObiRnCahUgHJHjBIwuRejHwYt3JRs=
|
||||
@@ -1328,8 +1349,8 @@ gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo=
|
||||
gorm.io/gorm v1.31.2/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
gorm.io/plugin/dbresolver v1.6.2 h1:F4b85TenghUeITqe3+epPSUtHH7RIk3fXr5l83DF8Pc=
|
||||
gorm.io/plugin/dbresolver v1.6.2/go.mod h1:tctw63jdrOezFR9HmrKnPkmig3m5Edem9fdxk9bQSzM=
|
||||
gorm.io/plugin/opentelemetry v0.1.16 h1:Kypj2YYAliJqkIczDZDde6P6sFMhKSlG5IpngMFQGpc=
|
||||
gorm.io/plugin/opentelemetry v0.1.16/go.mod h1:P3RmTeZXT+9n0F1ccUqR5uuTvEXDxF8k2UpO7mTIB2Y=
|
||||
gorm.io/plugin/opentelemetry v0.1.14 h1:xivP39t/0JgcceDl+BLwVAJHihjFEUj0ZocMSBwZ7ZY=
|
||||
gorm.io/plugin/opentelemetry v0.1.14/go.mod h1:ZAp4v5vU1CCcK9Oo8/va5rl6NStrzpSU+a70evd+W/g=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
@@ -1349,8 +1370,8 @@ modernc.org/gc/v3 v3.1.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI=
|
||||
modernc.org/gc/v3 v3.1.4/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.74.4 h1:fX1Omw4o2/1C2iRkkIsrQTasJQldLhRmuPreXLoWs9k=
|
||||
modernc.org/libc v1.74.4/go.mod h1:eeQAS9W3sZeKYMFubydxJpII9ybHWshk+7or7bLG9co=
|
||||
modernc.org/libc v1.74.3 h1:a4J+Z8aVaxPyjyxRAdJzw246PqpcFGvVPnfT/AuM5Ws=
|
||||
modernc.org/libc v1.74.3/go.mod h1:4H7h/MJ8wnjL8RAbp9v3OXgnk22X7MouHIhDbvP3gj4=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
@@ -1359,8 +1380,8 @@ modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
||||
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.56.0 h1:/D8e2RfFqoy/Zc6PuC76U28zFwmI/sYx1Kjm4yEn9e0=
|
||||
modernc.org/sqlite v1.56.0/go.mod h1:yCJ2cmAaIkHQ25oXWrF8H4O1lIfPYPR26yCEDj2P3pQ=
|
||||
modernc.org/sqlite v1.54.0 h1:JCxR4qwkJvOaqAoYcgDoO25Nc+ROg6EJ2LfBVzdrgog=
|
||||
modernc.org/sqlite v1.54.0/go.mod h1:4ntCLuNmnH8+GNqjka1wNg7KJd5/Hi5FYp8K+XQ7GZw=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"Wavelet/openflare/plugins/flared/sync"
|
||||
"Wavelet/openflare/plugins/flared/wsclient"
|
||||
edgerunner "Wavelet/openflare/share/edge/runner"
|
||||
"Wavelet/pkg/util"
|
||||
)
|
||||
|
||||
// Runner is the top-level orchestrator for the flared agent. It wires together
|
||||
@@ -31,8 +32,8 @@ type Runner struct {
|
||||
// Run starts all background services and enters the WebSocket reconnect loop.
|
||||
// It blocks until ctx is cancelled or an unrecoverable error occurs.
|
||||
func (r *Runner) Run(ctx context.Context) error {
|
||||
go r.HeartbeatService.Run(ctx)
|
||||
go r.SyncService.Run(ctx)
|
||||
util.Go(func() { r.HeartbeatService.Run(ctx) })
|
||||
util.Go(func() { r.SyncService.Run(ctx) })
|
||||
|
||||
return edgerunner.RunWSReconnectLoop(ctx, edgerunner.WSReconnectConfig{
|
||||
ComponentName: "flared",
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"time"
|
||||
|
||||
service "Wavelet/openflare/share/protocol"
|
||||
"Wavelet/pkg/util"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -143,7 +144,7 @@ func (m *Manager) UpdateConfig(ctx context.Context, cfg *service.RelayConfig) {
|
||||
m.lastError = err.Error()
|
||||
return
|
||||
}
|
||||
go m.supervise(ctx, generation)
|
||||
util.Go(func() { m.supervise(ctx, generation) })
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -167,7 +168,7 @@ func (m *Manager) UpdateConfig(ctx context.Context, cfg *service.RelayConfig) {
|
||||
return
|
||||
}
|
||||
|
||||
go m.supervise(ctx, generation)
|
||||
util.Go(func() { m.supervise(ctx, generation) })
|
||||
}
|
||||
|
||||
func (m *Manager) renderConfig(cfg *service.RelayConfig) error {
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"Wavelet/openflare/plugins/relay/wsclient"
|
||||
edgerunner "Wavelet/openflare/share/edge/runner"
|
||||
service "Wavelet/openflare/share/protocol"
|
||||
"Wavelet/pkg/util"
|
||||
)
|
||||
|
||||
// Runner manages the relay process.
|
||||
@@ -31,7 +32,7 @@ type Runner struct {
|
||||
|
||||
// Run starts the relay process by initiating the heartbeat and WS reconnection loop.
|
||||
func (r *Runner) Run(ctx context.Context) error {
|
||||
go r.HeartbeatService.Run(ctx)
|
||||
util.Go(func() { r.HeartbeatService.Run(ctx) })
|
||||
|
||||
return edgerunner.RunWSReconnectLoop(ctx, edgerunner.WSReconnectConfig{
|
||||
ComponentName: "relay",
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"Wavelet/openflare/plugins/server/kernel/credential"
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/gorm"
|
||||
@@ -60,8 +59,8 @@ func setupCloudflareLogicDB(t *testing.T) (context.Context, uint) {
|
||||
); err != nil {
|
||||
t.Fatalf("AutoMigrate() error = %v", err)
|
||||
}
|
||||
db.SetDB(conn)
|
||||
t.Cleanup(func() { db.SetDB(nil) })
|
||||
repository.SetDBForTest(conn)
|
||||
t.Cleanup(func() { repository.SetDBForTest(nil) })
|
||||
ctx := context.Background()
|
||||
sealed, err := credential.Seal(`{"api_token":"test-token"}`)
|
||||
if err != nil {
|
||||
@@ -143,11 +142,11 @@ func TestCreateMemberCopiesGroupDefaultProxied(t *testing.T) {
|
||||
t.Fatalf("GetCFPointingGroup() error = %v", err)
|
||||
}
|
||||
zone := model.Zone{Domain: "example.net"}
|
||||
if err := db.DB(ctx).Create(&zone).Error; err != nil {
|
||||
if err := repository.DB(ctx).Create(&zone).Error; err != nil {
|
||||
t.Fatalf("Create(zone) error = %v", err)
|
||||
}
|
||||
domain := model.ZoneDomain{ZoneID: zone.ID, Domain: "www.example.net"}
|
||||
if err := db.DB(ctx).Create(&domain).Error; err != nil {
|
||||
if err := repository.DB(ctx).Create(&domain).Error; err != nil {
|
||||
t.Fatalf("Create(domain) error = %v", err)
|
||||
}
|
||||
restore := SetDispatchTaskForTest(func(context.Context, string, []byte, string) (string, error) { return "task-1", nil })
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
|
||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||
"Wavelet/pkg/response"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -58,7 +57,7 @@ func TestGetGroupWithOrphanedMemberHealsAndSucceeds(t *testing.T) {
|
||||
}
|
||||
|
||||
// Simulate orphaned member by deleting the ZoneDomain directly
|
||||
if err := db.DB(ctx).Exec("DELETE FROM of_zone_domains WHERE id = ?", member.ZoneDomainID).Error; err != nil {
|
||||
if err := repository.DB(ctx).Exec("DELETE FROM of_zone_domains WHERE id = ?", member.ZoneDomainID).Error; err != nil {
|
||||
t.Fatalf("DELETE FROM of_zone_domains error = %v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
||||
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -28,10 +27,10 @@ func setupDashboardTestDB(t *testing.T) func() {
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, sqliteDB.AutoMigrate(&model.OpenFlareNode{}))
|
||||
|
||||
db.SetDB(sqliteDB)
|
||||
repository.SetDBForTest(sqliteDB)
|
||||
testhelper.SetupLogStoresForTest(t)
|
||||
return func() {
|
||||
db.SetDB(nil)
|
||||
repository.SetDBForTest(nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +42,7 @@ func TestGetOverviewStructure(t *testing.T) {
|
||||
now := time.Now().UTC()
|
||||
lastSeen := now.Add(-15 * time.Second) // within default 60s offline threshold
|
||||
|
||||
require.NoError(t, db.DB(ctx).Create(&model.OpenFlareNode{
|
||||
require.NoError(t, repository.DB(ctx).Create(&model.OpenFlareNode{
|
||||
NodeID: "node-dashboard-1",
|
||||
Name: "Edge 1",
|
||||
IP: "10.0.0.1",
|
||||
@@ -52,7 +51,7 @@ func TestGetOverviewStructure(t *testing.T) {
|
||||
CurrentVersion: "v1.0.0",
|
||||
LastSeenAt: &lastSeen,
|
||||
}).Error)
|
||||
require.NoError(t, db.DB(ctx).Create(&model.OpenFlareNode{
|
||||
require.NoError(t, repository.DB(ctx).Create(&model.OpenFlareNode{
|
||||
NodeID: "node-dashboard-2",
|
||||
Name: "Edge 2",
|
||||
IP: "10.0.0.2",
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
||||
"Wavelet/pkg/response"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/glebarez/sqlite"
|
||||
@@ -36,11 +35,11 @@ func setupAgentAuthTestDB(t *testing.T) func() {
|
||||
&model.SystemConfig{},
|
||||
))
|
||||
|
||||
db.SetDB(sqliteDB)
|
||||
repository.SetDBForTest(sqliteDB)
|
||||
tokenCache.reset()
|
||||
|
||||
return func() {
|
||||
db.SetDB(nil)
|
||||
repository.SetDBForTest(nil)
|
||||
tokenCache.reset()
|
||||
}
|
||||
}
|
||||
@@ -51,7 +50,7 @@ func TestAuthenticateAccessToken(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
now := time.Now()
|
||||
require.NoError(t, db.DB(ctx).Create(&model.OpenFlareNode{
|
||||
require.NoError(t, repository.DB(ctx).Create(&model.OpenFlareNode{
|
||||
NodeID: "node-auth-1",
|
||||
Name: "edge",
|
||||
AccessToken: "valid-agent-token",
|
||||
@@ -98,7 +97,7 @@ func TestAgentAuthMiddleware(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
now := time.Now()
|
||||
require.NoError(t, db.DB(ctx).Create(&model.OpenFlareNode{
|
||||
require.NoError(t, repository.DB(ctx).Create(&model.OpenFlareNode{
|
||||
NodeID: "node-mw-1",
|
||||
Name: "edge",
|
||||
AccessToken: "middleware-token",
|
||||
@@ -145,7 +144,7 @@ func TestAgentRegisterAuthMiddleware(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
now := time.Now()
|
||||
require.NoError(t, db.DB(ctx).Create(&model.OpenFlareNode{
|
||||
require.NoError(t, repository.DB(ctx).Create(&model.OpenFlareNode{
|
||||
NodeID: "node-register-1",
|
||||
Name: "edge",
|
||||
AccessToken: "existing-node-token",
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
"Wavelet/openflare/share/protocol"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -34,9 +33,9 @@ func setupWAFIPGroupTestDB(t *testing.T) func() {
|
||||
&model.ConfigVersion{},
|
||||
))
|
||||
|
||||
db.SetDB(sqliteDB)
|
||||
repository.SetDBForTest(sqliteDB)
|
||||
return func() {
|
||||
db.SetDB(nil)
|
||||
repository.SetDBForTest(nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +59,7 @@ func seedActiveConfigWithWAFIPGroup(t *testing.T, ctx context.Context, ipGroupID
|
||||
snapshotJSON, err := json.Marshal(snapshot)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, db.DB(ctx).Create(&model.ConfigVersion{
|
||||
require.NoError(t, repository.DB(ctx).Create(&model.ConfigVersion{
|
||||
Version: "20260618-001",
|
||||
SnapshotJSON: string(snapshotJSON),
|
||||
Checksum: "test-checksum",
|
||||
@@ -108,7 +107,7 @@ func seedActiveConfigWithWAFGraphIPGroup(t *testing.T, ctx context.Context, ipGr
|
||||
snapshotJSON, err := json.Marshal(snapshot)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, db.DB(ctx).Create(&model.ConfigVersion{
|
||||
require.NoError(t, repository.DB(ctx).Create(&model.ConfigVersion{
|
||||
Version: "20260713-graph-001",
|
||||
SnapshotJSON: string(snapshotJSON),
|
||||
Checksum: "graph-test-checksum",
|
||||
@@ -142,7 +141,7 @@ func TestChangedWAFIPGroupsForAgentRejectsMalformedIPMatchConfig(t *testing.T) {
|
||||
defer cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
require.NoError(t, db.DB(ctx).Create(&model.ConfigVersion{
|
||||
require.NoError(t, repository.DB(ctx).Create(&model.ConfigVersion{
|
||||
Version: "20260713-malformed-001",
|
||||
SnapshotJSON: `{"waf":{"rule_groups":[{"id":7,"graph":{"entry":"match","nodes":{` +
|
||||
`"match":{"type":"ip_match","config":{"ip_group_ids":"not-an-array"}}}}}],"bindings":[]}}`,
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -23,8 +22,8 @@ func TestUptimeKumaSyncHandlerSkipsWhenDisabled(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, sqliteDB.AutoMigrate(&model.SystemConfig{}))
|
||||
db.SetDB(sqliteDB)
|
||||
t.Cleanup(func() { db.SetDB(nil) })
|
||||
repository.SetDBForTest(sqliteDB)
|
||||
t.Cleanup(func() { repository.SetDBForTest(nil) })
|
||||
|
||||
ctx := context.Background()
|
||||
require.NoError(t, repository.SaveOrUpdateSystemConfig(ctx, model.ConfigKeyUptimeKumaEnabled, "false"))
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
"Wavelet/pkg/response"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/glebarez/sqlite"
|
||||
@@ -30,10 +29,10 @@ func setupFlaredMiddlewareTestDB(t *testing.T) func() {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, sqliteDB.AutoMigrate(&model.OpenFlareNode{}))
|
||||
db.SetDB(sqliteDB)
|
||||
repository.SetDBForTest(sqliteDB)
|
||||
|
||||
return func() {
|
||||
db.SetDB(nil)
|
||||
repository.SetDBForTest(nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
|
||||
"Wavelet/openflare/plugins/server/domain/fleet/agent"
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -33,11 +32,11 @@ func setupFlaredObservabilityTestDB(t *testing.T) func() {
|
||||
&model.ConfigVersion{},
|
||||
))
|
||||
|
||||
db.SetDB(sqliteDB)
|
||||
repository.SetDBForTest(sqliteDB)
|
||||
agent.ResetAuthCacheForTest()
|
||||
|
||||
return func() {
|
||||
db.SetDB(nil)
|
||||
repository.SetDBForTest(nil)
|
||||
agent.ResetAuthCacheForTest()
|
||||
}
|
||||
}
|
||||
@@ -54,7 +53,7 @@ func TestHeartbeatFlaredEmitsHealthEventOnUnhealthy(t *testing.T) {
|
||||
Status: "pending",
|
||||
NodeType: "tunnel_client",
|
||||
}
|
||||
require.NoError(t, db.DB(ctx).Create(node).Error)
|
||||
require.NoError(t, repository.DB(ctx).Create(node).Error)
|
||||
|
||||
_, err := Heartbeat(ctx, node, HeartbeatPayload{
|
||||
ClientVersion: "v0.2.0",
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
ofnode "Wavelet/openflare/plugins/server/domain/fleet/node"
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/glebarez/sqlite"
|
||||
@@ -39,7 +38,7 @@ func setupProtocolTestEnv(t *testing.T) (*gin.Engine, func()) {
|
||||
&model.ConfigVersion{},
|
||||
))
|
||||
|
||||
db.SetDB(sqliteDB)
|
||||
repository.SetDBForTest(sqliteDB)
|
||||
agent.ResetAuthCacheForTest()
|
||||
testhelper.SetupLogStoresForTest(t)
|
||||
|
||||
@@ -47,7 +46,7 @@ func setupProtocolTestEnv(t *testing.T) (*gin.Engine, func()) {
|
||||
mountOpenFlareTestRoutes(engine)
|
||||
|
||||
cleanup := func() {
|
||||
db.SetDB(nil)
|
||||
repository.SetDBForTest(nil)
|
||||
agent.ResetAuthCacheForTest()
|
||||
}
|
||||
return engine, cleanup
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
|
||||
"Wavelet/openflare/plugins/server/domain/fleet/agent"
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/glebarez/sqlite"
|
||||
@@ -55,7 +55,7 @@ func setupCoreChainTest(t *testing.T) (*gin.Engine, adminSeed, func()) {
|
||||
&model.ZoneDomain{},
|
||||
))
|
||||
|
||||
db.SetDB(sqliteDB)
|
||||
repository.SetDBForTest(sqliteDB)
|
||||
agent.ResetAuthCacheForTest()
|
||||
|
||||
seed, err := seedAdminWithAccessToken(sqliteDB)
|
||||
@@ -65,7 +65,7 @@ func setupCoreChainTest(t *testing.T) (*gin.Engine, adminSeed, func()) {
|
||||
mountOpenFlareTestRoutes(engine)
|
||||
|
||||
cleanup := func() {
|
||||
db.SetDB(nil)
|
||||
repository.SetDBForTest(nil)
|
||||
agent.ResetAuthCacheForTest()
|
||||
}
|
||||
|
||||
@@ -144,12 +144,12 @@ func TestCoreChainMigrationFlow(t *testing.T) {
|
||||
t.Run("create proxy route linked to origin", func(t *testing.T) {
|
||||
// Create Zone and ZoneDomain directly in the DB
|
||||
zone := model.Zone{Domain: "example.com"}
|
||||
require.NoError(t, db.DB(context.Background()).Create(&zone).Error)
|
||||
require.NoError(t, repository.DB(context.Background()).Create(&zone).Error)
|
||||
zoneDomain := model.ZoneDomain{
|
||||
ZoneID: zone.ID,
|
||||
Domain: "core-chain.example.com",
|
||||
}
|
||||
require.NoError(t, db.DB(context.Background()).Create(&zoneDomain).Error)
|
||||
require.NoError(t, repository.DB(context.Background()).Create(&zoneDomain).Error)
|
||||
|
||||
rec := performJSONRequest(t, engine, http.MethodPost, apiPath("/proxy-routes/"), map[string]any{
|
||||
"site_name": "core-chain-site",
|
||||
|
||||
@@ -17,9 +17,9 @@ import (
|
||||
"time"
|
||||
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||
"Wavelet/openflare/plugins/server/kernel/runtimeconfig"
|
||||
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/glebarez/sqlite"
|
||||
@@ -51,7 +51,7 @@ func setupSecurityTest(t *testing.T) (*gin.Engine, adminSeed, func()) {
|
||||
&model.SystemConfig{},
|
||||
))
|
||||
|
||||
db.SetDB(sqliteDB)
|
||||
repository.SetDBForTest(sqliteDB)
|
||||
|
||||
seed, err := seedAdminWithAccessToken(sqliteDB)
|
||||
require.NoError(t, err)
|
||||
@@ -64,7 +64,7 @@ func setupSecurityTest(t *testing.T) (*gin.Engine, adminSeed, func()) {
|
||||
|
||||
cleanup := func() {
|
||||
runtimeconfig.Set(previous)
|
||||
db.SetDB(nil)
|
||||
repository.SetDBForTest(nil)
|
||||
}
|
||||
|
||||
return engine, seed, cleanup
|
||||
@@ -205,12 +205,12 @@ func TestSecurityWAFTLSMigrationFlow(t *testing.T) {
|
||||
t.Run("create proxy route for WAF binding", func(t *testing.T) {
|
||||
// Create Zone and ZoneDomain directly in the DB
|
||||
routeZone := model.Zone{Domain: "example-route.com"}
|
||||
require.NoError(t, db.DB(context.Background()).Create(&routeZone).Error)
|
||||
require.NoError(t, repository.DB(context.Background()).Create(&routeZone).Error)
|
||||
routeZoneDomain := model.ZoneDomain{
|
||||
ZoneID: routeZone.ID,
|
||||
Domain: "route.example-route.com",
|
||||
}
|
||||
require.NoError(t, db.DB(context.Background()).Create(&routeZoneDomain).Error)
|
||||
require.NoError(t, repository.DB(context.Background()).Create(&routeZoneDomain).Error)
|
||||
|
||||
rec := performJSONRequest(t, engine, http.MethodPost, apiPath("/proxy-routes/"), map[string]any{
|
||||
"site_name": "security-site",
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -40,13 +39,15 @@ func setupNodeTestDB(t *testing.T) func() {
|
||||
&model.OpenFlareNode{},
|
||||
&model.SystemConfig{},
|
||||
&model.OpenFlareApplyLog{},
|
||||
&model.OpenFlareNodeSystemProfile{},
|
||||
&model.OpenFlareHealthEvent{},
|
||||
))
|
||||
|
||||
db.SetDB(sqliteDB)
|
||||
repository.SetDBForTest(sqliteDB)
|
||||
testhelper.SetupLogStoresForTest(t)
|
||||
|
||||
return func() {
|
||||
db.SetDB(nil)
|
||||
repository.SetDBForTest(nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +175,7 @@ func TestListNodesWithApplyLogMetadata(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
applyAt := time.Now().UTC().Truncate(time.Second)
|
||||
require.NoError(t, db.DB(ctx).Create(&model.OpenFlareApplyLog{
|
||||
require.NoError(t, repository.DB(ctx).Create(&model.OpenFlareApplyLog{
|
||||
NodeID: created.NodeID,
|
||||
Version: "20260618-001",
|
||||
Result: "success",
|
||||
@@ -280,7 +281,7 @@ func TestRequestOpenrestyRestart(t *testing.T) {
|
||||
|
||||
func seedActiveConfigVersion(t *testing.T, ctx context.Context) {
|
||||
t.Helper()
|
||||
conn := db.DB(ctx)
|
||||
conn := repository.DB(ctx)
|
||||
require.NotNil(t, conn)
|
||||
require.NoError(t, conn.AutoMigrate(&model.ConfigVersion{}))
|
||||
require.NoError(t, conn.Create(&model.ConfigVersion{
|
||||
@@ -311,7 +312,7 @@ func TestRequestForceSyncRequiresActiveConfig(t *testing.T) {
|
||||
cleanup := setupNodeTestDB(t)
|
||||
defer cleanup()
|
||||
ctx := context.Background()
|
||||
conn := db.DB(ctx)
|
||||
conn := repository.DB(ctx)
|
||||
require.NotNil(t, conn)
|
||||
require.NoError(t, conn.AutoMigrate(&model.ConfigVersion{}))
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
|
||||
"Wavelet/openflare/plugins/server/domain/fleet/agent"
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -38,12 +37,12 @@ func setupRelayTestDB(t *testing.T) func() {
|
||||
&model.OpenFlareNodeObservationFrps{},
|
||||
))
|
||||
|
||||
db.SetDB(sqliteDB)
|
||||
repository.SetDBForTest(sqliteDB)
|
||||
agent.ResetAuthCacheForTest()
|
||||
testhelper.SetupLogStoresForTest(t)
|
||||
|
||||
return func() {
|
||||
db.SetDB(nil)
|
||||
repository.SetDBForTest(nil)
|
||||
agent.ResetAuthCacheForTest()
|
||||
}
|
||||
}
|
||||
@@ -63,7 +62,7 @@ func TestHeartbeatPayloadBindingAndFrpsObservationInsert(t *testing.T) {
|
||||
NodeType: "tunnel_relay",
|
||||
RelayStatus: "unknown",
|
||||
}
|
||||
require.NoError(t, db.DB(ctx).Create(node).Error)
|
||||
require.NoError(t, repository.DB(ctx).Create(node).Error)
|
||||
|
||||
proxies := []ProxyStat{
|
||||
{
|
||||
@@ -103,7 +102,7 @@ func TestHeartbeatPayloadBindingAndFrpsObservationInsert(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
var stored model.OpenFlareNode
|
||||
require.NoError(t, db.DB(ctx).Where("node_id = ?", node.NodeID).First(&stored).Error)
|
||||
require.NoError(t, repository.DB(ctx).Where("node_id = ?", node.NodeID).First(&stored).Error)
|
||||
assert.Equal(t, "online", stored.Status)
|
||||
assert.Equal(t, "healthy", stored.RelayStatus)
|
||||
assert.Equal(t, "203.0.113.9", stored.IP)
|
||||
@@ -147,7 +146,7 @@ func TestHeartbeatRelayReconcilesFrpsUnhealthyEvent(t *testing.T) {
|
||||
NodeType: "tunnel_relay",
|
||||
RelayStatus: "healthy",
|
||||
}
|
||||
require.NoError(t, db.DB(ctx).Create(node).Error)
|
||||
require.NoError(t, repository.DB(ctx).Create(node).Error)
|
||||
|
||||
_, err := Heartbeat(ctx, node, HeartbeatPayload{
|
||||
Version: "v0.1.0",
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
"Wavelet/pkg/response"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/glebarez/sqlite"
|
||||
@@ -30,10 +29,10 @@ func setupRelayMiddlewareTestDB(t *testing.T) func() {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, sqliteDB.AutoMigrate(&model.OpenFlareNode{}))
|
||||
db.SetDB(sqliteDB)
|
||||
repository.SetDBForTest(sqliteDB)
|
||||
|
||||
return func() {
|
||||
db.SetDB(nil)
|
||||
repository.SetDBForTest(nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"Wavelet/pkg/util"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -64,7 +66,7 @@ func ServeAgent(c *gin.Context, nodeID string, onStatus AgentStatusHandler) {
|
||||
|
||||
slog.Debug("agent ws connected", "node_id", nodeID, "remote", client.remoteAddr)
|
||||
|
||||
go client.writePump()
|
||||
util.Go(client.writePump)
|
||||
client.readPump()
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"Wavelet/pkg/util"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -51,7 +53,7 @@ func ServeFlared(c *gin.Context, nodeID string) {
|
||||
|
||||
slog.Debug("flared ws connected", "node_id", nodeID, "remote", c.Request.RemoteAddr)
|
||||
|
||||
go client.writePump()
|
||||
util.Go(client.writePump)
|
||||
client.readPump()
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"Wavelet/pkg/util"
|
||||
)
|
||||
|
||||
// RelayWSConnectedLastSeenValue is the sentinel last_seen_at value when relay WS is connected.
|
||||
@@ -45,7 +47,7 @@ func ServeRelay(c *gin.Context, nodeID string) {
|
||||
|
||||
slog.Debug("relay ws connected", "node_id", nodeID, "remote", c.Request.RemoteAddr)
|
||||
|
||||
go client.writePump()
|
||||
util.Go(client.writePump)
|
||||
client.readPump()
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
|
||||
"Wavelet/openflare/plugins/server/domain/observability/chwriter"
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
)
|
||||
|
||||
// Run with Docker ClickHouse + config.yaml:
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||
"Wavelet/openflare/plugins/server/kernel/repository/logstore"
|
||||
"Wavelet/openflare/plugins/server/kernel/runtimeconfig"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
)
|
||||
|
||||
var logDBSwitchDBSeq int64
|
||||
@@ -57,13 +56,13 @@ func TestCopyAccessLogsPreservesIDs(t *testing.T) {
|
||||
srcDB := newLogDBSwitchDB(t)
|
||||
dstDB := newLogDBSwitchDB(t)
|
||||
|
||||
db.SetDB(srcDB)
|
||||
repository.SetDBForTest(srcDB)
|
||||
src, err := logstore.Active(ctx) // 无 reader 时按 seed 规则解析为 sqlite
|
||||
require.NoError(t, err)
|
||||
db.SetDB(dstDB)
|
||||
repository.SetDBForTest(dstDB)
|
||||
dst, err := logstore.BuildForMigration(ctx, "sqlite")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { db.SetDB(nil) })
|
||||
t.Cleanup(func() { repository.SetDBForTest(nil) })
|
||||
|
||||
now := time.Now().UTC()
|
||||
rows := []analyticsmodel.NodeAccessLog{
|
||||
@@ -103,13 +102,13 @@ func TestCopyUserAccessLogsPreservesIDs(t *testing.T) {
|
||||
srcDB := newLogDBSwitchDB(t)
|
||||
dstDB := newLogDBSwitchDB(t)
|
||||
|
||||
db.SetDB(srcDB)
|
||||
repository.SetDBForTest(srcDB)
|
||||
src, err := logstore.Active(ctx)
|
||||
require.NoError(t, err)
|
||||
db.SetDB(dstDB)
|
||||
repository.SetDBForTest(dstDB)
|
||||
dst, err := logstore.BuildForMigration(ctx, "sqlite")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { db.SetDB(nil) })
|
||||
t.Cleanup(func() { repository.SetDBForTest(nil) })
|
||||
|
||||
now := time.Now().UTC()
|
||||
rows := []analyticsmodel.UserAccessLog{
|
||||
@@ -142,8 +141,8 @@ func TestClearTargetLogTablesClearsUserAccessLogs(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
dstDB := newLogDBSwitchDB(t)
|
||||
db.SetDB(dstDB)
|
||||
t.Cleanup(func() { db.SetDB(nil) })
|
||||
repository.SetDBForTest(dstDB)
|
||||
t.Cleanup(func() { repository.SetDBForTest(nil) })
|
||||
dst, err := logstore.BuildForMigration(ctx, "sqlite")
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -168,8 +167,8 @@ func TestClearTargetLogTablesDuringMigration(t *testing.T) {
|
||||
defer logstore.ResetForTest()
|
||||
|
||||
gdb := newLogDBSwitchDB(t)
|
||||
db.SetDB(gdb)
|
||||
t.Cleanup(func() { db.SetDB(nil) })
|
||||
repository.SetDBForTest(gdb)
|
||||
t.Cleanup(func() { repository.SetDBForTest(nil) })
|
||||
ctx := context.Background()
|
||||
|
||||
logstore.SetConfigReader(func(ctx context.Context, key string) (string, error) {
|
||||
@@ -205,8 +204,8 @@ func TestValidateSwitch(t *testing.T) {
|
||||
})
|
||||
|
||||
gdb := newLogDBSwitchDB(t)
|
||||
db.SetDB(gdb)
|
||||
t.Cleanup(func() { db.SetDB(nil) })
|
||||
repository.SetDBForTest(gdb)
|
||||
t.Cleanup(func() { repository.SetDBForTest(nil) })
|
||||
ctx := context.Background()
|
||||
setLogDB := func(v string) {
|
||||
require.NoError(t, repository.SaveOrUpdateSystemConfig(ctx, model.ConfigKeyLogDatabase, v))
|
||||
@@ -288,8 +287,8 @@ func TestExecuteFailureClearsMigrationFlag(t *testing.T) {
|
||||
defer logstore.ResetForTest()
|
||||
|
||||
gdb := newLogDBSwitchDB(t)
|
||||
db.SetDB(gdb)
|
||||
t.Cleanup(func() { db.SetDB(nil) })
|
||||
repository.SetDBForTest(gdb)
|
||||
t.Cleanup(func() { repository.SetDBForTest(nil) })
|
||||
ctx := context.Background()
|
||||
|
||||
// FRESH DB:log_db_migration 行不存在(不预置),log_database 预置为 sqlite。
|
||||
@@ -323,8 +322,8 @@ func TestSetMigrationFlagObservableThroughCache(t *testing.T) {
|
||||
defer logstore.ResetForTest()
|
||||
|
||||
gdb := newLogDBSwitchDB(t)
|
||||
db.SetDB(gdb)
|
||||
t.Cleanup(func() { db.SetDB(nil) })
|
||||
repository.SetDBForTest(gdb)
|
||||
t.Cleanup(func() { repository.SetDBForTest(nil) })
|
||||
ctx := context.Background()
|
||||
|
||||
// 按 bootstrap 同款注入 repository 读取,走 RAM 缓存路径。
|
||||
@@ -353,8 +352,8 @@ func TestFlipLogDatabaseRefreshesCachedConfig(t *testing.T) {
|
||||
defer logstore.ResetForTest()
|
||||
|
||||
gdb := newLogDBSwitchDB(t)
|
||||
db.SetDB(gdb)
|
||||
t.Cleanup(func() { db.SetDB(nil) })
|
||||
repository.SetDBForTest(gdb)
|
||||
t.Cleanup(func() { repository.SetDBForTest(nil) })
|
||||
ctx := context.Background()
|
||||
|
||||
logstore.SetConfigReader(func(ctx context.Context, key string) (string, error) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -26,7 +26,8 @@ func setupOptionTestDB(t *testing.T) func() {
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, sqliteDB.AutoMigrate(&model.SystemConfig{}))
|
||||
|
||||
db.SetDB(sqliteDB)
|
||||
repository.SetDBForTest(sqliteDB)
|
||||
repository.SetSystemConfigService(testhelper.NewMockSystemConfigService(sqliteDB))
|
||||
|
||||
// 预填充一些业务配置用于测试
|
||||
seedConfigs := []model.SystemConfig{
|
||||
@@ -38,14 +39,15 @@ func setupOptionTestDB(t *testing.T) func() {
|
||||
}
|
||||
|
||||
return func() {
|
||||
db.SetDB(nil)
|
||||
repository.SetSystemConfigService(nil)
|
||||
repository.SetDBForTest(nil)
|
||||
}
|
||||
}
|
||||
|
||||
// setTestConfig 设置测试配置的辅助函数
|
||||
func setTestConfig(t *testing.T, ctx context.Context, key, value string) {
|
||||
t.Helper()
|
||||
require.NoError(t, db.DB(ctx).Model(&model.SystemConfig{}).Where("key = ?", key).Update("value", value).Error)
|
||||
require.NoError(t, repository.DB(ctx).Model(&model.SystemConfig{}).Where("key = ?", key).Update("value", value).Error)
|
||||
}
|
||||
|
||||
func TestListOptionsFiltersSecretKeys(t *testing.T) {
|
||||
@@ -89,7 +91,7 @@ func TestUpdateOpenRestyOptionPersistsToSystemConfig(t *testing.T) {
|
||||
defer cleanup()
|
||||
ctx := context.Background()
|
||||
|
||||
require.NoError(t, db.DB(ctx).Create(&model.SystemConfig{
|
||||
require.NoError(t, repository.DB(ctx).Create(&model.SystemConfig{
|
||||
Key: model.ConfigKeyOpenRestyEventsUse,
|
||||
Value: "epoll",
|
||||
Type: "business",
|
||||
|
||||
@@ -16,6 +16,8 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"Wavelet/pkg/util"
|
||||
)
|
||||
|
||||
const emitAckTimeout = 10 * time.Second
|
||||
@@ -137,7 +139,7 @@ func (c *SocketIOClient) Connect() error {
|
||||
_ = respConnect.Body.Close()
|
||||
slog.Debug("Namespace connected successfully to Uptime Kuma", "sid", c.sid)
|
||||
|
||||
go c.pollLoop()
|
||||
util.Go(c.pollLoop)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -125,17 +125,19 @@ func setupSyncTestDB(t *testing.T) func() {
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, sqliteDB.AutoMigrate(&model.ProxyRoute{}, &model.Zone{}, &model.ZoneDomain{}, &model.SystemConfig{}))
|
||||
|
||||
db.SetDB(sqliteDB)
|
||||
repository.SetDBForTest(sqliteDB)
|
||||
repository.SetSystemConfigService(testhelper.NewMockSystemConfigService(sqliteDB))
|
||||
return func() {
|
||||
db.SetDB(nil)
|
||||
repository.SetSystemConfigService(nil)
|
||||
repository.SetDBForTest(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func createRouteZoneDomain(t *testing.T, ctx context.Context, route *model.ProxyRoute, domain string) {
|
||||
t.Helper()
|
||||
zone := &model.Zone{Domain: fmt.Sprintf("zone-%d.example", route.ID)}
|
||||
require.NoError(t, db.DB(ctx).Create(zone).Error)
|
||||
require.NoError(t, db.DB(ctx).Create(&model.ZoneDomain{
|
||||
require.NoError(t, repository.DB(ctx).Create(zone).Error)
|
||||
require.NoError(t, repository.DB(ctx).Create(&model.ZoneDomain{
|
||||
ZoneID: zone.ID,
|
||||
ProxyRouteID: &route.ID,
|
||||
Domain: domain,
|
||||
@@ -166,7 +168,7 @@ func backupUptimeKumaConfig(ctx context.Context) func() {
|
||||
return func() {
|
||||
// 恢复所有配置
|
||||
for key, value := range oldValues {
|
||||
_ = db.DB(ctx).Model(&model.SystemConfig{}).Where("key = ?", key).Update("value", value).Error
|
||||
_ = repository.DB(ctx).Model(&model.SystemConfig{}).Where("key = ?", key).Update("value", value).Error
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,7 +199,7 @@ func TestSyncToUptimeKumaSuccess(t *testing.T) {
|
||||
restore := backupUptimeKumaConfig(ctx)
|
||||
defer restore()
|
||||
|
||||
require.NoError(t, db.DB(ctx).Where("1 = 1").Delete(&model.ProxyRoute{}).Error)
|
||||
require.NoError(t, repository.DB(ctx).Where("1 = 1").Delete(&model.ProxyRoute{}).Error)
|
||||
|
||||
routeA := &model.ProxyRoute{
|
||||
SiteName: "site-a",
|
||||
@@ -306,7 +308,7 @@ func TestSyncToUptimeKumaSelectedScope(t *testing.T) {
|
||||
restore := backupUptimeKumaConfig(ctx)
|
||||
defer restore()
|
||||
|
||||
require.NoError(t, db.DB(ctx).Where("1 = 1").Delete(&model.ProxyRoute{}).Error)
|
||||
require.NoError(t, repository.DB(ctx).Where("1 = 1").Delete(&model.ProxyRoute{}).Error)
|
||||
|
||||
routeA := &model.ProxyRoute{
|
||||
SiteName: "site-a",
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||
)
|
||||
|
||||
func TestGitHubSourceIdentityLengthPrefixesFieldsAndResetsRuntime(t *testing.T) {
|
||||
@@ -57,7 +57,7 @@ func TestGitHubSourceIdentityLengthPrefixesFieldsAndResetsRuntime(t *testing.T)
|
||||
syncedAt := time.Now().Add(-30 * time.Second)
|
||||
nextCheckAt := time.Now().Add(time.Hour)
|
||||
leaseExpiresAt := time.Now().Add(time.Minute)
|
||||
if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||
if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||
Where("source_id = ?", firstSource.ID).
|
||||
Updates(map[string]any{
|
||||
"etag": `"old-etag"`,
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||
"Wavelet/openflare/plugins/server/kernel/task"
|
||||
"Wavelet/openflare/share/githubrelease"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"github.com/hibiken/asynq"
|
||||
"gorm.io/gorm"
|
||||
@@ -62,7 +61,7 @@ func mustConfigureGitHubSourceWithoutDispatch(
|
||||
if err := validateGitHubSourceInput(input); err != nil {
|
||||
t.Fatalf("validateGitHubSourceInput(%+v) error = %v, want nil", input, err)
|
||||
}
|
||||
if err := db.DB(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := repository.DB(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
_, err := updateGitHubSourceTx(tx, projectID, input)
|
||||
return err
|
||||
}); err != nil {
|
||||
@@ -78,11 +77,11 @@ func mustLoadPagesSource(
|
||||
) (*model.PagesProjectSource, *model.PagesProjectSourceRuntime) {
|
||||
t.Helper()
|
||||
var source model.PagesProjectSource
|
||||
if err := db.DB(ctx).Where("project_id = ?", projectID).First(&source).Error; err != nil {
|
||||
if err := repository.DB(ctx).Where("project_id = ?", projectID).First(&source).Error; err != nil {
|
||||
t.Fatalf("load source for project %d error = %v, want nil", projectID, err)
|
||||
}
|
||||
var runtime model.PagesProjectSourceRuntime
|
||||
if err := db.DB(ctx).Where("source_id = ?", source.ID).First(&runtime).Error; err != nil {
|
||||
if err := repository.DB(ctx).Where("source_id = ?", source.ID).First(&runtime).Error; err != nil {
|
||||
t.Fatalf("load runtime for source %d error = %v, want nil", source.ID, err)
|
||||
}
|
||||
return &source, &runtime
|
||||
@@ -136,14 +135,14 @@ func TestGitHubSourceValidationNormalizationAndProviderSwitch(t *testing.T) {
|
||||
}
|
||||
|
||||
var taskCount int64
|
||||
if err := db.DB(ctx).Model(&model.TaskExecution{}).Count(&taskCount).Error; err != nil {
|
||||
if err := repository.DB(ctx).Model(&model.TaskExecution{}).Count(&taskCount).Error; err != nil {
|
||||
t.Fatalf("count initial checks error = %v, want nil", err)
|
||||
}
|
||||
if _, err := UpdateSourceAs(ctx, project.ID, input, "user:42"); err != nil {
|
||||
t.Fatalf("UpdateSourceAs(GitHub no-op) error = %v, want nil", err)
|
||||
}
|
||||
var noOpTaskCount int64
|
||||
if err := db.DB(ctx).Model(&model.TaskExecution{}).Count(&noOpTaskCount).Error; err != nil {
|
||||
if err := repository.DB(ctx).Model(&model.TaskExecution{}).Count(&noOpTaskCount).Error; err != nil {
|
||||
t.Fatalf("count no-op checks error = %v, want nil", err)
|
||||
}
|
||||
if noOpTaskCount != taskCount {
|
||||
@@ -275,7 +274,7 @@ func TestInitialCheckFailureUsesExactConfigFence(t *testing.T) {
|
||||
RepositoryURL: "https://github.com/a/b",
|
||||
})
|
||||
staleVersion := source.ConfigVersion
|
||||
if err := db.DB(ctx).Model(source).Update("config_version", staleVersion+1).Error; err != nil {
|
||||
if err := repository.DB(ctx).Model(source).Update("config_version", staleVersion+1).Error; err != nil {
|
||||
t.Fatalf("increment source config version error = %v, want nil", err)
|
||||
}
|
||||
markInitialCheckDispatchFailed(ctx, source.ID, staleVersion)
|
||||
@@ -294,7 +293,7 @@ func TestGitHubCheckUsesETagAndDetectsSameReleaseReplacement(t *testing.T) {
|
||||
})
|
||||
appliedRevision := strings.Repeat("a", 64)
|
||||
appliedDetail := `{"provider":"github","release_id":"100","asset_id":"1","tag":"release/v1","asset_name":"dist.zip"}`
|
||||
if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).Where("source_id = ?", source.ID).Updates(map[string]any{
|
||||
if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).Where("source_id = ?", source.ID).Updates(map[string]any{
|
||||
"etag": `"old-etag"`,
|
||||
"last_applied_revision": appliedRevision,
|
||||
"last_applied_detail": appliedDetail,
|
||||
@@ -375,7 +374,7 @@ func TestGitHubCheckNotModifiedRefreshesRuntimeWithoutDeployment(t *testing.T) {
|
||||
t.Errorf("304 runtime = %+v, want refreshed timestamps/etag and released lease", runtime)
|
||||
}
|
||||
var deployments int64
|
||||
if err := db.DB(ctx).Model(&model.PagesDeployment{}).Where("project_id = ?", project.ID).Count(&deployments).Error; err != nil {
|
||||
if err := repository.DB(ctx).Model(&model.PagesDeployment{}).Where("project_id = ?", project.ID).Count(&deployments).Error; err != nil {
|
||||
t.Fatalf("count deployments error = %v, want nil", err)
|
||||
}
|
||||
if deployments != 0 {
|
||||
@@ -391,7 +390,7 @@ func TestGitHubTargetMismatchPreservesAttentionAndExpeditesRecheck(t *testing.T)
|
||||
RepositoryURL: "https://github.com/a/b",
|
||||
})
|
||||
appliedRevision := strings.Repeat("a", 64)
|
||||
if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).Where("source_id = ?", source.ID).Updates(map[string]any{
|
||||
if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).Where("source_id = ?", source.ID).Updates(map[string]any{
|
||||
"last_applied_revision": appliedRevision,
|
||||
"last_applied_detail": `{"provider":"github","release_id":"100","asset_id":"1","tag":"v1","asset_name":"dist.zip"}`,
|
||||
}).Error; err != nil {
|
||||
@@ -430,7 +429,7 @@ func TestGitHubTargetMismatchPreservesAttentionAndExpeditesRecheck(t *testing.T)
|
||||
t.Errorf("target mismatch NextCheckAt = %v, want server deadline >= %v", runtime.NextCheckAt, retryAt)
|
||||
}
|
||||
var deployments int64
|
||||
if err := db.DB(ctx).Model(&model.PagesDeployment{}).Where("project_id = ?", project.ID).Count(&deployments).Error; err != nil {
|
||||
if err := repository.DB(ctx).Model(&model.PagesDeployment{}).Where("project_id = ?", project.ID).Count(&deployments).Error; err != nil {
|
||||
t.Fatalf("count mismatch deployments error = %v, want nil", err)
|
||||
}
|
||||
if deployments != 0 {
|
||||
@@ -454,7 +453,7 @@ func TestGitHubCheckLostLeaseReturnsStaleWithoutOverwritingRuntime(t *testing.T)
|
||||
},
|
||||
})
|
||||
snapshot, _, _ := acquireSourceLease(ctx, source.ID, source.ConfigVersion, sourceActionCheck)
|
||||
if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).Where("source_id = ?", source.ID).Updates(map[string]any{
|
||||
if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).Where("source_id = ?", source.ID).Updates(map[string]any{
|
||||
"lease_token": "new-owner",
|
||||
"lease_expires_at": time.Now().Add(time.Minute),
|
||||
"sync_status": pagesSourceStatusSyncing,
|
||||
@@ -741,7 +740,7 @@ func TestGitHubSyncActivatesExactConfirmedReplacement(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("buildGitHubSourceTarget() error = %v, want nil", err)
|
||||
}
|
||||
if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).Where("source_id = ?", source.ID).Updates(map[string]any{
|
||||
if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).Where("source_id = ?", source.ID).Updates(map[string]any{
|
||||
"last_seen_revision": target.Revision,
|
||||
"last_seen_detail": target.DetailJSON,
|
||||
"last_applied_revision": strings.Repeat("a", 64),
|
||||
|
||||
@@ -320,6 +320,7 @@ func removePagesUploadIfUnreferenced(ctx context.Context, projectID uint, upload
|
||||
if uploadID == 0 {
|
||||
return nil
|
||||
}
|
||||
shouldRemove := false
|
||||
err := repository.WithPagesTx(ctx, func(tx *gorm.DB) error {
|
||||
if projectID != 0 {
|
||||
if _, projectErr := repository.LockPagesProjectByIDTx(tx, projectID); projectErr != nil &&
|
||||
@@ -350,13 +351,13 @@ func removePagesUploadIfUnreferenced(ctx context.Context, projectID uint, upload
|
||||
if references > 0 {
|
||||
return nil
|
||||
}
|
||||
_, err := ofupload.RemoveLockedTx(tx, &uploadRecord)
|
||||
return err
|
||||
shouldRemove = true
|
||||
return nil
|
||||
})
|
||||
// Always invalidate after transaction completion, including idempotent no-op,
|
||||
// so a prior post-commit cache interruption can heal on retry.
|
||||
ofupload.InvalidateUploadMetaCache(ctx, uploadID)
|
||||
return err
|
||||
if err != nil || !shouldRemove {
|
||||
return err
|
||||
}
|
||||
return ofupload.Remove(ctx, uploadID)
|
||||
}
|
||||
|
||||
func inspectPagesPackage(packagePath string, format pagesarchive.Format, rootDir string, entryFile string, limits pagesLimits) (*deploymentManifest, error) {
|
||||
|
||||
@@ -1020,7 +1020,8 @@ func hydrateLegacyDeploymentUpload(
|
||||
if deployment.UploadID > 0 {
|
||||
uploadRecord, err := ofupload.GetActiveUpload(ctx, deployment.UploadID)
|
||||
if err == nil {
|
||||
return &uploadRecord, nil
|
||||
record := model.FromUploadDTO(uploadRecord)
|
||||
return &record, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1066,7 +1067,8 @@ func hydrateLegacyDeploymentUpload(
|
||||
}
|
||||
deployment.UploadID = winnerUploadID
|
||||
deployment.ArtifactPath = ""
|
||||
return &winner, nil
|
||||
winnerRecord := model.FromUploadDTO(winner)
|
||||
return &winnerRecord, nil
|
||||
}
|
||||
|
||||
func attachLegacyDeploymentUpload(
|
||||
|
||||
@@ -23,8 +23,6 @@ import (
|
||||
oftask "Wavelet/openflare/plugins/server/kernel/task"
|
||||
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
||||
"Wavelet/pkg/idgen"
|
||||
uploadshared "Wavelet/plugins/domain/upload/shared"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -39,6 +37,9 @@ func setupPagesTestDB(t *testing.T) func() {
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
if sqlDB, err := sqliteDB.DB(); err == nil {
|
||||
sqlDB.SetMaxOpenConns(1)
|
||||
}
|
||||
require.NoError(t, sqliteDB.AutoMigrate(
|
||||
&model.User{},
|
||||
&model.Upload{},
|
||||
@@ -74,30 +75,33 @@ func setupPagesTestDB(t *testing.T) func() {
|
||||
},
|
||||
}).Error)
|
||||
|
||||
db.SetDB(sqliteDB)
|
||||
repository.SetDBForTest(sqliteDB)
|
||||
repository.SetSystemConfigService(testhelper.NewMockSystemConfigService(sqliteDB))
|
||||
require.NoError(t, idgen.Init(1))
|
||||
oftask.SetService(&testhelper.NoopTaskService{})
|
||||
mockStorage := uploadshared.NewMockStorageService()
|
||||
uploadshared.SetDBService(db.NewService(sqliteDB))
|
||||
uploadshared.SetStorageService(mockStorage)
|
||||
noopTask := &testhelper.NoopTaskService{}
|
||||
oftask.SetService(noopTask)
|
||||
repository.SetTaskService(noopTask)
|
||||
mockStorage := testhelper.NewMockStorageService()
|
||||
ofupload.SetStorage(mockStorage)
|
||||
ofupload.SetUploadService(testhelper.NewMockUploadService(sqliteDB))
|
||||
_ = repository.InvalidateSystemConfigCache(context.Background(), model.ConfigKeyPagesMaxPackageSizeMB)
|
||||
_ = repository.InvalidateSystemConfigCache(context.Background(), model.ConfigKeyPagesMaxHistoryCount)
|
||||
return func() {
|
||||
ofupload.SetStorage(nil)
|
||||
uploadshared.ResetServices()
|
||||
db.SetDB(nil)
|
||||
ofupload.SetUploadService(nil)
|
||||
repository.SetTaskService(nil)
|
||||
oftask.SetService(nil)
|
||||
repository.SetSystemConfigService(nil)
|
||||
repository.SetDBForTest(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func setupPagesStorageMock(t *testing.T) (restore func(), disable func()) {
|
||||
t.Helper()
|
||||
mock := uploadshared.NewMockStorageService()
|
||||
uploadshared.SetStorageService(mock)
|
||||
mock := testhelper.NewMockStorageService()
|
||||
ofupload.SetStorage(mock)
|
||||
restore = func() {
|
||||
ofupload.SetStorage(nil)
|
||||
uploadshared.ResetServices()
|
||||
}
|
||||
disable = restore
|
||||
return restore, disable
|
||||
@@ -283,10 +287,10 @@ func TestUploadDeploymentStoresPackageInUploadFramework(t *testing.T) {
|
||||
assert.Empty(t, storedDeployment.ArtifactPath)
|
||||
|
||||
var uploadCount int64
|
||||
require.NoError(t, db.DB(ctx).Model(&model.Upload{}).Count(&uploadCount).Error)
|
||||
require.NoError(t, repository.DB(ctx).Model(&model.Upload{}).Count(&uploadCount).Error)
|
||||
assert.Equal(t, int64(1), uploadCount)
|
||||
var uploadRecord model.Upload
|
||||
require.NoError(t, db.DB(ctx).First(&uploadRecord, storedDeployment.UploadID).Error)
|
||||
require.NoError(t, repository.DB(ctx).First(&uploadRecord, storedDeployment.UploadID).Error)
|
||||
assert.Equal(t, ofupload.ReservedPagesDeploymentType, uploadRecord.Type)
|
||||
assert.Equal(t, pagesIngestMarkerV2, uploadRecord.Metadata.Extra[pagesIngestMarkerKey])
|
||||
assert.Equal(t, fmt.Sprint(project.ID), uploadRecord.Metadata.Extra[pagesProjectIDMetadataKey])
|
||||
@@ -323,8 +327,8 @@ func TestOpenDeploymentPackageHydratesLegacyArtifactPath(t *testing.T) {
|
||||
TotalSize: 10,
|
||||
CreatedBy: "test",
|
||||
}
|
||||
require.NoError(t, db.DB(ctx).Create(deployment).Error)
|
||||
require.NoError(t, db.DB(ctx).Create(&model.PagesDeploymentFile{
|
||||
require.NoError(t, repository.DB(ctx).Create(deployment).Error)
|
||||
require.NoError(t, repository.DB(ctx).Create(&model.PagesDeploymentFile{
|
||||
DeploymentID: deployment.ID,
|
||||
Path: "index.html",
|
||||
Size: 6,
|
||||
@@ -334,7 +338,7 @@ func TestOpenDeploymentPackageHydratesLegacyArtifactPath(t *testing.T) {
|
||||
_, err = ActivateDeployment(ctx, project.ID, deployment.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, db.DB(ctx).Create(&model.ConfigVersion{
|
||||
require.NoError(t, repository.DB(ctx).Create(&model.ConfigVersion{
|
||||
Version: "v2026-legacy",
|
||||
SnapshotJSON: fmt.Sprintf(`{"routes":[{"upstream_type":"pages","pages_deployment":{"deployment_id":%d}}]}`, deployment.ID),
|
||||
MainConfig: "",
|
||||
@@ -363,7 +367,7 @@ func TestOpenDeploymentPackageHydratesLegacyArtifactPath(t *testing.T) {
|
||||
assert.Empty(t, storedDeployment.ArtifactPath)
|
||||
|
||||
var uploadCount int64
|
||||
require.NoError(t, db.DB(ctx).Model(&model.Upload{}).Count(&uploadCount).Error)
|
||||
require.NoError(t, repository.DB(ctx).Model(&model.Upload{}).Count(&uploadCount).Error)
|
||||
assert.Equal(t, int64(1), uploadCount)
|
||||
|
||||
packageObj2, err := OpenDeploymentPackage(ctx, deployment.ID)
|
||||
@@ -400,7 +404,7 @@ func TestOpenDeploymentPackageRequiresActiveConfigSnapshot(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "激活配置")
|
||||
|
||||
require.NoError(t, db.DB(ctx).Create(&model.ConfigVersion{
|
||||
require.NoError(t, repository.DB(ctx).Create(&model.ConfigVersion{
|
||||
Version: "v2026-001",
|
||||
SnapshotJSON: fmt.Sprintf(`{"routes":[{"upstream_type":"pages","pages_deployment":{"deployment_id":%d}}]}`, deployment.ID),
|
||||
MainConfig: "",
|
||||
@@ -454,7 +458,7 @@ func TestProjectLatestRejectsWhenNotOnActiveConfigOrNotActive(t *testing.T) {
|
||||
_, _, err = GetProjectLatestPackageHash(ctx, project.ID)
|
||||
require.Error(t, err)
|
||||
|
||||
require.NoError(t, db.DB(ctx).Create(&model.ConfigVersion{
|
||||
require.NoError(t, repository.DB(ctx).Create(&model.ConfigVersion{
|
||||
Version: "v-gate",
|
||||
SnapshotJSON: fmt.Sprintf(
|
||||
`{"routes":[{"upstream_type":"pages","pages_project_id":%d,"pages_deployment":{"project_id":%d,"deployment_id":%d}}]}`,
|
||||
@@ -476,7 +480,7 @@ func TestProjectLatestRejectsWhenNotOnActiveConfigOrNotActive(t *testing.T) {
|
||||
assert.NotEmpty(t, hash)
|
||||
|
||||
// Disabled project rejects.
|
||||
require.NoError(t, db.DB(ctx).Model(&model.PagesProject{}).Where("id = ?", project.ID).Update("enabled", false).Error)
|
||||
require.NoError(t, repository.DB(ctx).Model(&model.PagesProject{}).Where("id = ?", project.ID).Update("enabled", false).Error)
|
||||
_, _, err = GetProjectLatestPackageHash(ctx, project.ID)
|
||||
require.Error(t, err)
|
||||
}
|
||||
@@ -572,7 +576,7 @@ func TestPruneProjectDeploymentHistory(t *testing.T) {
|
||||
defer disableStorage()
|
||||
ctx := context.Background()
|
||||
|
||||
require.NoError(t, db.DB(ctx).Model(&model.SystemConfig{}).
|
||||
require.NoError(t, repository.DB(ctx).Model(&model.SystemConfig{}).
|
||||
Where("key = ?", model.ConfigKeyPagesMaxHistoryCount).
|
||||
Update("value", "2").Error)
|
||||
require.NoError(t, repository.InvalidateSystemConfigCache(ctx, model.ConfigKeyPagesMaxHistoryCount))
|
||||
@@ -634,7 +638,7 @@ func TestHistoryCountOnePreservesFreshCandidateUntilActivation(t *testing.T) {
|
||||
defer disableStorage()
|
||||
ctx := context.Background()
|
||||
|
||||
require.NoError(t, db.DB(ctx).Model(&model.SystemConfig{}).
|
||||
require.NoError(t, repository.DB(ctx).Model(&model.SystemConfig{}).
|
||||
Where("key = ?", model.ConfigKeyPagesMaxHistoryCount).
|
||||
Update("value", "1").Error)
|
||||
require.NoError(t, repository.InvalidateSystemConfigCache(ctx, model.ConfigKeyPagesMaxHistoryCount))
|
||||
@@ -671,7 +675,7 @@ func TestHistoryCountOnePreservesFreshCandidateUntilActivation(t *testing.T) {
|
||||
assert.True(t, kept[newCandidate.ID])
|
||||
assert.False(t, kept[oldCandidate.ID])
|
||||
var removedUpload model.Upload
|
||||
require.NoError(t, db.DB(ctx).First(&removedUpload, oldCandidate.UploadID).Error)
|
||||
require.NoError(t, repository.DB(ctx).First(&removedUpload, oldCandidate.UploadID).Error)
|
||||
assert.Equal(t, model.UploadStatusDeleted, removedUpload.Status)
|
||||
|
||||
_, err = ActivateDeployment(ctx, project.ID, newCandidate.ID)
|
||||
@@ -741,12 +745,12 @@ func TestDeleteDeploymentAndProjectSoftDeleteUnreferencedArtifacts(t *testing.T)
|
||||
|
||||
require.NoError(t, DeleteDeployment(ctx, project.ID, second.ID))
|
||||
var secondUpload model.Upload
|
||||
require.NoError(t, db.DB(ctx).First(&secondUpload, second.UploadID).Error)
|
||||
require.NoError(t, repository.DB(ctx).First(&secondUpload, second.UploadID).Error)
|
||||
assert.Equal(t, model.UploadStatusDeleted, secondUpload.Status)
|
||||
|
||||
require.NoError(t, DeleteProject(ctx, project.ID))
|
||||
var firstUpload model.Upload
|
||||
require.NoError(t, db.DB(ctx).First(&firstUpload, first.UploadID).Error)
|
||||
require.NoError(t, repository.DB(ctx).First(&firstUpload, first.UploadID).Error)
|
||||
assert.Equal(t, model.UploadStatusDeleted, firstUpload.Status)
|
||||
_, err = repository.GetPagesProjectByID(ctx, project.ID)
|
||||
assert.Error(t, err)
|
||||
|
||||
@@ -58,7 +58,7 @@ func GetProjectLatestPackageMetadata(ctx context.Context, projectID uint) (*Proj
|
||||
return &ProjectLatestPackageMetadata{
|
||||
DeploymentID: deployment.ID,
|
||||
Hash: hash,
|
||||
PackageSize: uploadRecord.FileSize,
|
||||
PackageSize: uploadRecord.Size,
|
||||
FileCount: deployment.FileCount,
|
||||
TotalSize: deployment.TotalSize,
|
||||
}, nil
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||
)
|
||||
|
||||
func TestGetProjectLatestPackageMetadataPreservesZeroTotalSize(t *testing.T) {
|
||||
@@ -46,7 +46,7 @@ func TestGetProjectLatestPackageMetadataPreservesZeroTotalSize(t *testing.T) {
|
||||
if _, err := ActivateDeployment(ctx, project.ID, deployment.ID); err != nil {
|
||||
t.Fatalf("ActivateDeployment() error = %v", err)
|
||||
}
|
||||
if err := db.DB(ctx).Create(&model.ConfigVersion{
|
||||
if err := repository.DB(ctx).Create(&model.ConfigVersion{
|
||||
Version: "v-package-metadata",
|
||||
SnapshotJSON: fmt.Sprintf(
|
||||
`{"routes":[{"upstream_type":"pages","pages_project_id":%d}]}`,
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -36,7 +36,7 @@ func TestRebindSnapshotPagesToCurrentActive(t *testing.T) {
|
||||
Status: model.PagesDeploymentStatusUploaded,
|
||||
FileCount: 1,
|
||||
}
|
||||
require.NoError(t, db.DB(ctx).Create(old).Error)
|
||||
require.NoError(t, repository.DB(ctx).Create(old).Error)
|
||||
active := &model.PagesDeployment{
|
||||
ProjectID: project.ID,
|
||||
DeploymentNumber: 2,
|
||||
@@ -44,8 +44,8 @@ func TestRebindSnapshotPagesToCurrentActive(t *testing.T) {
|
||||
Status: model.PagesDeploymentStatusActive,
|
||||
FileCount: 1,
|
||||
}
|
||||
require.NoError(t, db.DB(ctx).Create(active).Error)
|
||||
require.NoError(t, db.DB(ctx).Model(&model.PagesProject{}).
|
||||
require.NoError(t, repository.DB(ctx).Create(active).Error)
|
||||
require.NoError(t, repository.DB(ctx).Model(&model.PagesProject{}).
|
||||
Where("id = ?", project.ID).
|
||||
Update("active_deployment_id", active.ID).Error)
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
|
||||
"Wavelet/core/contracts"
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -117,7 +117,7 @@ func TestPagesSourceHandlersReturnStableActionErrors(t *testing.T) {
|
||||
false,
|
||||
)
|
||||
future := time.Now().Add(time.Minute)
|
||||
if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||
if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||
Where("source_id = ?", busySource.ID).
|
||||
Updates(map[string]any{
|
||||
"sync_status": pagesSourceStatusSyncing,
|
||||
@@ -181,7 +181,7 @@ func TestSyncSourceHandlerAcceptsEmptyBodyAndEmptyObject(t *testing.T) {
|
||||
}
|
||||
|
||||
var executions []model.TaskExecution
|
||||
if err := db.DB(ctx).Where("task_type = ?", TaskTypePagesSourceAction).Order("id asc").Find(&executions).Error; err != nil {
|
||||
if err := repository.DB(ctx).Where("task_type = ?", TaskTypePagesSourceAction).Order("id asc").Find(&executions).Error; err != nil {
|
||||
t.Fatalf("list Pages source task executions error = %v, want nil", err)
|
||||
}
|
||||
if got, want := len(executions), 2; got != want {
|
||||
|
||||
@@ -131,7 +131,6 @@ func reconcilePagesOrphanUploadCandidate(
|
||||
}
|
||||
|
||||
outcome := pagesOrphanCleanupSkipped
|
||||
uploadLocked := false
|
||||
err := repository.WithPagesTx(ctx, func(tx *gorm.DB) error {
|
||||
scopeOutcome, proceed, err := lockPagesOrphanCleanupScope(ctx, tx, candidate.ID, marker)
|
||||
if err != nil {
|
||||
@@ -141,7 +140,7 @@ func reconcilePagesOrphanUploadCandidate(
|
||||
outcome = scopeOutcome
|
||||
return nil
|
||||
}
|
||||
lockedOutcome, locked, err := reconcileLockedPagesOrphanUpload(
|
||||
lockedOutcome, _, err := reconcileLockedPagesOrphanUpload(
|
||||
ctx,
|
||||
tx,
|
||||
candidate.ID,
|
||||
@@ -153,16 +152,15 @@ func reconcilePagesOrphanUploadCandidate(
|
||||
return err
|
||||
}
|
||||
outcome = lockedOutcome
|
||||
uploadLocked = locked
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return pagesOrphanCleanupSkipped, err
|
||||
}
|
||||
if uploadLocked {
|
||||
// Also heal a prior post-commit cache invalidation interruption when the
|
||||
// status transition was an idempotent no-op.
|
||||
ofupload.InvalidateUploadMetaCache(ctx, candidate.ID)
|
||||
if outcome == pagesOrphanCleanupReconciled {
|
||||
if err := ofupload.Remove(ctx, candidate.ID); err != nil {
|
||||
return pagesOrphanCleanupSkipped, err
|
||||
}
|
||||
}
|
||||
return outcome, nil
|
||||
}
|
||||
@@ -252,14 +250,7 @@ func reconcileLockedPagesOrphanUpload(
|
||||
return pagesOrphanCleanupReferenced, true, nil
|
||||
}
|
||||
|
||||
transitioned, err := ofupload.RemoveLockedTx(tx, &lockedUpload)
|
||||
if err != nil {
|
||||
return pagesOrphanCleanupSkipped, true, err
|
||||
}
|
||||
if transitioned {
|
||||
return pagesOrphanCleanupReconciled, true, nil
|
||||
}
|
||||
return pagesOrphanCleanupSkipped, true, nil
|
||||
return pagesOrphanCleanupReconciled, true, nil
|
||||
}
|
||||
|
||||
func lockOptionalPagesCleanupRecord(
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
"Wavelet/openflare/plugins/server/kernel/ofupload"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
@@ -164,7 +164,7 @@ func TestReconcilePagesOrphanUploadsSkipsBusyLeaseAndSourceMismatch(t *testing.T
|
||||
project := createPagesOrphanProject(t, ctx, "busy-orphan")
|
||||
source := createPagesOrphanSource(t, ctx, project.ID)
|
||||
future := realNow.Add(time.Hour)
|
||||
if err := db.DB(ctx).Create(&model.PagesProjectSourceRuntime{
|
||||
if err := repository.DB(ctx).Create(&model.PagesProjectSourceRuntime{
|
||||
SourceID: source.ID,
|
||||
LeaseToken: "busy-worker",
|
||||
LeaseExpiresAt: &future,
|
||||
@@ -213,7 +213,7 @@ func TestReconcilePagesOrphanUploadsRejectsMalformedMarker(t *testing.T) {
|
||||
metadata := candidate.Metadata
|
||||
metadata.Extra[pagesProjectIDMetadataKey] = "01"
|
||||
candidate.Metadata = metadata
|
||||
if err := db.DB(ctx).Save(candidate).Error; err != nil {
|
||||
if err := repository.DB(ctx).Save(candidate).Error; err != nil {
|
||||
t.Fatalf("seed malformed candidate marker error = %v, want nil", err)
|
||||
}
|
||||
|
||||
@@ -239,7 +239,7 @@ func TestPagesOrphanCleanupAndDeploymentCommitInterleavings(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("parsePagesOrphanMarker() error = %v, want nil", err)
|
||||
}
|
||||
if err := db.DB(ctx).Create(&model.PagesDeployment{
|
||||
if err := repository.DB(ctx).Create(&model.PagesDeployment{
|
||||
ProjectID: project.ID,
|
||||
DeploymentNumber: 1,
|
||||
Checksum: "deployment-first",
|
||||
@@ -276,7 +276,7 @@ func TestPagesOrphanCleanupAndDeploymentCommitInterleavings(t *testing.T) {
|
||||
}
|
||||
|
||||
target := &model.PagesDeployment{ProjectID: project.ID, UploadID: candidate.ID}
|
||||
err = db.DB(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
err = repository.DB(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
var lockedProject model.PagesProject
|
||||
if err := tx.Clauses(clause.Locking{Strength: pagesRowLockStrength}).First(&lockedProject, project.ID).Error; err != nil {
|
||||
return err
|
||||
@@ -287,7 +287,7 @@ func TestPagesOrphanCleanupAndDeploymentCommitInterleavings(t *testing.T) {
|
||||
t.Errorf("final deployment upload lock after cleanup error = %v, want %v", err, errSourceFinalFence)
|
||||
}
|
||||
var references int64
|
||||
if err := db.DB(ctx).Model(&model.PagesDeployment{}).Where("upload_id = ?", candidate.ID).Count(&references).Error; err != nil {
|
||||
if err := repository.DB(ctx).Model(&model.PagesDeployment{}).Where("upload_id = ?", candidate.ID).Count(&references).Error; err != nil {
|
||||
t.Fatalf("count deployment references error = %v, want nil", err)
|
||||
}
|
||||
if references != 0 {
|
||||
@@ -303,7 +303,7 @@ func cleanupOutcomeTotal(summary PagesOrphanCleanupSummary) int {
|
||||
func createPagesOrphanProject(t *testing.T, ctx context.Context, slug string) *model.PagesProject {
|
||||
t.Helper()
|
||||
project := &model.PagesProject{Name: slug, Slug: slug, Enabled: true}
|
||||
if err := db.DB(ctx).Create(project).Error; err != nil {
|
||||
if err := repository.DB(ctx).Create(project).Error; err != nil {
|
||||
t.Fatalf("create Pages orphan project %q error = %v, want nil", slug, err)
|
||||
}
|
||||
return project
|
||||
@@ -317,7 +317,7 @@ func createPagesOrphanSource(t *testing.T, ctx context.Context, projectID uint)
|
||||
ConfigVersion: 1,
|
||||
SourceIdentity: "orphan-source-identity",
|
||||
}
|
||||
if err := db.DB(ctx).Create(source).Error; err != nil {
|
||||
if err := repository.DB(ctx).Create(source).Error; err != nil {
|
||||
t.Fatalf("create Pages orphan source for project %d error = %v, want nil", projectID, err)
|
||||
}
|
||||
return source
|
||||
@@ -339,21 +339,19 @@ func createPagesOrphanUpload(
|
||||
extra[pagesSourceIDMetadataKey] = strconv.FormatUint(uint64(*sourceID), 10)
|
||||
}
|
||||
candidate := &model.Upload{
|
||||
UserID: 999,
|
||||
FileName: "site.zip",
|
||||
FilePath: "pages/orphan-site.zip",
|
||||
FileSize: 64,
|
||||
MimeType: "application/zip",
|
||||
Extension: "zip",
|
||||
Hash: "orphan-checksum",
|
||||
Type: ofupload.ReservedPagesDeploymentType,
|
||||
Status: model.UploadStatusUsed,
|
||||
AccessMode: 0,
|
||||
Metadata: model.UploadMetadata{Extra: extra},
|
||||
CreatedAt: createdAt,
|
||||
UpdatedAt: createdAt,
|
||||
UserID: 999,
|
||||
FileName: "site.zip",
|
||||
FilePath: "pages/orphan-site.zip",
|
||||
Size: 64,
|
||||
MimeType: "application/zip",
|
||||
Hash: "orphan-checksum",
|
||||
Type: ofupload.ReservedPagesDeploymentType,
|
||||
Status: model.UploadStatusUsed,
|
||||
Metadata: model.UploadMetadata{Extra: extra},
|
||||
CreatedAt: createdAt,
|
||||
UpdatedAt: createdAt,
|
||||
}
|
||||
if err := db.DB(ctx).Create(candidate).Error; err != nil {
|
||||
if err := repository.DB(ctx).Table("w_uploads").Create(candidate).Error; err != nil {
|
||||
t.Fatalf("create Pages orphan upload error = %v, want nil", err)
|
||||
}
|
||||
return candidate
|
||||
@@ -362,7 +360,7 @@ func createPagesOrphanUpload(
|
||||
func assertPagesCleanupUploadStatus(t *testing.T, ctx context.Context, uploadID uint64, want model.UploadStatus) {
|
||||
t.Helper()
|
||||
var got model.Upload
|
||||
if err := db.DB(ctx).First(&got, uploadID).Error; err != nil {
|
||||
if err := repository.DB(ctx).Table("w_uploads").First(&got, uploadID).Error; err != nil {
|
||||
t.Fatalf("load upload %d error = %v, want nil", uploadID, err)
|
||||
}
|
||||
if got.Status != want {
|
||||
@@ -373,10 +371,10 @@ func assertPagesCleanupUploadStatus(t *testing.T, ctx context.Context, uploadID
|
||||
func assertPagesCleanupTotalStat(t *testing.T, ctx context.Context, want int64) {
|
||||
t.Helper()
|
||||
var stat model.UploadStat
|
||||
if err := db.DB(ctx).Where("dimension = ? AND stat_key = ?", model.UploadStatDimensionTotal, "").First(&stat).Error; err != nil {
|
||||
if err := repository.DB(ctx).Where("dimension = ?", model.UploadStatDimensionTotal).First(&stat).Error; err != nil {
|
||||
t.Fatalf("load total upload stat error = %v, want nil", err)
|
||||
}
|
||||
if stat.FileCount != want {
|
||||
if int64(stat.FileCount) != want {
|
||||
t.Errorf("total upload stat FileCount = %d, want %d", stat.FileCount, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
)
|
||||
|
||||
func TestSourceLeaseHeartbeatRenewsAndCancelsOnOwnershipLoss(t *testing.T) {
|
||||
@@ -38,7 +37,7 @@ func TestSourceLeaseHeartbeatRenewsAndCancelsOnOwnershipLoss(t *testing.T) {
|
||||
t.Cleanup(func() { _ = heartbeat.stop() })
|
||||
|
||||
var initial model.PagesProjectSourceRuntime
|
||||
if err := db.DB(ctx).Where("source_id = ?", source.ID).First(&initial).Error; err != nil {
|
||||
if err := repository.DB(ctx).Where("source_id = ?", source.ID).First(&initial).Error; err != nil {
|
||||
t.Fatalf("load initial heartbeat runtime error = %v, want nil", err)
|
||||
}
|
||||
if initial.LeaseExpiresAt == nil {
|
||||
@@ -47,7 +46,7 @@ func TestSourceLeaseHeartbeatRenewsAndCancelsOnOwnershipLoss(t *testing.T) {
|
||||
deadline := time.Now().Add(2 * time.Second)
|
||||
for {
|
||||
var renewedRuntime model.PagesProjectSourceRuntime
|
||||
if err := db.DB(ctx).Where("source_id = ?", source.ID).First(&renewedRuntime).Error; err != nil {
|
||||
if err := repository.DB(ctx).Where("source_id = ?", source.ID).First(&renewedRuntime).Error; err != nil {
|
||||
t.Fatalf("load renewed heartbeat runtime error = %v, want nil", err)
|
||||
}
|
||||
if renewedRuntime.LeaseExpiresAt != nil && renewedRuntime.LeaseExpiresAt.After(*initial.LeaseExpiresAt) {
|
||||
@@ -59,7 +58,7 @@ func TestSourceLeaseHeartbeatRenewsAndCancelsOnOwnershipLoss(t *testing.T) {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||
if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||
Where("source_id = ?", source.ID).
|
||||
Update("lease_token", "replacement-owner").Error; err != nil {
|
||||
t.Fatalf("replace heartbeat lease owner error = %v, want nil", err)
|
||||
@@ -166,7 +165,7 @@ func TestAcquireSourceLeaseMutualExclusionExpiryAndTerminalOwnership(t *testing.
|
||||
}
|
||||
|
||||
past := time.Now().Add(-time.Second)
|
||||
if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||
if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||
Where("source_id = ?", source.ID).
|
||||
Update("lease_expires_at", &past).Error; err != nil {
|
||||
t.Fatalf("expire first lease error = %v, want nil", err)
|
||||
@@ -196,7 +195,7 @@ func TestAcquireSourceLeaseMutualExclusionExpiryAndTerminalOwnership(t *testing.
|
||||
t.Fatalf("failSourceLease(expired owner) error = %v, want nil", err)
|
||||
}
|
||||
var runtime model.PagesProjectSourceRuntime
|
||||
if err := db.DB(ctx).Where("source_id = ?", source.ID).First(&runtime).Error; err != nil {
|
||||
if err := repository.DB(ctx).Where("source_id = ?", source.ID).First(&runtime).Error; err != nil {
|
||||
t.Fatalf("load runtime after takeover error = %v, want nil", err)
|
||||
}
|
||||
if got, want := runtime.LeaseToken, takeover.LeaseToken; got != want {
|
||||
@@ -217,7 +216,7 @@ func TestAcquireSourceLeaseMutualExclusionExpiryAndTerminalOwnership(t *testing.
|
||||
t.Fatalf("failSourceLease(current owner) error = %v, want nil", err)
|
||||
}
|
||||
var failedRuntime model.PagesProjectSourceRuntime
|
||||
if err := db.DB(ctx).Where("source_id = ?", source.ID).First(&failedRuntime).Error; err != nil {
|
||||
if err := repository.DB(ctx).Where("source_id = ?", source.ID).First(&failedRuntime).Error; err != nil {
|
||||
t.Fatalf("load failed runtime error = %v, want nil", err)
|
||||
}
|
||||
if got, want := failedRuntime.SyncStatus, pagesSourceStatusFailed; got != want {
|
||||
@@ -258,7 +257,7 @@ func TestSourceConfigAndProjectContentChangesFenceLease(t *testing.T) {
|
||||
t.Error("renewSourceLease(after source update) = true, want false")
|
||||
}
|
||||
var updatedSource model.PagesProjectSource
|
||||
if err := db.DB(ctx).Where("id = ?", source.ID).First(&updatedSource).Error; err != nil {
|
||||
if err := repository.DB(ctx).Where("id = ?", source.ID).First(&updatedSource).Error; err != nil {
|
||||
t.Fatalf("load updated source error = %v, want nil", err)
|
||||
}
|
||||
if got, want := updatedSource.ConfigVersion, source.ConfigVersion+1; got != want {
|
||||
@@ -296,7 +295,7 @@ func TestSourceConfigAndProjectContentChangesFenceLease(t *testing.T) {
|
||||
t.Errorf("ContentConfigVersion after RootDir update = %d, want %d", got, want)
|
||||
}
|
||||
var runtime model.PagesProjectSourceRuntime
|
||||
if err := db.DB(ctx).Where("source_id = ?", source.ID).First(&runtime).Error; err != nil {
|
||||
if err := repository.DB(ctx).Where("source_id = ?", source.ID).First(&runtime).Error; err != nil {
|
||||
t.Fatalf("load fenced runtime error = %v, want nil", err)
|
||||
}
|
||||
if runtime.LeaseToken != "" || runtime.LeaseExpiresAt != nil {
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||
"Wavelet/openflare/share/githubrelease"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -42,7 +41,7 @@ func TestGitHubLatestAutoConfigPreservesIdentityAndRuntimeCursor(t *testing.T) {
|
||||
seenRevision := strings.Repeat("a", sourceRevisionHexLength)
|
||||
appliedRevision := strings.Repeat("b", sourceRevisionHexLength)
|
||||
future := time.Now().Add(time.Hour)
|
||||
if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||
if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||
Where("source_id = ?", source.ID).
|
||||
Updates(map[string]any{
|
||||
"etag": `"cursor-etag"`,
|
||||
@@ -67,7 +66,7 @@ func TestGitHubLatestAutoConfigPreservesIdentityAndRuntimeCursor(t *testing.T) {
|
||||
if err := validateGitHubSourceInput(input); err != nil {
|
||||
t.Fatalf("validateGitHubSourceInput(auto latest) error = %v, want nil", err)
|
||||
}
|
||||
if err := db.DB(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := repository.DB(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
changed, err := updateGitHubSourceTx(tx, project.ID, input)
|
||||
if err == nil && !changed {
|
||||
return errors.New("auto config update was treated as no-op")
|
||||
@@ -132,7 +131,7 @@ func TestRecoverExpiredPagesSourceLeaseUsesExactCASAndStableJitter(t *testing.T)
|
||||
now := time.Date(2026, 7, 19, 12, 0, 0, 0, time.UTC)
|
||||
usePagesSourceScannerClock(t, now)
|
||||
expiredAt := now.Add(-time.Minute)
|
||||
if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||
if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||
Where("source_id = ?", source.ID).
|
||||
Updates(map[string]any{
|
||||
"sync_status": pagesSourceStatusChecking,
|
||||
@@ -167,7 +166,7 @@ func TestRecoverExpiredPagesSourceLeaseUsesExactCASAndStableJitter(t *testing.T)
|
||||
}
|
||||
|
||||
renewedExpiry := now.Add(time.Minute)
|
||||
if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||
if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||
Where("source_id = ?", source.ID).
|
||||
Updates(map[string]any{
|
||||
"sync_status": pagesSourceStatusSyncing,
|
||||
@@ -274,24 +273,24 @@ func TestPagesSourceScannerSerialBatchIsolation304AndActualBacklog(t *testing.T)
|
||||
byRepository := make(map[string]int, 22)
|
||||
for index := 1; index <= 22; index++ {
|
||||
project := mustCreatePagesSourceProject(t, ctx, fmt.Sprintf("scanner-batch-%02d", index))
|
||||
repository := fmt.Sprintf("scanner/source-%02d", index)
|
||||
repoPath := fmt.Sprintf("scanner/source-%02d", index)
|
||||
source, runtime := mustConfigureGitHubSourceWithoutDispatch(t, ctx, project.ID, SourceUpdateInput{
|
||||
SourceType: PagesSourceTypeGitHubRelease,
|
||||
RepositoryURL: "https://github.com/" + repository,
|
||||
RepositoryURL: "https://github.com/" + repoPath,
|
||||
AutoUpdateEnabled: index != 4,
|
||||
CheckIntervalMinutes: 60,
|
||||
})
|
||||
if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||
if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||
Where("source_id = ?", source.ID).
|
||||
Update("next_check_at", &dueAt).Error; err != nil {
|
||||
t.Fatalf("mark source %d due error = %v, want nil", source.ID, err)
|
||||
}
|
||||
fixtures = append(fixtures, fixture{source: source, runtime: runtime, repository: repository})
|
||||
byRepository[repository] = index
|
||||
fixtures = append(fixtures, fixture{source: source, runtime: runtime, repository: repoPath})
|
||||
byRepository[repoPath] = index
|
||||
}
|
||||
|
||||
busyUntil := now.Add(time.Hour)
|
||||
if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||
if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||
Where("source_id = ?", fixtures[0].source.ID).
|
||||
Updates(map[string]any{
|
||||
"sync_status": pagesSourceStatusChecking,
|
||||
@@ -302,7 +301,7 @@ func TestPagesSourceScannerSerialBatchIsolation304AndActualBacklog(t *testing.T)
|
||||
}
|
||||
|
||||
stored304Revision := strings.Repeat("3", sourceRevisionHexLength)
|
||||
if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||
if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||
Where("source_id = ?", fixtures[2].source.ID).
|
||||
Updates(map[string]any{
|
||||
"etag": `"stored-etag"`,
|
||||
@@ -313,7 +312,7 @@ func TestPagesSourceScannerSerialBatchIsolation304AndActualBacklog(t *testing.T)
|
||||
t.Fatalf("seed 304 cursor error = %v, want nil", err)
|
||||
}
|
||||
|
||||
if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||
if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||
Where("source_id = ?", fixtures[4].source.ID).
|
||||
Updates(map[string]any{
|
||||
"last_applied_revision": strings.Repeat("a", sourceRevisionHexLength),
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
"Wavelet/openflare/plugins/server/kernel/ofupload"
|
||||
"Wavelet/openflare/share/pagesarchive"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -160,7 +159,7 @@ func TestSyncRemoteSourceAtomicallyActivatesAndReusesChecksum(t *testing.T) {
|
||||
t.Errorf("deployment provenance = label:%q meta:%q, want no query secret", deployment.SourceLabel, deployment.SourceMeta)
|
||||
}
|
||||
var runtime model.PagesProjectSourceRuntime
|
||||
if err := db.DB(ctx).Where("source_id = ?", source.ID).First(&runtime).Error; err != nil {
|
||||
if err := repository.DB(ctx).Where("source_id = ?", source.ID).First(&runtime).Error; err != nil {
|
||||
t.Fatalf("load source runtime error = %v, want nil", err)
|
||||
}
|
||||
if got, want := runtime.SyncStatus, pagesSourceStatusIdle; got != want {
|
||||
@@ -173,7 +172,7 @@ func TestSyncRemoteSourceAtomicallyActivatesAndReusesChecksum(t *testing.T) {
|
||||
t.Errorf("runtime lease = (%q, %v), want cleared", runtime.LeaseToken, runtime.LeaseExpiresAt)
|
||||
}
|
||||
var uploadRecord model.Upload
|
||||
if err := db.DB(ctx).First(&uploadRecord, deployment.UploadID).Error; err != nil {
|
||||
if err := repository.DB(ctx).First(&uploadRecord, deployment.UploadID).Error; err != nil {
|
||||
t.Fatalf("load deployment upload %d error = %v, want nil", deployment.UploadID, err)
|
||||
}
|
||||
if got, want := uploadRecord.Status, model.UploadStatusUsed; got != want {
|
||||
@@ -198,10 +197,10 @@ func TestSyncRemoteSourceAtomicallyActivatesAndReusesChecksum(t *testing.T) {
|
||||
t.Errorf("reused deployment ID = %d, want %d", got, want)
|
||||
}
|
||||
var deploymentCount, uploadCount int64
|
||||
if err := db.DB(ctx).Model(&model.PagesDeployment{}).Where("project_id = ?", project.ID).Count(&deploymentCount).Error; err != nil {
|
||||
if err := repository.DB(ctx).Model(&model.PagesDeployment{}).Where("project_id = ?", project.ID).Count(&deploymentCount).Error; err != nil {
|
||||
t.Fatalf("count source deployments error = %v, want nil", err)
|
||||
}
|
||||
if err := db.DB(ctx).Model(&model.Upload{}).Count(&uploadCount).Error; err != nil {
|
||||
if err := repository.DB(ctx).Model(&model.Upload{}).Count(&uploadCount).Error; err != nil {
|
||||
t.Fatalf("count source uploads error = %v, want nil", err)
|
||||
}
|
||||
if got, want := deploymentCount, int64(1); got != want {
|
||||
@@ -267,7 +266,7 @@ func TestSyncRemoteSourceFinalFenceCompensatesIngest(t *testing.T) {
|
||||
packageBytes := testPagesZip(t, map[string]string{"index.html": "never-activate"})
|
||||
mutationResult := make(chan error, 1)
|
||||
server := newPagesArchiveServer(t, http.StatusOK, packageBytes, func() error {
|
||||
err := db.DB(context.Background()).Model(&model.PagesProject{}).
|
||||
err := repository.DB(context.Background()).Model(&model.PagesProject{}).
|
||||
Where("id = ?", project.ID).
|
||||
Update("content_config_version", gorm.Expr("content_config_version + 1")).Error
|
||||
mutationResult <- err
|
||||
@@ -304,14 +303,14 @@ func TestSyncRemoteSourceFinalFenceCompensatesIngest(t *testing.T) {
|
||||
t.Errorf("ActiveDeploymentID after final fence = %v, want %d", storedProject.ActiveDeploymentID, oldActive.ID)
|
||||
}
|
||||
var deployments []model.PagesDeployment
|
||||
if err := db.DB(ctx).Where("project_id = ?", project.ID).Find(&deployments).Error; err != nil {
|
||||
if err := repository.DB(ctx).Where("project_id = ?", project.ID).Find(&deployments).Error; err != nil {
|
||||
t.Fatalf("list deployments after final fence error = %v, want nil", err)
|
||||
}
|
||||
if got, want := len(deployments), 1; got != want {
|
||||
t.Errorf("deployment count after final fence = %d, want %d", got, want)
|
||||
}
|
||||
var uploads []model.Upload
|
||||
if err := db.DB(ctx).Order("id asc").Find(&uploads).Error; err != nil {
|
||||
if err := repository.DB(ctx).Order("id asc").Find(&uploads).Error; err != nil {
|
||||
t.Fatalf("list uploads after final fence error = %v, want nil", err)
|
||||
}
|
||||
var compensated *model.Upload
|
||||
@@ -328,7 +327,7 @@ func TestSyncRemoteSourceFinalFenceCompensatesIngest(t *testing.T) {
|
||||
t.Errorf("compensated upload Status = %q, want %q", got, want)
|
||||
}
|
||||
var danglingCount int64
|
||||
if err := db.DB(ctx).Model(&model.PagesDeployment{}).
|
||||
if err := repository.DB(ctx).Model(&model.PagesDeployment{}).
|
||||
Where("upload_id = ?", compensated.ID).
|
||||
Count(&danglingCount).Error; err != nil {
|
||||
t.Fatalf("count compensated upload references error = %v, want nil", err)
|
||||
@@ -358,12 +357,12 @@ func TestCommitSourceDeploymentRechecksLeaseAfterUploadLocks(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("GetPagesDeploymentByID(%d) error = %v, want nil", first.Deployment.ID, err)
|
||||
}
|
||||
if err := db.DB(ctx).Model(&model.PagesProject{}).
|
||||
if err := repository.DB(ctx).Model(&model.PagesProject{}).
|
||||
Where("id = ?", project.ID).
|
||||
Update("active_deployment_id", nil).Error; err != nil {
|
||||
t.Fatalf("clear active deployment error = %v, want nil", err)
|
||||
}
|
||||
if err := db.DB(ctx).Model(&model.PagesDeployment{}).
|
||||
if err := repository.DB(ctx).Model(&model.PagesDeployment{}).
|
||||
Where("id = ?", deployment.ID).
|
||||
Update("status", model.PagesDeploymentStatusUploaded).Error; err != nil {
|
||||
t.Fatalf("reset deployment status error = %v, want nil", err)
|
||||
@@ -371,7 +370,7 @@ func TestCommitSourceDeploymentRechecksLeaseAfterUploadLocks(t *testing.T) {
|
||||
|
||||
snapshot := mustAcquireRemoteSyncLease(t, ctx, source)
|
||||
expiresAt := time.Now().Add(time.Hour)
|
||||
if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||
if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||
Where("source_id = ? AND lease_token = ?", source.ID, snapshot.LeaseToken).
|
||||
Update("lease_expires_at", &expiresAt).Error; err != nil {
|
||||
t.Fatalf("set deterministic lease expiry error = %v, want nil", err)
|
||||
@@ -465,7 +464,7 @@ func TestCompensateSourceIngestSurvivesCanceledParentContext(t *testing.T) {
|
||||
})
|
||||
|
||||
var uploadRecord model.Upload
|
||||
if err := db.DB(ctx).Where("id = ?", result.Upload.ID).First(&uploadRecord).Error; err != nil {
|
||||
if err := repository.DB(ctx).Where("id = ?", result.Upload.ID).First(&uploadRecord).Error; err != nil {
|
||||
t.Fatalf("load compensated upload error = %v, want nil", err)
|
||||
}
|
||||
if got, want := uploadRecord.Status, model.UploadStatusDeleted; got != want {
|
||||
@@ -490,7 +489,7 @@ func assertPagesSyncFailureState(
|
||||
t.Errorf("project %d ActiveDeploymentID = %v, want %d", projectID, project.ActiveDeploymentID, oldActiveID)
|
||||
}
|
||||
var deploymentCount int64
|
||||
if err := db.DB(ctx).Model(&model.PagesDeployment{}).
|
||||
if err := repository.DB(ctx).Model(&model.PagesDeployment{}).
|
||||
Where("project_id = ?", projectID).
|
||||
Count(&deploymentCount).Error; err != nil {
|
||||
t.Fatalf("count project %d deployments error = %v, want nil", projectID, err)
|
||||
@@ -499,7 +498,7 @@ func assertPagesSyncFailureState(
|
||||
t.Errorf("project %d deployment count = %d, want %d", projectID, got, want)
|
||||
}
|
||||
var runtime model.PagesProjectSourceRuntime
|
||||
if err := db.DB(ctx).Where("source_id = ?", sourceID).First(&runtime).Error; err != nil {
|
||||
if err := repository.DB(ctx).Where("source_id = ?", sourceID).First(&runtime).Error; err != nil {
|
||||
t.Fatalf("load source %d runtime error = %v, want nil", sourceID, err)
|
||||
}
|
||||
if got, want := runtime.SyncStatus, pagesSourceStatusFailed; got != want {
|
||||
@@ -529,19 +528,17 @@ func TestCommitSourceDeploymentRejectsDeletedTargetUpload(t *testing.T) {
|
||||
identity := source.SourceIdentity
|
||||
revision := strings.Repeat("d", 64)
|
||||
uploadRecord := &model.Upload{
|
||||
ID: 987654321,
|
||||
UserID: 999,
|
||||
FileName: "deleted.zip",
|
||||
FilePath: "deleted.zip",
|
||||
FileSize: 1,
|
||||
MimeType: "application/zip",
|
||||
Extension: "zip",
|
||||
Hash: revision,
|
||||
Type: ofupload.ReservedPagesDeploymentType,
|
||||
Status: model.UploadStatusDeleted,
|
||||
AccessMode: 0,
|
||||
ID: 987654321,
|
||||
UserID: 999,
|
||||
FileName: "deleted.zip",
|
||||
FilePath: "deleted.zip",
|
||||
Size: 1,
|
||||
MimeType: "application/zip",
|
||||
Hash: revision,
|
||||
Type: ofupload.ReservedPagesDeploymentType,
|
||||
Status: model.UploadStatusDeleted,
|
||||
}
|
||||
if err := db.DB(ctx).Create(uploadRecord).Error; err != nil {
|
||||
if err := repository.DB(ctx).Table("w_uploads").Create(uploadRecord).Error; err != nil {
|
||||
t.Fatalf("create deleted upload error = %v, want nil", err)
|
||||
}
|
||||
deployment := &model.PagesDeployment{
|
||||
@@ -560,10 +557,10 @@ func TestCommitSourceDeploymentRejectsDeletedTargetUpload(t *testing.T) {
|
||||
SourceMeta: `{"provider":"remote_url","display_name":"deleted.zip"}`,
|
||||
TriggerType: pagesSourceTriggerManualSync,
|
||||
}
|
||||
if err := db.DB(ctx).Create(deployment).Error; err != nil {
|
||||
if err := repository.DB(ctx).Create(deployment).Error; err != nil {
|
||||
t.Fatalf("create source deployment error = %v, want nil", err)
|
||||
}
|
||||
if err := db.DB(ctx).Create(&model.PagesDeploymentFile{
|
||||
if err := repository.DB(ctx).Create(&model.PagesDeploymentFile{
|
||||
DeploymentID: deployment.ID,
|
||||
Path: "index.html",
|
||||
Size: 1,
|
||||
@@ -600,7 +597,7 @@ func TestCommitSourceDeploymentRejectsDeletedTargetUpload(t *testing.T) {
|
||||
t.Errorf("ActiveDeploymentID after deleted upload rejection = %v, want nil", storedProject.ActiveDeploymentID)
|
||||
}
|
||||
var activeCount int64
|
||||
if err := db.DB(ctx).Model(&model.PagesDeployment{}).
|
||||
if err := repository.DB(ctx).Model(&model.PagesDeployment{}).
|
||||
Where("project_id = ? AND status = ?", project.ID, model.PagesDeploymentStatusActive).
|
||||
Count(&activeCount).Error; err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
t.Fatalf("count active deployments error = %v, want nil", err)
|
||||
|
||||
@@ -13,16 +13,15 @@ import (
|
||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
)
|
||||
|
||||
func setupPagesSourceTest(t *testing.T) context.Context {
|
||||
t.Helper()
|
||||
cleanup := setupPagesTestDB(t)
|
||||
t.Cleanup(cleanup)
|
||||
sqlDB, err := db.DB(t.Context()).DB()
|
||||
sqlDB, err := repository.DB(t.Context()).DB()
|
||||
if err != nil {
|
||||
t.Fatalf("db.DB().DB() error = %v, want nil", err)
|
||||
t.Fatalf("repository.DB().DB() error = %v, want nil", err)
|
||||
}
|
||||
// SQLite :memory: is scoped to one connection. Keeping one connection also
|
||||
// makes lease tests exercise the production CAS without creating empty
|
||||
@@ -93,11 +92,11 @@ func mustConfigureRemoteSource(
|
||||
t.Fatalf("UpdateSource(%d, %q) error = %v, want nil", projectID, remoteURL, err)
|
||||
}
|
||||
var source model.PagesProjectSource
|
||||
if err := db.DB(ctx).Where("project_id = ?", projectID).First(&source).Error; err != nil {
|
||||
if err := repository.DB(ctx).Where("project_id = ?", projectID).First(&source).Error; err != nil {
|
||||
t.Fatalf("load source for project %d error = %v, want nil", projectID, err)
|
||||
}
|
||||
var runtime model.PagesProjectSourceRuntime
|
||||
if err := db.DB(ctx).Where("source_id = ?", source.ID).First(&runtime).Error; err != nil {
|
||||
if err := repository.DB(ctx).Where("source_id = ?", source.ID).First(&runtime).Error; err != nil {
|
||||
t.Fatalf("load runtime for source %d error = %v, want nil", source.ID, err)
|
||||
}
|
||||
return &source, &runtime
|
||||
@@ -187,7 +186,7 @@ func TestRemoteSourceCRUDPreservesSecretAndResetsRuntimeByIdentity(t *testing.T)
|
||||
t.Fatalf("UpdateSource(%d, no-op) error = %v, want nil", project.ID, err)
|
||||
}
|
||||
var unchangedSource model.PagesProjectSource
|
||||
if err := db.DB(ctx).Where("id = ?", source.ID).First(&unchangedSource).Error; err != nil {
|
||||
if err := repository.DB(ctx).Where("id = ?", source.ID).First(&unchangedSource).Error; err != nil {
|
||||
t.Fatalf("load no-op source error = %v, want nil", err)
|
||||
}
|
||||
if got, want := unchangedSource.ConfigVersion, source.ConfigVersion; got != want {
|
||||
@@ -197,7 +196,7 @@ func TestRemoteSourceCRUDPreservesSecretAndResetsRuntimeByIdentity(t *testing.T)
|
||||
seenRevision := strings.Repeat("a", 64)
|
||||
appliedRevision := strings.Repeat("b", 64)
|
||||
future := time.Now().Add(time.Hour)
|
||||
if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||
if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).
|
||||
Where("source_id = ?", source.ID).
|
||||
Updates(map[string]any{
|
||||
"last_seen_revision": seenRevision,
|
||||
@@ -327,10 +326,10 @@ func TestDeleteSourceIsIdempotentAndKeepsDeploymentState(t *testing.T) {
|
||||
SourceType: "manual_upload",
|
||||
TriggerType: "manual_upload",
|
||||
}
|
||||
if err := db.DB(ctx).Create(deployment).Error; err != nil {
|
||||
if err := repository.DB(ctx).Create(deployment).Error; err != nil {
|
||||
t.Fatalf("create deployment error = %v, want nil", err)
|
||||
}
|
||||
if err := db.DB(ctx).Model(&model.PagesProject{}).
|
||||
if err := repository.DB(ctx).Model(&model.PagesProject{}).
|
||||
Where("id = ?", project.ID).
|
||||
Update("active_deployment_id", deployment.ID).Error; err != nil {
|
||||
t.Fatalf("set active deployment error = %v, want nil", err)
|
||||
@@ -346,13 +345,13 @@ func TestDeleteSourceIsIdempotentAndKeepsDeploymentState(t *testing.T) {
|
||||
}
|
||||
}
|
||||
var sourceCount, runtimeCount, deploymentCount int64
|
||||
if err := db.DB(ctx).Model(&model.PagesProjectSource{}).Where("id = ?", source.ID).Count(&sourceCount).Error; err != nil {
|
||||
if err := repository.DB(ctx).Model(&model.PagesProjectSource{}).Where("id = ?", source.ID).Count(&sourceCount).Error; err != nil {
|
||||
t.Fatalf("count source error = %v, want nil", err)
|
||||
}
|
||||
if err := db.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).Where("source_id = ?", source.ID).Count(&runtimeCount).Error; err != nil {
|
||||
if err := repository.DB(ctx).Model(&model.PagesProjectSourceRuntime{}).Where("source_id = ?", source.ID).Count(&runtimeCount).Error; err != nil {
|
||||
t.Fatalf("count runtime error = %v, want nil", err)
|
||||
}
|
||||
if err := db.DB(ctx).Model(&model.PagesDeployment{}).Where("id = ?", deployment.ID).Count(&deploymentCount).Error; err != nil {
|
||||
if err := repository.DB(ctx).Model(&model.PagesDeployment{}).Where("id = ?", deployment.ID).Count(&deploymentCount).Error; err != nil {
|
||||
t.Fatalf("count deployment error = %v, want nil", err)
|
||||
}
|
||||
if sourceCount != 0 || runtimeCount != 0 || deploymentCount != 1 {
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -29,10 +28,10 @@ func setupApplyLogTestDB(t *testing.T) func() {
|
||||
err = sqliteDB.AutoMigrate(&model.OpenFlareApplyLog{})
|
||||
require.NoError(t, err)
|
||||
|
||||
db.SetDB(sqliteDB)
|
||||
repository.SetDBForTest(sqliteDB)
|
||||
|
||||
return func() {
|
||||
db.SetDB(nil)
|
||||
repository.SetDBForTest(nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +48,7 @@ func TestListPageAndCleanup(t *testing.T) {
|
||||
{NodeID: "node-logs", Version: "v3", Result: "success", Message: "3", CreatedAt: now},
|
||||
}
|
||||
for i := range logs {
|
||||
require.NoError(t, db.DB(ctx).Create(&logs[i]).Error)
|
||||
require.NoError(t, repository.DB(ctx).Create(&logs[i]).Error)
|
||||
}
|
||||
|
||||
pageResult, err := ListPage(ctx, ListQuery{
|
||||
|
||||
+5
-6
@@ -20,7 +20,6 @@ import (
|
||||
oftls "Wavelet/openflare/plugins/server/domain/tls"
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
"Wavelet/openflare/plugins/server/kernel/runtimeconfig"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -29,7 +28,7 @@ import (
|
||||
func TestBuildCertificateSupportFilesDecryptsSealedPrivateKey(t *testing.T) {
|
||||
cleanup := setupConfigVersionTestDB(t)
|
||||
defer cleanup()
|
||||
require.NoError(t, db.DB(context.Background()).AutoMigrate(&model.TLSCertificate{}))
|
||||
require.NoError(t, repository.DB(context.Background()).AutoMigrate(&model.TLSCertificate{}))
|
||||
|
||||
previous := runtimeconfig.Get()
|
||||
runtimeconfig.SetSessionSecret("test-session-secret-for-tls-seal")
|
||||
@@ -65,7 +64,7 @@ func TestBuildSnapshotReadsZoneDomainCertificates(t *testing.T) {
|
||||
cleanup := setupConfigVersionTestDB(t)
|
||||
defer cleanup()
|
||||
ctx := context.Background()
|
||||
require.NoError(t, db.DB(ctx).AutoMigrate(&model.TLSCertificate{}))
|
||||
require.NoError(t, repository.DB(ctx).AutoMigrate(&model.TLSCertificate{}))
|
||||
|
||||
previous := runtimeconfig.Get()
|
||||
runtimeconfig.SetSessionSecret("test-session-secret-for-zone-domain-snapshots")
|
||||
@@ -81,9 +80,9 @@ func TestBuildSnapshotReadsZoneDomainCertificates(t *testing.T) {
|
||||
route := &model.ProxyRoute{SiteName: "tls-site", OriginURL: "http://origin:8080", Upstreams: `["http://origin:8080"]`, Enabled: true, EnableHTTPS: true}
|
||||
require.NoError(t, repository.CreateProxyRouteRecord(ctx, route))
|
||||
zone := &model.Zone{Domain: "example.com"}
|
||||
require.NoError(t, db.DB(ctx).Create(zone).Error)
|
||||
require.NoError(t, db.DB(ctx).Create(&model.ZoneDomain{ZoneID: zone.ID, ProxyRouteID: &route.ID, Domain: "one.example.com", CertID: &first.ID}).Error)
|
||||
require.NoError(t, db.DB(ctx).Create(&model.ZoneDomain{ZoneID: zone.ID, ProxyRouteID: &route.ID, Domain: "two.example.com", CertID: &second.ID}).Error)
|
||||
require.NoError(t, repository.DB(ctx).Create(zone).Error)
|
||||
require.NoError(t, repository.DB(ctx).Create(&model.ZoneDomain{ZoneID: zone.ID, ProxyRouteID: &route.ID, Domain: "one.example.com", CertID: &first.ID}).Error)
|
||||
require.NoError(t, repository.DB(ctx).Create(&model.ZoneDomain{ZoneID: zone.ID, ProxyRouteID: &route.ID, Domain: "two.example.com", CertID: &second.ID}).Error)
|
||||
|
||||
bundle, err := buildCurrentConfigBundle(ctx, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
openrestyrender "Wavelet/openflare/share/render/openresty"
|
||||
"Wavelet/pkg/cache/ram"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -45,9 +44,9 @@ func setupConfigVersionTestDB(t *testing.T) func() {
|
||||
&model.SystemConfig{},
|
||||
))
|
||||
|
||||
db.SetDB(sqliteDB)
|
||||
repository.SetDBForTest(sqliteDB)
|
||||
return func() {
|
||||
db.SetDB(nil)
|
||||
repository.SetDBForTest(nil)
|
||||
ram.ResetForTest()
|
||||
}
|
||||
}
|
||||
@@ -55,9 +54,9 @@ func setupConfigVersionTestDB(t *testing.T) func() {
|
||||
func createSnapshotZoneDomains(t *testing.T, ctx context.Context, route *model.ProxyRoute, domains ...string) {
|
||||
t.Helper()
|
||||
zone := &model.Zone{Domain: fmt.Sprintf("zone-%d.example", route.ID)}
|
||||
require.NoError(t, db.DB(ctx).Create(zone).Error)
|
||||
require.NoError(t, repository.DB(ctx).Create(zone).Error)
|
||||
for _, domain := range domains {
|
||||
require.NoError(t, db.DB(ctx).Create(&model.ZoneDomain{
|
||||
require.NoError(t, repository.DB(ctx).Create(&model.ZoneDomain{
|
||||
ZoneID: zone.ID,
|
||||
ProxyRouteID: &route.ID,
|
||||
Domain: domain,
|
||||
@@ -69,7 +68,7 @@ func TestListConfigVersionsOrdersByCreatedAtDesc(t *testing.T) {
|
||||
cleanup := setupConfigVersionTestDB(t)
|
||||
defer cleanup()
|
||||
ctx := context.Background()
|
||||
conn := db.DB(ctx)
|
||||
conn := repository.DB(ctx)
|
||||
require.NotNil(t, conn)
|
||||
|
||||
newer := &model.ConfigVersion{
|
||||
@@ -220,7 +219,7 @@ func TestBuildCurrentConfigBundleEnablesGlobalPoWWithoutExplicitBinding(t *testi
|
||||
graphJSON, err := json.Marshal(snapshotPoWGraph())
|
||||
require.NoError(t, err)
|
||||
globalGroup.Graph = string(graphJSON)
|
||||
require.NoError(t, db.DB(ctx).Model(globalGroup).Update("graph", globalGroup.Graph).Error)
|
||||
require.NoError(t, repository.DB(ctx).Model(globalGroup).Update("graph", globalGroup.Graph).Error)
|
||||
|
||||
bundle, err := buildCurrentConfigBundle(ctx, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
+7
-19
@@ -9,13 +9,12 @@ import (
|
||||
"testing"
|
||||
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
||||
"Wavelet/pkg/cache/ram"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func setupOriginErrorPageSnapshotDB(t *testing.T) func() {
|
||||
@@ -25,15 +24,10 @@ func setupOriginErrorPageSnapshotDB(t *testing.T) func() {
|
||||
// 重置,否则 shuffle 下先跑的用例会污染后跑的用例。
|
||||
ram.ResetForTest()
|
||||
|
||||
sqliteDB, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, sqliteDB.AutoMigrate(&model.SystemConfig{}))
|
||||
db.SetDB(sqliteDB)
|
||||
_, _, cleanup := testhelper.SetupTestEnvironment(t)
|
||||
|
||||
return func() {
|
||||
db.SetDB(nil)
|
||||
cleanup()
|
||||
ram.ResetForTest()
|
||||
}
|
||||
}
|
||||
@@ -58,15 +52,9 @@ func TestBuildOpenRestyConfigSnapshotOriginErrorPageCustom(t *testing.T) {
|
||||
defer cleanup()
|
||||
ctx := context.Background()
|
||||
|
||||
require.NoError(t, db.DB(ctx).Create(&model.SystemConfig{
|
||||
Key: model.ConfigKeyOriginErrorPageEnabled, Value: "false", Type: "business",
|
||||
}).Error)
|
||||
require.NoError(t, db.DB(ctx).Create(&model.SystemConfig{
|
||||
Key: model.ConfigKeyOriginErrorPageStatusCodes, Value: `["522","500-502"]`, Type: "business",
|
||||
}).Error)
|
||||
require.NoError(t, db.DB(ctx).Create(&model.SystemConfig{
|
||||
Key: model.ConfigKeyOriginErrorPageHTML, Value: "<h1>{{status}}</h1>", Type: "business",
|
||||
}).Error)
|
||||
require.NoError(t, repository.SaveOrUpdateSystemConfig(ctx, model.ConfigKeyOriginErrorPageEnabled, "false"))
|
||||
require.NoError(t, repository.SaveOrUpdateSystemConfig(ctx, model.ConfigKeyOriginErrorPageStatusCodes, `["522","500-502"]`))
|
||||
require.NoError(t, repository.SaveOrUpdateSystemConfig(ctx, model.ConfigKeyOriginErrorPageHTML, "<h1>{{status}}</h1>"))
|
||||
|
||||
snapshot := buildOpenRestyConfigSnapshot(ctx)
|
||||
assert.False(t, snapshot.OriginErrorPageEnabled)
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
openrestyrender "Wavelet/openflare/share/render/openresty"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -95,7 +94,7 @@ func TestBuildSnapshotPagesDeploymentRejectsUnsafeStoredPaths(t *testing.T) {
|
||||
|
||||
func requireDB(t *testing.T, ctx context.Context) *gorm.DB {
|
||||
t.Helper()
|
||||
conn := db.DB(ctx)
|
||||
conn := repository.DB(ctx)
|
||||
require.NotNil(t, conn)
|
||||
require.NoError(t, conn.AutoMigrate(
|
||||
&model.PagesProject{},
|
||||
|
||||
+5
-6
@@ -13,7 +13,6 @@ import (
|
||||
|
||||
"Wavelet/openflare/plugins/server/domain/waf"
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -38,7 +37,7 @@ func TestBuildSnapshotRejectsOversizedAggregateWAFIPGroups(t *testing.T) {
|
||||
Enabled: true,
|
||||
IPList: string(ipList),
|
||||
}
|
||||
require.NoError(t, db.DB(ctx).Create(group).Error)
|
||||
require.NoError(t, repository.DB(ctx).Create(group).Error)
|
||||
groupIDs = append(groupIDs, group.ID)
|
||||
}
|
||||
createSnapshotRule(t, ctx, "oversized-aggregate", snapshotIPMatchGraphForGroups(groupIDs))
|
||||
@@ -59,8 +58,8 @@ func TestWAFGraphSnapshotPreservesOrderAndGraphReferences(t *testing.T) {
|
||||
|
||||
referenced := &model.OpenFlareWAFIPGroup{Name: "referenced", Type: "manual", Enabled: true, IPList: `["192.0.2.1"]`}
|
||||
unused := &model.OpenFlareWAFIPGroup{Name: "unused", Type: "manual", Enabled: true, IPList: `["198.51.100.1"]`}
|
||||
require.NoError(t, db.DB(ctx).Create(referenced).Error)
|
||||
require.NoError(t, db.DB(ctx).Create(unused).Error)
|
||||
require.NoError(t, repository.DB(ctx).Create(referenced).Error)
|
||||
require.NoError(t, repository.DB(ctx).Create(unused).Error)
|
||||
|
||||
customA := createSnapshotRule(t, ctx, "custom-a", waf.DefaultRuleGraph())
|
||||
customB := createSnapshotRule(t, ctx, "custom-b", snapshotIPMatchGraph(referenced.ID))
|
||||
@@ -114,7 +113,7 @@ func TestBuildSnapshotRejectsInvalidWAFGraph(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
invalid := &model.OpenFlareWAFRuleGroup{Name: "invalid", Enabled: true, Graph: `{"schema_version":1,"nodes":[],"edges":[]}`, Revision: 1}
|
||||
require.NoError(t, db.DB(ctx).Create(invalid).Error)
|
||||
require.NoError(t, repository.DB(ctx).Create(invalid).Error)
|
||||
_, err := buildSnapshotWAFDocument(ctx, nil)
|
||||
require.ErrorContains(t, err, "invalid")
|
||||
}
|
||||
@@ -124,7 +123,7 @@ func createSnapshotRule(t *testing.T, ctx context.Context, name string, graph wa
|
||||
raw, err := json.Marshal(graph)
|
||||
require.NoError(t, err)
|
||||
rule := &model.OpenFlareWAFRuleGroup{Name: name, Enabled: true, Graph: string(raw), Revision: 1}
|
||||
require.NoError(t, db.DB(ctx).Create(rule).Error)
|
||||
require.NoError(t, repository.DB(ctx).Create(rule).Error)
|
||||
return rule
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -25,9 +25,9 @@ func setupOriginTestDB(t *testing.T) func() {
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, sqliteDB.AutoMigrate(&model.Origin{}))
|
||||
|
||||
db.SetDB(sqliteDB)
|
||||
repository.SetDBForTest(sqliteDB)
|
||||
return func() {
|
||||
db.SetDB(nil)
|
||||
repository.SetDBForTest(nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -29,21 +28,21 @@ func setupProxyRouteTestDB(t *testing.T) func() {
|
||||
&model.TLSCertificate{},
|
||||
&model.PagesProject{},
|
||||
))
|
||||
db.SetDB(sqliteDB)
|
||||
return func() { db.SetDB(nil) }
|
||||
repository.SetDBForTest(sqliteDB)
|
||||
return func() { repository.SetDBForTest(nil) }
|
||||
}
|
||||
|
||||
func createZoneDomain(t *testing.T, ctx context.Context, domain string, certID *uint) *model.ZoneDomain {
|
||||
t.Helper()
|
||||
zone := &model.Zone{Domain: "example.com"}
|
||||
var existing model.Zone
|
||||
if err := db.DB(ctx).Where("domain = ?", zone.Domain).First(&existing).Error; err == nil {
|
||||
if err := repository.DB(ctx).Where("domain = ?", zone.Domain).First(&existing).Error; err == nil {
|
||||
zone = &existing
|
||||
} else {
|
||||
require.NoError(t, db.DB(ctx).Create(zone).Error)
|
||||
require.NoError(t, repository.DB(ctx).Create(zone).Error)
|
||||
}
|
||||
item := &model.ZoneDomain{ZoneID: zone.ID, Domain: domain, CertID: certID}
|
||||
require.NoError(t, db.DB(ctx).Create(item).Error)
|
||||
require.NoError(t, repository.DB(ctx).Create(item).Error)
|
||||
return item
|
||||
}
|
||||
|
||||
@@ -102,7 +101,7 @@ func TestPagesRouteLocksAndRevalidatesTargetProject(t *testing.T) {
|
||||
Enabled: true,
|
||||
ActiveDeploymentID: &activeDeploymentID,
|
||||
}
|
||||
require.NoError(t, db.DB(ctx).Create(project).Error)
|
||||
require.NoError(t, repository.DB(ctx).Create(project).Error)
|
||||
|
||||
view, err := CreateProxyRoute(ctx, Input{
|
||||
SiteName: "pages",
|
||||
@@ -115,7 +114,7 @@ func TestPagesRouteLocksAndRevalidatesTargetProject(t *testing.T) {
|
||||
require.NotNil(t, view.PagesProjectID)
|
||||
assert.Equal(t, project.ID, *view.PagesProjectID)
|
||||
|
||||
require.NoError(t, db.DB(ctx).Delete(&model.PagesProject{}, project.ID).Error)
|
||||
require.NoError(t, repository.DB(ctx).Delete(&model.PagesProject{}, project.ID).Error)
|
||||
route := &model.ProxyRoute{UpstreamType: proxyRouteUpstreamTypePages, PagesProjectID: &project.ID}
|
||||
err = repository.WithProxyRouteTx(ctx, func(tx *gorm.DB) error {
|
||||
return lockPagesProjectsForRouteMutation(tx, 0, route)
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"database/sql"
|
||||
"testing"
|
||||
|
||||
db "Wavelet/plugins/infra/database"
|
||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -65,10 +65,10 @@ func setupLegacyImportDB(t *testing.T) (*sql.DB, func()) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
previous := db.DB(context.Background())
|
||||
db.SetDB(gormDB)
|
||||
previous := repository.DB(context.Background())
|
||||
repository.SetDBForTest(gormDB)
|
||||
return sqlDB, func() {
|
||||
db.SetDB(previous)
|
||||
repository.SetDBForTest(previous)
|
||||
_ = sqlDB.Close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
"Wavelet/openflare/plugins/server/kernel/testhelper"
|
||||
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -24,8 +23,8 @@ func setupZoneDB(t *testing.T) context.Context {
|
||||
conn, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, conn.AutoMigrate(&model.Zone{}, &model.ZoneDomain{}, &model.TLSCertificate{}, &model.CFPointingGroup{}, &model.CFPointingMember{}))
|
||||
db.SetDB(conn)
|
||||
t.Cleanup(func() { db.SetDB(nil) })
|
||||
repository.SetDBForTest(conn)
|
||||
t.Cleanup(func() { repository.SetDBForTest(nil) })
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@ import (
|
||||
"mime/multipart"
|
||||
"strings"
|
||||
|
||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
"Wavelet/openflare/plugins/server/kernel/repository"
|
||||
"Wavelet/openflare/plugins/server/kernel/task"
|
||||
"Wavelet/pkg/util"
|
||||
)
|
||||
|
||||
// CertificateInput TLS 证书创建/更新请求。
|
||||
@@ -202,10 +202,10 @@ func ApplyCertificate(ctx context.Context, input ApplyInput) (*model.TLSCertific
|
||||
returned := sanitizeCertificateForResponse(cert)
|
||||
|
||||
obtainFn := obtainTLSCertificate // 捕获当前实现,避免 goroutine 内读可变包变量(测试热替换)
|
||||
go func(c *model.TLSCertificate) {
|
||||
util.Go(func() {
|
||||
asyncCtx := context.WithoutCancel(ctx)
|
||||
_ = obtainFn(asyncCtx, c)
|
||||
}(cert)
|
||||
_ = obtainFn(asyncCtx, cert)
|
||||
})
|
||||
|
||||
return returned, nil
|
||||
}
|
||||
@@ -233,10 +233,10 @@ func UpdateACMECertificate(ctx context.Context, id uint, input ApplyInput) (*mod
|
||||
returned := sanitizeCertificateForResponse(cert)
|
||||
|
||||
obtainFn := obtainTLSCertificate // 捕获当前实现,避免 goroutine 内读可变包变量(测试热替换)
|
||||
go func(c *model.TLSCertificate) {
|
||||
util.Go(func() {
|
||||
asyncCtx := context.WithoutCancel(ctx)
|
||||
_ = obtainFn(asyncCtx, c)
|
||||
}(cert)
|
||||
_ = obtainFn(asyncCtx, cert)
|
||||
})
|
||||
|
||||
return returned, nil
|
||||
}
|
||||
@@ -266,12 +266,12 @@ func ConvertCertificateToACME(ctx context.Context, id uint, input ApplyInput) (*
|
||||
}
|
||||
|
||||
obtainFn := obtainTLSCertificate // 捕获当前实现,避免 goroutine 内读可变包变量(测试热替换)
|
||||
go func(c *model.TLSCertificate) {
|
||||
util.Go(func() {
|
||||
asyncCtx := context.WithoutCancel(ctx)
|
||||
if err := obtainFn(asyncCtx, c); err != nil {
|
||||
if err := obtainFn(asyncCtx, cert); err != nil {
|
||||
return
|
||||
}
|
||||
latest, err := repository.GetTLSCertificateByID(asyncCtx, c.ID)
|
||||
latest, err := repository.GetTLSCertificateByID(asyncCtx, cert.ID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -279,7 +279,7 @@ func ConvertCertificateToACME(ctx context.Context, id uint, input ApplyInput) (*
|
||||
latest.ApplyStatus = tlsApplyStatusReady
|
||||
latest.ApplyMessage = ""
|
||||
_ = repository.SaveTLSCertificate(asyncCtx, latest)
|
||||
}(cert)
|
||||
})
|
||||
|
||||
return sanitizeCertificateForResponse(cert), nil
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
"Wavelet/openflare/plugins/server/kernel/model"
|
||||
"Wavelet/openflare/plugins/server/kernel/runtimeconfig"
|
||||
"Wavelet/pkg/idgen"
|
||||
db "Wavelet/plugins/infra/database"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -51,7 +50,7 @@ func setupTLSTestDB(t *testing.T) func() {
|
||||
&model.TaskExecution{}, // 异步任务执行记录也需要 migrate
|
||||
))
|
||||
|
||||
db.SetDB(sqliteDB)
|
||||
repository.SetDBForTest(sqliteDB)
|
||||
require.NoError(t, idgen.Init(1))
|
||||
previous := runtimeconfig.Get()
|
||||
runtimeconfig.SetSessionSecret("test_session_secret_for_tls_encryption")
|
||||
@@ -59,7 +58,7 @@ func setupTLSTestDB(t *testing.T) func() {
|
||||
oftask.SetService(&testhelper.NoopTaskService{})
|
||||
|
||||
return func() {
|
||||
db.SetDB(nil)
|
||||
repository.SetDBForTest(nil)
|
||||
runtimeconfig.Set(previous)
|
||||
credential.SetSessionSecret(previous.SessionSecret)
|
||||
tlsTestDBMu.Unlock()
|
||||
@@ -75,8 +74,8 @@ func TestDeleteCertificateRejectsZoneDomainReference(t *testing.T) {
|
||||
certificate, err := CreateCertificate(ctx, CertificateInput{Name: "api-cert", CertPEM: certPEM, KeyPEM: keyPEM})
|
||||
require.NoError(t, err)
|
||||
zone := &model.Zone{Domain: "example.com"}
|
||||
require.NoError(t, db.DB(ctx).Create(zone).Error)
|
||||
require.NoError(t, db.DB(ctx).Create(&model.ZoneDomain{ZoneID: zone.ID, Domain: "api.example.com", CertID: &certificate.ID}).Error)
|
||||
require.NoError(t, repository.DB(ctx).Create(zone).Error)
|
||||
require.NoError(t, repository.DB(ctx).Create(&model.ZoneDomain{ZoneID: zone.ID, Domain: "api.example.com", CertID: &certificate.ID}).Error)
|
||||
|
||||
err = DeleteCertificate(ctx, certificate.ID)
|
||||
require.EqualError(t, err, errCertificateDeleteReferenced)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user