feat: 升级 samber/do v1 → v2

This commit is contained in:
2026-05-15 09:04:12 +08:00
parent 7741f7584d
commit 9ad5d25836
47 changed files with 1456 additions and 684 deletions
+8 -1
View File
@@ -1,6 +1,6 @@
package contracts package contracts
type Service interface { type App interface {
LoadGoPlugin(file string) error LoadGoPlugin(file string) error
RegControllers(items map[string]Controller) error RegControllers(items map[string]Controller) error
RegListens(items map[string]ResListener) error RegListens(items map[string]ResListener) error
@@ -8,10 +8,17 @@ type Service interface {
RegJobs(items map[string]Job) error RegJobs(items map[string]Job) error
RegCrons(items map[string]Cron) error RegCrons(items map[string]Cron) error
RegMqtts(items map[string]Mqtt) error RegMqtts(items map[string]Mqtt) error
RegFlowDecisions(items map[string]FlowDecision) error
RegGoBridges(items map[string]GoBridge) error
RegResVirtualTables(items map[string]ResVirtualTable) error
GetResListener(code string) (ResListener, bool) GetResListener(code string) (ResListener, bool)
GetAppController(code string) (Controller, bool) GetAppController(code string) (Controller, bool)
GetAppGRpc(code string) (GRpc, bool) GetAppGRpc(code string) (GRpc, bool)
GetAppJob(code string) (Job, bool) GetAppJob(code string) (Job, bool)
GetAppCron(code string) (Cron, bool) GetAppCron(code string) (Cron, bool)
GetAppMqtt(code string) (Mqtt, bool) GetAppMqtt(code string) (Mqtt, bool)
GetAppFlowDecision(code string) (FlowDecision, bool)
GetAppGoBridge(code string) (GoBridge, bool)
GetAppResVirtualTable(code string) (ResVirtualTable, bool)
} }
+37
View File
@@ -0,0 +1,37 @@
package base
// DataType 定义参数数据类型
type DataType string
const (
DataTypeString DataType = "string" // 字符串类型
DataTypeInteger DataType = "integer" // 整数类型
DataTypeFloat DataType = "float" // 浮点数类型
DataTypeBoolean DataType = "boolean" // 布尔类型
DataTypeJSON DataType = "json" // JSON类型
)
// SchemaParam 工作流参数
type SchemaParam struct {
Name string `db:"name" json:"name"`
Code string `db:"code" json:"code"`
DataType DataType `db:"data_type" json:"data_type"`
Required bool `db:"required" json:"required"`
Comment string `db:"comment" json:"comment"`
Fields SchemaParams `db:"fields" json:"fields,omitempty"`
}
type SchemaParams []SchemaParam
// Condflow 工作流
type Condflow struct {
Uuid string `db:"uuid" json:"uuid"`
Name string `db:"name" json:"name"`
Code string `db:"code" json:"code"`
Comment string `db:"comment" json:"comment"`
Params SchemaParams `db:"params" json:"params"`
CreatedAt string `db:"created_at" json:"created_at"`
UpdatedAt string `db:"updated_at" json:"updated_at"`
}
type GetCondflow func(code string) (Condflow, bool)
+23
View File
@@ -0,0 +1,23 @@
package base
import "git.fsdpf.net/go/reflux/fieldx"
// CondflowCase 工作流条件分支
type CondflowCase struct {
Uuid string `db:"uuid" json:"uuid"`
CondflowUuid string `db:"condflow_uuid" json:"condflow_uuid"`
Name string `db:"name" json:"name"`
Code string `db:"code" json:"code"`
Priority int `db:"priority" json:"priority"`
Action string `db:"action" json:"action"`
ActionParams fieldx.Schema `db:"action_params" json:"action_params"` // 动作参数值, 对应 params_schema
ActionConfig fieldx.Schema `db:"action_config" json:"action_config"` // 动作配置值, 对应 config_schema
NextCaseOnSuccess bool `db:"next_case_on_success" json:"next_case_on_success"`
NextFlowUuid string `db:"next_flow_uuid" json:"next_flow_uuid"`
NextFlowParams fieldx.Schema `db:"next_flow_params" json:"next_flow_params"` // 流程间参数映射
Comment string `db:"comment" json:"comment"`
CreatedAt string `db:"created_at" json:"created_at"`
UpdatedAt string `db:"updated_at" json:"updated_at"`
}
type GetCondflowCases func(uuid string) []CondflowCase
+25
View File
@@ -0,0 +1,25 @@
package base
// CondflowDecisionAction 执行器动作定义
type CondflowDecisionAction struct {
Name string `db:"name" json:"name"`
Code string `db:"code" json:"code"`
ConfigSchema SchemaParams `db:"config_schema" json:"config_schema"` // 配置项Schema定义
ParamsSchema SchemaParams `db:"params_schema" json:"params_schema"` // 参数Schema定义
ResourceUuid string `db:"resource_uuid" json:"resource_uuid"`
Comment string `db:"comment" json:"comment"`
}
// CondflowDecision 执行器注册表
type CondflowDecision struct {
Uuid string `db:"uuid" json:"uuid"`
ResourceUuid string `db:"resource_uuid" json:"resource_uuid"`
Name string `db:"name" json:"name"`
Code string `db:"code" json:"code"`
Comment string `db:"comment" json:"comment"`
Actions []CondflowDecisionAction `db:"actions" json:"actions"`
CreatedAt string `db:"created_at" json:"created_at"`
UpdatedAt string `db:"updated_at" json:"updated_at"`
}
type GetCondflowDecision func(code string) (CondflowDecision, bool)
+1
View File
@@ -51,6 +51,7 @@ type DataList struct {
ThemeConfig map[string]any `db:"themeConfig"` // 列表布局配置 ThemeConfig map[string]any `db:"themeConfig"` // 列表布局配置
Roles []string `db:"roles"` // 列表权限 Roles []string `db:"roles"` // 列表权限
IsAuthDB int `db:"is_auth_db"` // 启用权限过滤 IsAuthDB int `db:"is_auth_db"` // 启用权限过滤
SelectionType string `db:"selectionType"` // 行选择类型
Platform string `db:"platform"` Platform string `db:"platform"`
UpdatedAt string `db:"updated_at"` UpdatedAt string `db:"updated_at"`
CreatedAt string `db:"created_at"` CreatedAt string `db:"created_at"`
+1
View File
@@ -25,6 +25,7 @@ type DataListField struct {
FilterByJsSetting map[string]any `db:"filterByJsSetting"` // 字段筛选组件设置参数 FilterByJsSetting map[string]any `db:"filterByJsSetting"` // 字段筛选组件设置参数
FilterOperator string `db:"filterOperator"` // 字段筛选符号 FilterOperator string `db:"filterOperator"` // 字段筛选符号
EditableRoles []string `db:"editableRoles"` // 字段编辑权限 EditableRoles []string `db:"editableRoles"` // 字段编辑权限
Middlewares [][2]any `db:"middlewares"` // 字段数据转换
UpdatedAt string `db:"updated_at"` UpdatedAt string `db:"updated_at"`
CreatedAt string `db:"created_at"` CreatedAt string `db:"created_at"`
} }
+19 -16
View File
@@ -1,22 +1,25 @@
package base package base
type GridLayout struct { type GridLayout struct {
Uuid string `db:"uuid"` Uuid string `db:"uuid"`
ResourceUuid string `db:"resource_uuid"` // 所属资源UUID ResourceUuid string `db:"resource_uuid"` // 所属资源UUID
Code string `db:"code"` // 布局标识 Code string `db:"code"` // 布局标识
Name string `db:"name"` // 布局名称 Name string `db:"name"` // 布局名称
Type int `db:"type"` // 布局类型 [0=普通布局, 1=辅助布局] Type int `db:"type"` // 布局类型 [0=普通布局, 1=辅助布局]
PrimaryKey string `db:"primaryKey"` // 主键 PrimaryKey string `db:"primaryKey"` // 主键
MarginX int `db:"marginX"` // 栅格 x 间距 MarginX int `db:"marginX"` // 栅格 x 间距
MarginY int `db:"marginY"` // 栅格 y 间距 MarginY int `db:"marginY"` // 栅格 y 间距
Cols int `db:"cols"` // 网格列基数 Cols int `db:"cols"` // 网格列基数
RowHeight float32 `db:"rowHeight"` // 栅格行高 RowHeight float32 `db:"rowHeight"` // 栅格行高
Css map[string]any `db:"css"` // 布局CSS Css map[string]any `db:"css"` // 布局CSS
Props []any `db:"props"` // 布局PROPS Props []any `db:"props"` // 布局PROPS
Platform string `db:"platform"` Groups []map[string]any `db:"groups"` // 布局分组
Roles []string `db:"roles"` Theme string `db:"theme"` // 布局样式
UpdatedAt string `db:"updated_at"` ThemeConfig map[string]any `db:"themeConfig"` // 布局样式配置
CreatedAt string `db:"created_at"` Platform string `db:"platform"`
Roles []string `db:"roles"`
UpdatedAt string `db:"updated_at"`
CreatedAt string `db:"created_at"`
} }
type GetGridLayout func(code string) (GridLayout, bool) type GetGridLayout func(code string) (GridLayout, bool)
+1
View File
@@ -3,6 +3,7 @@ package base
type GridLayoutField struct { type GridLayoutField struct {
Uuid string `db:"uuid"` Uuid string `db:"uuid"`
GridLayoutUuid string `db:"grid_layout_uuid"` // 所属布局 UUID GridLayoutUuid string `db:"grid_layout_uuid"` // 所属布局 UUID
Group string `db:"group"`
Type string `db:"type"` Type string `db:"type"`
X int `db:"x"` X int `db:"x"`
Y int `db:"y"` Y int `db:"y"`
+3 -1
View File
@@ -14,7 +14,9 @@ type GridLayoutForm struct {
MarginY int `db:"marginY"` // 栅格 y 间距 MarginY int `db:"marginY"` // 栅格 y 间距
Cols int `db:"cols"` // 网格列基数 Cols int `db:"cols"` // 网格列基数
FormProps []map[string]string `db:"formProps"` // 表单PROPS FormProps []map[string]string `db:"formProps"` // 表单PROPS
Groups []map[string]any `db:"groups"` // 表单分组 Groups []map[string]any `db:"groups"` // 布局分组
Theme string `db:"theme"` // 布局样式
ThemeConfig map[string]any `db:"themeConfig"` // 布局样式配置
ListenChangeFields []string `db:"listenChangeFields"` // 监听字段 ListenChangeFields []string `db:"listenChangeFields"` // 监听字段
ListenChangeFieldsFunc string `db:"listenChangeFieldsFunc"` // 监听字段回调方法 ListenChangeFieldsFunc string `db:"listenChangeFieldsFunc"` // 监听字段回调方法
Platform string `db:"platform"` Platform string `db:"platform"`
+15 -9
View File
@@ -6,7 +6,9 @@ import (
"strings" "strings"
"git.fsdpf.net/go/contracts" "git.fsdpf.net/go/contracts"
"github.com/samber/do" "git.fsdpf.net/go/db"
"git.fsdpf.net/go/req"
"github.com/samber/do/v2"
) )
type PkgVersion struct { type PkgVersion struct {
@@ -39,7 +41,7 @@ func (this PkgVersion) GetSourceMapPath(prefix ...string) string {
) )
} }
func (this PkgVersion) GetFile(app *do.Injector) (file string, err error) { func (this PkgVersion) GetFile(app do.Injector, u req.User) (file string, err error) {
if this.Id == 0 { if this.Id == 0 {
return file, contracts.ErrPkgUnknown return file, contracts.ErrPkgUnknown
} }
@@ -50,23 +52,27 @@ func (this PkgVersion) GetFile(app *do.Injector) (file string, err error) {
return file, contracts.ErrResNotFound return file, contracts.ErrResNotFound
} }
_, err = res.GetDBTable().Where("id", this.Id).Value(&file, "file") ok, err = res.GetDBTable(u).Where(db.C("id").Eq(this.Id)).Select("file").ScanVal(&file)
if !ok {
return file, contracts.ErrResNotFound
}
return file, err return file, err
} }
func (this PkgVersion) GetSourceMap(app *do.Injector) (file string, err error) { func (this PkgVersion) GetSourceMap(app do.Injector) (file string, err error) {
if this.Id == 0 { if this.Id == 0 {
return file, contracts.ErrPkgUnknown return file, contracts.ErrPkgUnknown
} }
res, ok := do.MustInvoke[contracts.GetResource](app)("PkgVersion") // res, ok := do.MustInvoke[contracts.GetResource](app)("PkgVersion")
if !ok { // if !ok {
return file, contracts.ErrResNotFound // return file, contracts.ErrResNotFound
} // }
_, err = res.GetDBTable().Where("id", this.Id).Value(&file, "sourcemap") // _, err = res.GetDBTable().Where("id", this.Id).Value(&file, "sourcemap")
return file, err return file, err
} }
+7 -8
View File
@@ -5,7 +5,6 @@ import (
"strings" "strings"
"unicode" "unicode"
"git.fsdpf.net/go/contracts/res_type"
"git.fsdpf.net/go/db" "git.fsdpf.net/go/db"
"git.fsdpf.net/go/req" "git.fsdpf.net/go/req"
) )
@@ -67,13 +66,13 @@ func (this *QueryField) ToSql() db.Expression {
if this.Alias() != "" { if this.Alias() != "" {
if this.IsExpr() { if this.IsExpr() {
if this.GetCode() == "" { if this.GetCode() == "" {
return db.Raw("null as `" + this.Alias() + "`") return db.V(nil).As(this.Alias())
} }
return db.Raw("(" + this.GetCode() + ") as `" + this.Alias() + "`") return db.L(this.GetCode()).As(this.Alias())
} }
return db.Raw("`" + this.GetCodeResource() + "`.`" + this.GetCode() + "` as `" + this.Alias() + "`") return db.T(this.GetCodeResource()).Col(this.GetCode()).As(this.Alias())
} }
return db.Raw("`" + this.GetCodeResource() + "`.`" + this.GetCode() + "`") return db.T(this.GetCodeResource()).Col(this.GetCode())
} }
func (this *QueryField) ToStructField(tags ...string) reflect.StructField { func (this *QueryField) ToStructField(tags ...string) reflect.StructField {
@@ -104,11 +103,11 @@ func (this *QueryField) ToStructField(tags ...string) reflect.StructField {
case req.ReqString: case req.ReqString:
typ = reflect.TypeOf(string("")) typ = reflect.TypeOf(string(""))
case req.ReqNumber: case req.ReqNumber:
typ = reflect.TypeOf(res_type.ResFieldByNumber(0)) typ = reflect.TypeOf(float64(0))
case req.ReqInteger: case req.ReqInteger:
typ = reflect.TypeOf(res_type.ResFieldByInteger(0)) typ = reflect.TypeOf(int64(0))
case req.ReqFloat: case req.ReqFloat:
typ = reflect.TypeOf(res_type.ResFieldByFloat(0)) typ = reflect.TypeOf(float64(0))
case req.ReqBool: case req.ReqBool:
typ = reflect.TypeOf(true) typ = reflect.TypeOf(true)
case req.ReqArray: case req.ReqArray:
+6 -6
View File
@@ -1,11 +1,11 @@
package base package base
type ResApiParam struct { type ResApiParam struct {
Code string `db:"code"` Code string `db:"code" json:"code"`
Name string `db:"name"` Name string `db:"name" json:"name"`
Category string `db:"category"` Category string `db:"category" json:"category"`
DataType string `db:"type"` DataType string `db:"type" json:"type"`
IsRequired bool `db:"isRequired"` IsRequired bool `db:"isRequired" json:"isRequired"`
} }
type ResApi struct { type ResApi struct {
@@ -16,7 +16,7 @@ type ResApi struct {
PrimaryKey string `db:"primaryKey"` PrimaryKey string `db:"primaryKey"`
ResourceUuid string `db:"resource_uuid"` ResourceUuid string `db:"resource_uuid"`
Method string `db:"method"` Method string `db:"method"`
Category string `db:"action"` Action string `db:"action"`
Params []ResApiParam `db:"params"` Params []ResApiParam `db:"params"`
Roles []string `db:"roles"` Roles []string `db:"roles"`
IsAuthDB int `db:"is_auth_db"` // 启用权限过滤 IsAuthDB int `db:"is_auth_db"` // 启用权限过滤
+4 -2
View File
@@ -1,6 +1,8 @@
package base package base
import "git.fsdpf.net/go/condition" import (
"git.fsdpf.net/go/condition"
)
type ResCondition struct { type ResCondition struct {
Id int `db:"id"` Id int `db:"id"`
@@ -53,7 +55,7 @@ func NewConditionByRes(items []ResCondition, opts ...condition.Option) (root *co
conditions[item.Id] = condition.NewExpr( conditions[item.Id] = condition.NewExpr(
item.ColumnResource, item.Column, item.ColumnResource, item.Column,
condition.Token(item.Value, condition.TokenType(item.ValueType)), condition.Token(item.Value, condition.TokenType(item.ValueType)),
condition.Operator(condition.ConditionOperator(item.Operator)), condition.Operator(condition.ConditionOperator(condition.GetDBOperator(item.Operator))),
condition.FieldSqlFn(item.ColumnSqlFunc, item.ColumnSqlFuncParam), condition.FieldSqlFn(item.ColumnSqlFunc, item.ColumnSqlFuncParam),
condition.IgnoreEmptyParma(item.IgnoreEmptyParma), condition.IgnoreEmptyParma(item.IgnoreEmptyParma),
) )
+43 -11
View File
@@ -1,16 +1,48 @@
package base package base
import (
"git.fsdpf.net/go/reflux"
"git.fsdpf.net/go/reflux/valuex"
)
// role_uuid => value
type ResRoleValue map[string]string
type ResConfigure struct { type ResConfigure struct {
Uuid string `db:"uuid"` Uuid string `db:"uuid"`
ResourceUuid string `db:"resource_uuid"` ResourceUuid string `db:"resource_uuid"`
Key string `db:"key"` Key string `db:"key"`
Value string `db:"value"` Value string `db:"value"`
Label string `db:"label"` Label string `db:"label"`
Type string `db:"type"` Type string `db:"type"`
IsPrivate bool `db:"isPrivate"` IsPrivate bool `db:"isPrivate"`
Desc string `db:"desc"` Desc string `db:"desc"`
UpdatedAt string `db:"updated_at"` RoleValues []ResRoleValue `db:"role_values"`
CreatedAt string `db:"created_at"` UpdatedAt string `db:"updated_at"`
CreatedAt string `db:"created_at"`
} }
type GetResConfigures func(uuid string) []ResConfigure // GetValueByRoles 根据角色优先级获取配置值
//
// - roles: 角色UUIDs
//
// 返回: valuex.Accessor 对象和是否找到角色特定值的标志
func (rc *ResConfigure) GetValueByRoles(roles ...string) (valuex.Accessor, bool) {
// 如果没有角色值配置或没有传入角色,返回默认值
if len(rc.RoleValues) == 0 || len(roles) == 0 {
return reflux.New(rc.Value), false
}
for _, rv := range rc.RoleValues {
for _, role := range roles {
if value, exists := rv[role]; exists {
return reflux.New(value), true
}
}
}
// 没有找到匹配的角色值,返回默认值
return reflux.New(rc.Value), false
}
type GetResConfigure func(key string, roles ...string) (valuex.Accessor, bool)
+62 -54
View File
@@ -7,24 +7,24 @@ import (
"strconv" "strconv"
"strings" "strings"
"git.fsdpf.net/go/contracts/res_type"
"git.fsdpf.net/go/db" "git.fsdpf.net/go/db"
"git.fsdpf.net/go/db/schema" "git.fsdpf.net/go/db/schema"
"git.fsdpf.net/go/req" "git.fsdpf.net/go/req"
"github.com/google/uuid"
"github.com/spf13/cast" "github.com/spf13/cast"
) )
// 资源字段 // ResField 资源字段
type ResField struct { type ResField struct {
Uuid string `db:"uuid"` Uuid string `db:"uuid" json:"uuid"`
Name string `db:"name"` Name string `db:"name" json:"name"`
Code string `db:"code"` Code string `db:"code" json:"code"`
CodeResource string `db:"codeResource"` CodeResource string `db:"codeResource" json:"codeResource"`
DataType req.ResDataType `db:"table_type"` DataType req.ResDataType `db:"data_type" json:"data_type"`
Length string `db:"length"` Length string `db:"length" json:"length"`
Comment string `db:"comment"` Comment string `db:"comment" json:"comment"`
Default string `db:"default"` Default string `db:"default" json:"default"`
Virtual bool `db:"virtual" json:"virtual"`
VirtualExpr string `db:"virtual_expr" json:"virtual_expr"`
} }
func (this ResField) ToStructField(tags ...string) reflect.StructField { func (this ResField) ToStructField(tags ...string) reflect.StructField {
@@ -42,7 +42,7 @@ func (this ResField) ToStructField(tags ...string) reflect.StructField {
req.ResTimestamp, req.ResDate, req.ResDatetime: req.ResTimestamp, req.ResDate, req.ResDatetime:
typ = reflect.TypeOf(string("")) typ = reflect.TypeOf(string(""))
case req.ResInteger, req.ResSmallInteger: case req.ResInteger, req.ResSmallInteger:
typ = reflect.TypeOf(res_type.ResFieldByInteger(0)) typ = reflect.TypeOf(int64(0))
case req.ResDecimal: case req.ResDecimal:
typ = reflect.TypeOf(float64(0)) typ = reflect.TypeOf(float64(0))
case req.ResBoolean: case req.ResBoolean:
@@ -62,6 +62,10 @@ func (this ResField) ToStructField(tags ...string) reflect.StructField {
} }
} }
func (this ResField) IsVirtual() bool {
return this.Virtual
}
func (this ResField) GetCode() string { func (this ResField) GetCode() string {
return this.Code return this.Code
} }
@@ -101,30 +105,16 @@ func (this ResField) GetQueryDataType() req.RouteParamType {
} }
func (this ResField) ToValue(v any) any { func (this ResField) ToValue(v any) any {
switch this.DataType { if this.DataType == req.ResJson {
case req.ResString, req.ResText, req.ResEnum,
req.ResTimestamp, req.ResDate, req.ResDatetime:
return strings.Trim(cast.ToString(v), " ")
case req.ResInteger, req.ResSmallInteger:
return cast.ToInt(v)
case req.ResDecimal:
return strings.Trim(cast.ToString(v), " ")
case req.ResBoolean:
if v, _ := strconv.ParseBool(fmt.Sprintf("%v", v)); v {
return 1
}
return 0
case req.ResJson:
if v == nil { if v == nil {
if this.Default != "" && this.Default[0:1] == "[" { if this.Default != "" && this.Default[0:1] == "[" {
return db.Raw("'[]'") return db.V("[]")
} else if this.Default != "" && this.Default[0:1] == "{" { } else if this.Default != "" && this.Default[0:1] == "{" {
return db.Raw("'{}'") return db.V("{}")
} else if this.Default == "" { } else if this.Default == "" {
return db.Raw("'{}'") return db.V("{}")
} }
return this.GetRawDefault()
return this.Default
} }
if str, ok := v.(string); ok { if str, ok := v.(string); ok {
@@ -137,52 +127,61 @@ func (this ResField) ToValue(v any) any {
panic(fmt.Sprintf("%s, 类型转换错误, %s", this.Code, err)) panic(fmt.Sprintf("%s, 类型转换错误, %s", this.Code, err))
} }
} }
return v
return strings.Trim(cast.ToString(v), " ")
} }
func (this ResField) GetRawDefault(driver string) db.Expression { func (this ResField) GetRawDefault() db.Expression {
if this.DataType == req.ResJson { if this.DataType == req.ResJson {
if this.Default != "" && this.Default[0:1] == "[" { if this.Default != "" && this.Default[0:1] == "[" {
return db.Raw("'[]'") return db.V("[]")
} else if this.Default != "" && this.Default[0:1] == "{" { } else if this.Default != "" && this.Default[0:1] == "{" {
return db.Raw("'{}'") return db.V("{}")
} else if this.Default == "" { } else if this.Default == "" {
return db.Raw("'{}'") return db.V("{}")
} }
} else if this.DataType == req.ResBoolean { } else if this.DataType == req.ResBoolean {
if v, _ := strconv.ParseBool(this.Default); v { if v, _ := strconv.ParseBool(this.Default); v {
return db.Raw("'1'") return db.V(true)
} }
return db.Raw("'0'") return db.V(false)
} }
if len(this.Default) > 4 && strings.ToLower(this.Default[0:4]) == "sql:" { if len(this.Default) > 4 && strings.ToLower(this.Default[0:4]) == "sql:" {
sql := strings.ToLower(this.Default[4:]) return db.L(this.Default[4:])
if sql == "uuid()" {
if driver == "sqlite" {
return db.Raw("'" + uuid.NewString() + "'")
}
return db.Raw("uuid()")
}
return db.Raw(sql)
} }
if this.Default == "" { if this.Default == "" {
if this.GetDataType() == req.ResDate || this.GetDataType() == req.ResDatetime { if this.GetDataType() == req.ResDate || this.GetDataType() == req.ResDatetime {
return db.Raw("NULL") return db.V(nil)
} }
return db.Raw("''") return db.V("")
} }
if strings.ToUpper(this.Default) == "CURRENT_TIMESTAMP" { if strings.ToUpper(this.Default) == "CURRENT_TIMESTAMP" {
return db.Raw(this.Default) return db.L(this.Default)
} }
return db.Raw("'" + this.Default + "'") return db.V(this.Default)
} }
func (this ResField) ToBlueprint(table *schema.Blueprint) (temp *schema.ColumnDefinition) { func (this ResField) ToBlueprint(table *schema.Blueprint) (temp *schema.ColumnDefinition) {
switch this.Code {
case "id":
return table.BigIncrements("id").AutoIncrement().Comment("ID")
case "enabled":
return table.Boolean("enabled").Default("1").Comment("是否有效")
case "created_user":
return table.Char("created_user", 36).Default("00000000-0000-0000-0000-000000000000").Comment("创建者")
case "owned_user":
return table.Char("owned_user", 36).Default("00000000-0000-0000-0000-000000000000").Comment("拥有者")
case "created_at":
return table.Timestamp("created_at").UseCurrent().Comment("创建时间")
case "updated_at":
return table.Timestamp("updated_at").UseCurrent().Default(db.L("ON UPDATE CURRENT_TIMESTAMP")).Comment("更新时间")
case "deleted_at":
return table.DateTime("deleted_at").Nullable().Comment("删除时间")
}
isNull := false isNull := false
comment := this.Name comment := this.Name
def := any(this.Default) def := any(this.Default)
@@ -207,15 +206,15 @@ func (this ResField) ToBlueprint(table *schema.Blueprint) (temp *schema.ColumnDe
case "integer": case "integer":
// integer 默认长度 11 // integer 默认长度 11
temp = table.Integer(this.Code) temp = table.Integer(this.Code)
case "date", "dateTime": case "date", "dateTime", "timestamp":
if this.DataType == "date" { if this.DataType == "date" {
temp = table.Date(this.Code) temp = table.Date(this.Code)
} else { } else {
temp = table.DateTime(this.Code) temp = table.DateTime(this.Code)
} }
if strings.ToUpper(this.Default) == "CURRENT_TIMESTAMP" { if strings.ToUpper(this.Default) == "SQL:CURRENT_TIMESTAMP" {
def = db.Raw(this.Default) def = db.L("CURRENT_TIMESTAMP")
} else if def == "" { } else if def == "" {
isNull = true isNull = true
} }
@@ -242,6 +241,15 @@ func (this ResField) ToBlueprint(table *schema.Blueprint) (temp *schema.ColumnDe
case "text": case "text":
temp = table.Text(this.Code) temp = table.Text(this.Code)
isNull = true isNull = true
case "vector":
temp = table.Vector(this.Code, cast.ToInt(this.Length))
isNull = false
default:
panic(fmt.Sprintf("Unknown type: %s", this.DataType))
}
if this.IsVirtual() {
temp.VirtualAs(this.VirtualExpr)
} }
if isNull { if isNull {
+30
View File
@@ -0,0 +1,30 @@
package base
import (
"testing"
"git.fsdpf.net/go/db/schema"
"git.fsdpf.net/go/req"
_ "git.fsdpf.net/go/db/schema/dialect/mysql"
_ "git.fsdpf.net/go/db/schema/dialect/sqlite3"
)
func TestResFieldToBlueprint(t *testing.T) {
t.Run("id", func(t *testing.T) {
table := schema.NewBlueprint("users")
id := ResField{
Uuid: "00000000-0000-0000-0000-000000000000",
Name: "ID",
Code: "id",
CodeResource: "User",
DataType: req.ResInteger,
Length: "20",
Comment: "",
Default: "sql:AUTO_INCREMENT",
}
id.ToBlueprint(table)
})
}
-26
View File
@@ -1,26 +0,0 @@
package base
import (
"database/sql/driver"
"encoding/json"
"fmt"
)
type ResFields []ResField
func (c *ResFields) Scan(value any) error {
if value == nil {
return nil
}
if err := json.Unmarshal(value.([]byte), c); err != nil {
return fmt.Errorf("ResFields json.Unmarshal Error, %s", err)
}
return nil
}
func (c ResFields) Value() (driver.Value, error) {
b, err := json.Marshal(c)
return string(b), err
}
+222 -249
View File
@@ -5,21 +5,26 @@ import (
"fmt" "fmt"
"log" "log"
"reflect" "reflect"
"sync"
"unicode" "unicode"
"github.com/samber/do" "github.com/samber/do/v2"
"github.com/samber/lo" "github.com/samber/lo"
"git.fsdpf.net/go/condition" "git.fsdpf.net/go/condition"
"git.fsdpf.net/go/contracts" "git.fsdpf.net/go/contracts"
"git.fsdpf.net/go/db" "git.fsdpf.net/go/db"
"git.fsdpf.net/go/db/engine"
"git.fsdpf.net/go/db/exec"
"git.fsdpf.net/go/db/exp"
"git.fsdpf.net/go/db/schema"
"git.fsdpf.net/go/req" "git.fsdpf.net/go/req"
) )
// 资源变更事件 // ResChangeEventTopic 资源变更事件
const ResChangeEventTopic = "res-change-event-topic" const ResChangeEventTopic = "res-change-event-topic"
// 资源变更数据 // ResChangeRecordTopic 资源变更数据
const ResChangeRecordTopic = "res-change-record-topic" const ResChangeRecordTopic = "res-change-record-topic"
type ResChangeEventTopicPayload struct { type ResChangeEventTopicPayload struct {
@@ -37,61 +42,72 @@ type ResChangeRecordTopicPayload struct {
New []map[string]any // 新数据 New []map[string]any // 新数据
} }
// 资源 // ResVirtualTableSetup 由 framework-v2 在 sqlite_vtable build tag 下注册到容器。
// resource.DB() 通过 do.Invoke 取出后调用,完成 vtab 模块注册和 CREATE VIRTUAL TABLE。
// res 提供 Code / Table / Fields 信息,vt 是 ResVirtualTable 的具体实现。
type ResVirtualTableSetup func(res Resource) error
// Resource 资源
type Resource struct { type Resource struct {
container *do.Injector container do.Injector
initOnce *sync.Once
Uuid string `db:"uuid"` Uuid string `db:"uuid"`
PUuid string `db:"pUuid"` PUuid string `db:"pUuid"`
Code string `db:"code"` Code string `db:"code"`
Name string `db:"name"` Name string `db:"name"`
IsResSystem bool `db:"isSystem"` Conn string `db:"conn"`
IsResVirtual bool `db:"isVirtual"` IsResVirtual bool `db:"isVirtual"`
Table string `db:"table"` Table string `db:"table"`
Primarykey string `db:"primaryKey"` Primarykey string `db:"primaryKey"`
IsHistoryRecord bool `db:"isHistoryRecord"` HistoryRoles []string `db:"historyRoles"`
HistoryCacheMax int `db:"historyCacheMax"` Fields []ResField `db:"fields"`
Fields ResFields `db:"fields"` Roles map[string]any `db:"roles"`
Roles map[string]any `db:"roles"` UpdatedAt string `db:"updated_at"`
UpdatedAt string `db:"updated_at"` CreatedAt string `db:"created_at"`
CreatedAt string `db:"created_at"`
} }
func (this *Resource) InitContainer(container *do.Injector) { func (this *Resource) InitContainer(container do.Injector) {
this.container = container this.container = container
this.initOnce = &sync.Once{}
} }
// 资源UUID // GetUuid 资源UUID
func (this Resource) GetUuid() string { func (this Resource) GetUuid() string {
return this.Uuid return this.Uuid
} }
// 资源CODE // GetCode 资源CODE
func (this Resource) GetCode() string { func (this Resource) GetCode() string {
return this.Code return this.Code
} }
// 资源名 // GetName 资源名
func (this Resource) GetName() string { func (this Resource) GetName() string {
return this.Name return this.Name
} }
// 主键 // GetConn 数据库连接
func (this Resource) GetConn() string {
return this.Conn
}
// GetPrimarykey 主键
func (this Resource) GetPrimarykey() string { func (this Resource) GetPrimarykey() string {
return this.Primarykey return this.Primarykey
} }
// 是否虚拟资源 // IsVirtual 是否虚拟资源
func (this Resource) IsVirtual() bool { func (this Resource) IsVirtual() bool {
return this.IsResVirtual return this.IsResVirtual
} }
// 是否系统资源 // IsSystem 是否系统资源
func (this Resource) IsSystem() bool { func (this Resource) IsSystem() bool {
return this.IsResSystem return this.Conn == "service-support"
} }
// 资源字段 // GetFields 资源字段
func (this Resource) GetFields() (result []req.ResField) { func (this Resource) GetFields() (result []req.ResField) {
for _, item := range this.Fields { for _, item := range this.Fields {
result = append(result, item) result = append(result, item)
@@ -99,137 +115,145 @@ func (this Resource) GetFields() (result []req.ResField) {
return result return result
} }
// 资源字段 // GetField 资源字段
func (this Resource) GetField(code string) (req.ResField, bool) { func (this Resource) GetField(code string) (req.ResField, bool) {
return lo.Find(this.GetFields(), func(v req.ResField) bool { return lo.Find(this.GetFields(), func(v req.ResField) bool {
return v.GetCode() == code return v.GetCode() == code
}) })
} }
// 判断资源字段 // HasField 判断资源字段
func (this Resource) HasField(code string) bool { func (this Resource) HasField(code string) bool {
return lo.SomeBy(this.GetFields(), func(v req.ResField) bool { return lo.SomeBy(this.GetFields(), func(v req.ResField) bool {
return v.GetCode() == code return v.GetCode() == code
}) })
} }
// 开启事物 // BeginTransaction 开启事物
func (this Resource) BeginTransaction() (*db.Transaction, error) { func (this Resource) BeginTransaction() (*db.TxDatabase, error) {
return this.GetDBConn().BeginTransaction() return this.DB().Begin()
} }
// 获取资源链接 // DB 获取资源链接
func (this Resource) GetDBConn() *db.Connection { func (this Resource) DB() *db.Database {
db := do.MustInvoke[db.DB](this.container) dbEngine := do.MustInvoke[engine.Engine](this.container)
conn := dbEngine.Connection(this.Conn)
if this.IsSystem() { if isLocalDB(conn.Dialect()) && !this.IsResVirtual && this.Table != "" && this.initOnce != nil {
return db.Connection("service-support") this.initOnce.Do(func() {
} if err := this.autoCreateTable(conn); err != nil {
panic(err)
return db.Connection("default")
}
// 获取资源对应的数据库连接
func (this Resource) GetDBBuilder() *db.Builder {
return this.GetDBConn().Query()
}
// 获取资源对应的数据表
func (this Resource) GetTable() db.Expression {
if this.IsVirtual() {
return db.Raw("(" + this.Table + ")")
}
return db.Raw(this.Table)
}
func (this Resource) GetDBDriver() string {
return this.GetDBConn().GetConfig().Driver
}
func (this Resource) GetAuthDBTable(u req.User, params ...any) *db.Builder {
builder := this.GetDBTable(append(params, u)...)
// 数据权限过滤
builder.Before(func(b *db.Builder, t string, data ...map[string]any) {
if t == db.TYPE_SELECT || t == db.TYPE_UPDATE || t == db.TYPE_DELETE {
this.WithRolesCondition(b, t, u)
}
})
return builder
}
// GetDBTable("Test", contracts.User)
func (this Resource) GetDBTable(params ...any) *db.Builder {
builder := this.GetDBBuilder()
var user req.User
alias := this.Code
for _, param := range params {
switch v := param.(type) {
case *db.Transaction:
builder.Tx = v
case string:
alias = v
case req.User:
user = v
}
}
// 格式化数据库存储数据
builder.Before(func(b *db.Builder, t string, data ...map[string]any) {
if t == db.TYPE_UPDATE {
// 格式化保存数据
this.formatSaveValue(data[0])
}
if t == db.TYPE_INSERT {
// 移除 table alias
b.Table(string(this.GetTable()))
for i := 0; i < len(data); i++ {
// 格式化保存数据
this.formatSaveValue(data[i])
// 填充保存数据
this.fillSaveValue(data[i], user, db.TYPE_INSERT)
} }
} })
})
// 资源事件
this.onResEvent(builder)
// 用户事件
if this.IsHistoryRecord {
this.onUserEvent(builder, user)
} }
// 虚拟资源暂时不考虑鉴权 // vtable 资源:查询走 engine 中的 :memory: 连接("__"+Conn),
if !this.IsVirtual() { // 原文件连接仅用于 _vtab_cache 持久化,两者互不阻塞。
// 返回鉴权后的 DB Builder if conn.Dialect() == "vtable" && this.Table != "" && this.initOnce != nil {
// return memConn := dbEngine.Connection("__" + this.Conn)
this.initOnce.Do(func() {
if err := this.autoSetupVtab(memConn); err != nil {
panic(err)
}
})
return memConn
} }
return builder.Table(string(this.GetTable()), alias) return conn
} }
func (this Resource) WithRolesCondition(b *db.Builder, t string, u req.User) error { // autoSetupVtab 通过容器获取 ResVirtualTableSetup(由 framework-v2 在 sqlite_vtable tag 下注册),
// 再从 App 取出当前资源对应的 ResVirtualTable 实现,完成 vtab 注册和 CREATE VIRTUAL TABLE。
func (this Resource) autoSetupVtab(conn *db.Database) error {
_, err := conn.Exec(fmt.Sprintf(`CREATE VIRTUAL TABLE IF NOT EXISTS %s USING %s_mod()`, this.Table, this.Table))
return err
}
// autoCreateTable 自动创建数据表
func (this Resource) autoCreateTable(conn *db.Database) error {
sb := schema.New(conn)
return sb.Create(this.Table, func(bp *schema.Blueprint) {
bp.Comment = this.Name
for _, field := range this.Fields {
field.ToBlueprint(bp)
}
})
}
// GetTable 获取资源对应的数据表
func (this Resource) GetTable() exp.LiteralExpression {
if this.IsResVirtual {
return db.L("(" + this.Table + ")")
}
return db.V(db.T(this.Table))
}
// GetDBTable 获取资源对应的数据库连接
func (this Resource) GetDBTable(u req.User, opts ...req.ResOption) (sd *db.SelectDataset) {
if u == nil {
panic("GetDBTable: user cannot be nil")
}
op := &req.ResOptions{}
for _, cb := range opts {
cb(op)
}
alias := this.GetCode()
if op.Alias != "" {
alias = op.Alias
}
sd = this.DB().From(this.GetTable().As(alias))
if op.Tx != nil {
sd = op.Tx.From(this.GetTable().As(alias))
}
sd.WithHook(&ResourceHooks{res: this, u: u, tx: op.Tx})
return sd
}
func (this Resource) GetRolesCondition(u req.User) (*db.SelectDataset, db.Expression) {
isFullRight := false isFullRight := false
isFullNot := false isFullNot := false
if u == nil {
u = GetAnonymous()
}
// 如果是系统用户, 则不做校验
if u.Uuid() == "ffffffff-ffff-ffff-ffff-ffffffffffff" {
return nil, nil
}
// 这里不能放在 u.Uuid() == "ffffffff-ffff-ffff-ffff-ffffffffffff" 前面
// 会导致初始化数据时循环依赖
GetResRoles, err := do.Invoke[GetResRoles](this.container)
if err != nil {
return nil, nil
}
items := GetResRoles(this.GetUuid(), u.Roles()...)
if len(items) == 0 {
return nil, nil
}
GetResource := do.MustInvoke[contracts.GetResource](this.container)
NewOrm := do.MustInvoke[contracts.NewOrm](this.container) NewOrm := do.MustInvoke[contracts.NewOrm](this.container)
NewOrmModel := do.MustInvoke[contracts.NewOrmModel](this.container) NewOrmModel := do.MustInvoke[contracts.NewOrmModel](this.container)
NewOrmJoin := do.MustInvoke[contracts.NewOrmJoin](this.container) NewOrmJoin := do.MustInvoke[contracts.NewOrmJoin](this.container)
GetResRelationResource := do.MustInvoke[GetResRelationResource](this.container) GetResRelationResource := do.MustInvoke[GetResRelationResource](this.container)
GetResRelations := do.MustInvoke[GetResRelations](this.container) GetResRelations := do.MustInvoke[GetResRelations](this.container)
GetResource := do.MustInvoke[contracts.GetResource](this.container)
GetOrmConditions := do.MustInvoke[contracts.GetOrmConditions](this.container) GetOrmConditions := do.MustInvoke[contracts.GetOrmConditions](this.container)
items := do.MustInvoke[GetResRoles](this.container)(this.GetUuid(), u.Roles()...) sub := lo.Reduce(items, func(carry *db.SelectDataset, item ResRole, _ int) *db.SelectDataset {
sd := this.DB().From(this.GetTable().As(this.GetCode())).Select(db.T(this.GetCode()).All())
subTables := lo.Reduce(items, func(carry string, item ResRole, _ int) string {
db := this.GetDBBuilder().Table(string(this.GetTable()), this.GetCode()).Select(db.Raw("distinct `" + this.GetCode() + "`.*"))
joins := lo.Filter(GetResRelations(item.Uuid), func(item ResRelation, _ int) bool { joins := lo.Filter(GetResRelations(item.Uuid), func(item ResRelation, _ int) bool {
return item.Type == "inner" || item.Type == "left" || item.Type == "right" return item.Type == "inner" || item.Type == "left" || item.Type == "right"
@@ -249,106 +273,52 @@ func (this Resource) WithRolesCondition(b *db.Builder, t string, u req.User) err
// 关联扩展条件 // 关联扩展条件
join.SetCondition(GetOrmConditions(joins[i].Uuid, condition.Describe("关联扩展条件"))) join.SetCondition(GetOrmConditions(joins[i].Uuid, condition.Describe("关联扩展条件")))
join.Inject(db, NewOrmModel(rResource, rResource.GetCode(), rResource.GetName())) join.Inject(sd, NewOrmModel(rResource, rResource.GetCode(), rResource.GetName()))
} }
conditions := GetOrmConditions(item.Uuid, condition.Describe("关联扩展条件")) conditions := GetOrmConditions(item.Uuid, condition.Describe("关联扩展条件"))
if len(joins) == 0 && conditions.IsEmpty() { if len(joins) == 0 && conditions.IsEmpty() {
// 无权限, 直接跳过这个 unoin 语句 // 无权限, 直接跳过这个 unoin 语句
if carry != "" { if carry != nil {
return carry return carry
} }
// 第一个无权限除外, 避免所有用户所属角色都是无权限 // 第一个无权限除外, 避免所有用户所属角色都是无权限
db.WhereRaw("false") sd = sd.Where(db.V(false))
isFullNot = true isFullNot = true
} else if len(joins) == 0 && conditions.IsNotEmpty() && conditions.IsAlwaysRight() /* 1=1 的这种条件*/ { } else if len(joins) == 0 && conditions.IsNotEmpty() && conditions.IsAlwaysRight() /* 1=1 的这种条件*/ {
// 只要有1个满权限, 直接返回单条语句 // 只要有1个满权限, 直接返回单条语句
isFullRight = true isFullRight = true
return db.ToSql() return sd
} else if conditions.IsNotEmpty() { } else if conditions.IsNotEmpty() {
oOrm := NewOrm(this, nil) oOrm := NewOrm(this, nil)
oOrm.SetGlobalParams(req.NewGlobalParam("{}", u)) oOrm.SetGlobalParams(req.NewGlobalParams("{}", u))
db.Where(conditions.ToSql(oOrm.GetModel())) sd = sd.Where(conditions.ToSql(oOrm.GetModel()))
// 如果前面是无权限的sql查看, 这直接返回本次查询 // 如果前面是无权限的sql查看, 这直接返回本次查询
if isFullNot { if isFullNot {
isFullNot = false isFullNot = false
return db.ToSql() return sd
} }
} }
if carry != "" { if carry != nil {
carry += " UNION " return carry.Union(sd)
} }
return fmt.Sprintf("%s(%s)", carry, db.ToSql()) return sd
}, "") }, nil)
if isFullRight { if isFullRight {
return nil return nil, nil
} }
if isFullNot { if isFullNot {
b.WhereRaw("false") return nil, db.V(false)
} else if subTables != "" {
if t == db.TYPE_SELECT {
b.FromSub(subTables, b.TableAlias)
} else {
b.WhereRaw(fmt.Sprintf(
"`%s`.`id` in (SELECT `temp`.`id` FROM (%s) as `temp`)",
lo.Ternary(b.TableAlias != "", b.TableAlias, this.GetCode()),
subTables,
))
}
} }
return nil return sub, nil
}
// 格式化保存数据
func (this Resource) formatSaveValue(data map[string]any) {
//
for k, v := range data {
if k == "id" || k == "created_user" || k == "created_at" || k == "deleted_at" || k == "updated_at" {
delete(data, k)
} else if val, ok := v.(db.Expression); ok {
data[k] = val
} else if field, ok := this.GetField(k); ok {
data[k] = field.ToValue(v)
}
}
}
// 填充保存数据
func (this Resource) fillSaveValue(data map[string]any, u req.User, t string) {
for _, field := range this.GetFields() {
fCode := field.GetCode()
if fCode == "id" || fCode == "created_user" || fCode == "created_at" || fCode == "deleted_at" || fCode == "updated_at" || fCode == "owned_user" {
continue
}
// 只有新增默认字段
if _, ok := data[fCode]; !ok {
data[fCode] = field.GetRawDefault(this.GetDBDriver())
}
}
// 拥有者
if _, ok := data["owned_user"]; !ok {
data["owned_user"] = u.Uuid()
}
// 创建者
data["created_user"] = u.Uuid()
if this.GetDBDriver() == "sqlite" {
// 更新时间
// sqlite 不能自动更新时间, "DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
data["updated_at"] = db.Raw("CURRENT_TIMESTAMP")
} else {
}
} }
func (this Resource) GetStruct(extends ...reflect.StructField) any { func (this Resource) GetStruct(extends ...reflect.StructField) any {
@@ -379,67 +349,66 @@ func (this Resource) GetSliceStruct(extends ...reflect.StructField) any {
return reflect.New(st).Interface() return reflect.New(st).Interface()
} }
// 资源事件 // onResEvent 资源事件
func (this Resource) onResEvent(builder *db.Builder) { func (this Resource) onResEvent(t string, result sql.Result) {
builder.After(func(b *db.Builder, t string, result sql.Result, err error, data ...map[string]any) { if num, err := result.RowsAffected(); num == 0 || err != nil {
if err != nil || t == db.TYPE_SELECT { return
return }
} else if num, err := result.RowsAffected(); num == 0 || err != nil {
return
}
// 有些地方可能没有注册 contracts.Queue
if qu, err := do.Invoke[contracts.Queue](this.container); err == nil {
// 全局触发器 // 全局触发器
// 1. 清除系统缓存 // 1. 清除系统缓存
if err := do.MustInvoke[contracts.Queue](this.container).Publish(ResChangeEventTopic, ResChangeEventTopicPayload{ if err := qu.Publish(ResChangeEventTopic, ResChangeEventTopicPayload{
Type: t, Type: t,
Res: this, Res: this,
Result: result, Result: result,
}); err != nil { }); err != nil {
log.Println("Queue Publish Err:", ResChangeEventTopic, err) log.Println("Queue Publish Err:", ResChangeEventTopic, err)
} }
}) }
} }
// 用户事件 // onUserEvent 用户事件
func (this Resource) onUserEvent(builder *db.Builder, user req.User) { func (this Resource) onUserEvent(builder exec.QueryExecutor, user req.User) {
old := []map[string]any{} // old := []map[string]any{}
builder.Before(func(b *db.Builder, t string, data ...map[string]any) { // builder.Before(func(b *db.Builder, t string, data ...map[string]any) {
if t != db.TYPE_UPDATE && t != db.TYPE_DELETE { // if t != db.TYPE_UPDATE && t != db.TYPE_DELETE {
return // return
} // }
// 查询保存之前的数据 // // 查询保存之前的数据
if _, err := b.Get(&old); err != nil { // if _, err := b.Get(&old); err != nil {
panic(err) // panic(err)
} // }
}) // })
builder.After(func(b *db.Builder, t string, result sql.Result, err error, data ...map[string]any) { // builder.After(func(b *db.Builder, t string, result sql.Result, err error, data ...map[string]any) {
if err != nil || t == db.TYPE_SELECT { // if err != nil || t == db.TYPE_SELECT {
return // return
} else if num, err := result.RowsAffected(); num == 0 || err != nil { // } else if num, err := result.RowsAffected(); num == 0 || err != nil {
return // return
} // }
if user == nil { // if user == nil {
user = GetAnonymous() // user = GetAnonymous()
} // }
// 触发消息队列 // // 触发消息队列
if err := do.MustInvoke[contracts.Queue](this.container).Publish(ResChangeRecordTopic, ResChangeRecordTopicPayload{ // if err := do.MustInvoke[contracts.Queue](this.container).Publish(ResChangeRecordTopic, ResChangeRecordTopicPayload{
Type: t, // Type: t,
User: user, // User: user,
Res: this, // Res: this,
Old: old, // Old: old,
New: data, // New: data,
Result: result, // Result: result,
}); err != nil { // }); err != nil {
log.Println("Queue Publish Err:", ResChangeRecordTopic, err) // log.Println("Queue Publish Err:", ResChangeRecordTopic, err)
} // }
}) // })
} }
func NewVirtualResource(pRes req.Resource, code, name, sql string, fields []ResField) req.Resource { func NewVirtualResource(pRes req.Resource, code, name string, sd *db.SelectDataset, fields []ResField) req.Resource {
fieldsCopy := make([]ResField, len(fields)) fieldsCopy := make([]ResField, len(fields))
copy(fieldsCopy, fields) copy(fieldsCopy, fields)
@@ -448,17 +417,21 @@ func NewVirtualResource(pRes req.Resource, code, name, sql string, fields []ResF
fieldsCopy[i].CodeResource = code fieldsCopy[i].CodeResource = code
} }
sql, _, err := sd.WithDialect(pRes.DB().Dialect()).ToSQL()
if err != nil {
panic(err)
}
return &Resource{ return &Resource{
Uuid: code, Uuid: code,
PUuid: pRes.GetUuid(), PUuid: pRes.GetUuid(),
Code: code, Code: code,
Name: name, Name: name,
IsResVirtual: true, IsResVirtual: true,
IsResSystem: pRes.IsSystem(), Conn: pRes.GetConn(),
Table: sql, Table: sql,
IsHistoryRecord: false, Fields: fieldsCopy,
HistoryCacheMax: 0, Roles: nil,
Fields: fieldsCopy,
Roles: nil,
} }
} }
+426
View File
@@ -0,0 +1,426 @@
package base
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"reflect"
"strconv"
"strings"
"sync"
"git.fsdpf.net/go/db"
"git.fsdpf.net/go/db/exp"
"git.fsdpf.net/go/db/schema"
"git.fsdpf.net/go/req"
"github.com/samber/lo"
)
var changeLogOnce sync.Map // key: Conn,每个数据库连接只建一次 _change_log 表
// isLocalDB 判断是否为本地文件型数据库(LastInsertId 返回最后一条而非第一条)
func isLocalDB(dialect string) bool {
return dialect == "sqlite3" || dialect == "duckdb"
}
type ResourceHooks struct {
res Resource
u req.User
tx *db.TxDatabase
snapshots []map[string]any // UPDATE/DELETE 前预查的行数据
}
func (rh *ResourceHooks) isTracked() bool {
if len(rh.res.HistoryRoles) == 0 || rh.res.IsVirtual() {
return false
}
if lo.Contains(rh.res.HistoryRoles, "00000000-0000-0000-0000-000000000000") {
return true
}
return lo.Some(rh.res.HistoryRoles, rh.u.Roles())
}
// autoCreateChangeLog 在当前连接上建 _change_log 表,每个连接只执行一次
func (rh *ResourceHooks) autoCreateChangeLog() error {
conn := rh.res.DB()
v, _ := changeLogOnce.LoadOrStore(rh.res.Conn, &sync.Once{})
var err error
v.(*sync.Once).Do(func() {
sb := schema.New(conn)
err = sb.Create("_change_log", func(bp *schema.Blueprint) {
bp.Comment = "资源数据变更记录"
bp.BigIncrements("id").AutoIncrement().Comment("ID")
bp.Boolean("enabled").Default("1").Comment("是否有效")
bp.Char("created_user", 36).Default("00000000-0000-0000-0000-000000000000").Comment("操作用户")
bp.Char("owned_user", 36).Default("00000000-0000-0000-0000-000000000000").Comment("拥有者")
bp.Timestamp("created_at").UseCurrent().Comment("操作时间")
bp.Timestamp("updated_at").UseCurrent().Default(db.L("ON UPDATE CURRENT_TIMESTAMP")).Comment("更新时间")
bp.DateTime("deleted_at").Nullable().Comment("删除时间")
bp.Char("resource_uuid", 36).Default("").Comment("资源UUID")
bp.String("category", 10).Default("").Comment("操作类型 INSERT/UPDATE/DELETE")
bp.Char("trace_id", 36).Default("").Comment("请求追踪ID")
bp.Integer("row_id").Default("0").Comment("受影响行ID")
bp.Json("snapshot").Nullable().Comment("操作前数据快照")
})
})
return err
}
// captureSnapshot 在写操作执行前,查出受影响的行存入 snapshots
func (rh *ResourceHooks) captureSnapshot(where exp.ExpressionList) {
sd := rh.res.DB().From(rh.res.GetTable())
if where != nil && len(where.Expressions()) > 0 {
sd = sd.Where(where.Expressions()...)
}
rows, err := sd.Executor().GetRecords()
if err != nil {
log.Printf("_change_log captureSnapshot err: %v", err)
return
}
rh.snapshots = rows
}
// writeChangeLogs 将变更写入 _change_log
func (rh *ResourceHooks) writeChangeLogs(category string, result sql.Result) {
if err := rh.autoCreateChangeLog(); err != nil {
log.Printf("_change_log init err: %v", err)
return
}
traceId := rh.u.Runtime().TraceId()
opUser := rh.u.Uuid()
var rows []any
switch category {
case "INSERT":
lastId, _ := result.LastInsertId()
count, _ := result.RowsAffected()
for i := int64(0); i < count; i++ {
rowId := lastId + i // MySQL: lastId 是第一条
if isLocalDB(rh.res.DB().Dialect()) {
rowId = lastId - count + 1 + i // SQLite: lastId 是最后一条
}
rows = append(rows, map[string]any{
"enabled": true,
"created_user": opUser,
"owned_user": opUser,
"resource_uuid": rh.res.Uuid,
"category": category,
"trace_id": traceId,
"row_id": rowId,
"snapshot": nil,
})
}
case "UPDATE", "DELETE":
for _, snap := range rh.snapshots {
rowId := int64(0)
if id, ok := snap["id"]; ok {
rowId = toChangeLogInt64(id)
}
snapJSON, _ := json.Marshal(snap)
rows = append(rows, map[string]any{
"enabled": true,
"created_user": opUser,
"owned_user": opUser,
"resource_uuid": rh.res.Uuid,
"category": category,
"trace_id": traceId,
"row_id": rowId,
"snapshot": string(snapJSON),
})
}
}
if len(rows) == 0 {
return
}
inserter := rh.res.DB().Insert("_change_log")
if rh.tx != nil {
inserter = rh.tx.Insert("_change_log")
}
if _, err := inserter.Rows(rows...).Executor().Exec(); err != nil {
log.Printf("_change_log write err: %v", err)
}
}
func toChangeLogInt64(v any) int64 {
switch val := v.(type) {
case int64:
return val
case int:
return int64(val)
case int32:
return int64(val)
case float64:
return int64(val)
case string:
n, _ := strconv.ParseInt(val, 10, 64)
return n
}
return 0
}
func (rh *ResourceHooks) Before(dataset interface{}) error {
switch d := dataset.(type) {
case *db.SelectDataset:
return rh.beforeSelectDataset(d)
case *db.InsertDataset:
return rh.beforeInsertDataset(d)
case *db.UpdateDataset:
return rh.beforeUpdateDataset(d)
case *db.DeleteDataset:
return rh.beforeDeleteDataset(d)
}
return nil
}
func (rh *ResourceHooks) After(dataset interface{}, result interface{}) error {
if rh.res.IsVirtual() {
// 返回鉴权后的 DB Builder
return nil
}
// 用户事件
// if rh.res.IsHistoryRecord {
// rh.res.onUserEvent(builder, user)
// }
switch dataset.(type) {
case *db.SelectDataset:
case *db.InsertDataset:
r := result.(sql.Result)
rh.res.onResEvent("INSERT", r)
if rh.isTracked() {
rh.writeChangeLogs("INSERT", r)
}
case *db.UpdateDataset:
r := result.(sql.Result)
rh.res.onResEvent("UPDATE", r)
if rh.isTracked() {
rh.writeChangeLogs("UPDATE", r)
}
case *db.DeleteDataset:
r := result.(sql.Result)
rh.res.onResEvent("DELETE", r)
if rh.isTracked() {
rh.writeChangeLogs("DELETE", r)
}
}
return nil
}
func (rh *ResourceHooks) beforeInsertDataset(id *db.InsertDataset) error {
switch true {
case id.GetClauses().HasRows():
return rh.beforeInsertRows(id)
case id.GetClauses().HasVals():
return rh.beforeInsertColsVals(id)
case id.GetClauses().HasFrom():
return rh.beforeInsertFromQuery(id)
}
return nil
}
func (rh *ResourceHooks) beforeInsertRows(id *db.InsertDataset) error {
rows := id.GetClauses().Rows()
for i := range rows {
rowValue := reflect.ValueOf(rows[i])
if rowValue.Kind() == reflect.Ptr {
rowValue = rowValue.Elem()
}
if rowValue.Kind() == reflect.Struct {
if row, err := exp.NewRecordFromStruct(rowValue.Interface(), true, false); err != nil {
return err
} else {
rows[i] = row
}
}
}
for i := range rows {
row := rows[i].(map[string]any)
// 格式化保存数据
if err := rh.normalizeSaveValue(row); err != nil {
return err
}
// 填充默认数据
if err := rh.applyDefaultValue(row, true, false); err != nil {
return err
}
}
return nil
}
func (rh *ResourceHooks) beforeInsertColsVals(id *db.InsertDataset) error {
cols := id.GetClauses().Cols()
vals := id.GetClauses().Vals()
colsName := []string{}
for _, col := range cols.Columns() {
colsName = append(colsName, col.(exp.IdentifierExpression).GetCol().(string))
}
colsVal := []any{}
for k, v := range lo.OmitByKeys(rh.getFieldsDefaultValue(true, false), colsName) {
cols = cols.Append(db.C(k))
colsVal = append(colsVal, v)
}
for i := 0; i < len(vals); i++ {
for j := 0; j < len(vals[i]); j++ {
if filed, ok := rh.res.GetField(colsName[j]); ok {
vals[i][j] = filed.ToValue(vals[i][j])
}
}
vals[i] = append(vals[i], colsVal...)
}
*id = *id.Cols(cols)
return nil
}
func (rh *ResourceHooks) beforeInsertFromQuery(id *db.InsertDataset) error {
return nil
}
// 规范化保存数据
// 1. 移除系统缺省字段, 如果是 insert 需要调用 applyDefaultValue
// 2. 移除系统中没有的字段
func (rh *ResourceHooks) normalizeSaveValue(row db.Record) error {
for k, v := range row {
if k == "id" || k == "created_user" || k == "created_at" || k == "deleted_at" || k == "updated_at" {
delete(row, k)
} else if val, ok := v.(db.Expression); ok {
row[k] = val
} else if field, ok := rh.res.GetField(k); ok {
row[k] = field.ToValue(v)
} else {
delete(row, k)
}
}
return nil
}
// 填充默认数据
func (rh *ResourceHooks) applyDefaultValue(row db.Record, forInsert, forUpdate bool) error {
// 填充默认数据
for k, v := range rh.getFieldsDefaultValue(forInsert, forUpdate) {
if _, ok := row[k]; !ok {
row[k] = v
}
}
return nil
}
func (rh *ResourceHooks) getFieldsDefaultValue(forInsert, forUpdate bool) map[string]db.Expression {
defs := map[string]db.Expression{}
if forInsert {
for _, item := range rh.res.Fields {
if item.GetCode() == "updated_at" || item.GetCode() == "created_user" || item.GetCode() == "owned_user" {
continue
}
if len(item.Default) > 4 && strings.ToLower(item.Default[0:4]) == "sql:" {
defs[item.GetCode()] = item.GetRawDefault()
} else if item.DataType == req.ResJson {
defs[item.GetCode()] = item.GetRawDefault()
}
}
if _, ok := defs["owned_user"]; !ok {
defs["owned_user"] = db.V(rh.u.Uuid())
}
defs["created_user"] = db.V(rh.u.Uuid())
}
if forUpdate && isLocalDB(rh.res.DB().Dialect()) {
defs["updated_at"] = db.L("CURRENT_TIMESTAMP")
}
return defs
}
func (rh *ResourceHooks) beforeSelectDataset(sd *db.SelectDataset) error {
sub, ex := rh.res.GetRolesCondition(rh.u)
if ex != nil {
*sd = *sd.Where(ex)
} else if sub != nil {
table := sd.GetClauses().From().Columns()[0]
if alias, ok := table.(exp.AliasedExpression); ok {
*sd = *sd.From(db.V(sub.Expression()).As(alias.GetAs()))
} else {
*sd = *sd.From(db.V(sub.Expression()).As(table))
}
}
return nil
}
func (rh *ResourceHooks) beforeUpdateDataset(ud *db.UpdateDataset) error {
// 虚拟资源暂时不考虑鉴权
if rh.res.IsVirtual() {
return nil
}
sub, ex := rh.res.GetRolesCondition(rh.u)
if ex != nil {
*ud = *ud.Where(ex)
} else if sub != nil {
table := ud.GetClauses().Table()
if alias, ok := table.(exp.AliasedExpression); ok {
*ud = *ud.Where(alias.GetAs().Col("id").In(sub.Select(db.T(rh.res.GetCode()).Col("id"))))
} else {
*ud = *ud.Where(db.L("?.id", table).In(sub.Select(db.T(rh.res.GetCode()).Col("id"))))
}
}
// 记录变更前快照(role 条件已应用,WHERE 与实际 UPDATE 一致)
if rh.isTracked() {
rh.captureSnapshot(ud.GetClauses().Where())
}
// 格式化保存数据
if ud.GetClauses().HasSetValues() {
udv := ud.GetClauses().SetValues()
switch data := udv.(type) {
case map[string]any:
return rh.normalizeSaveValue(data)
case db.Record:
return rh.normalizeSaveValue(data)
default:
return fmt.Errorf("unsupported type: %T", udv)
}
}
return nil
}
func (rh *ResourceHooks) beforeDeleteDataset(dd *db.DeleteDataset) error {
// 虚拟资源暂时不考虑鉴权
if rh.res.IsVirtual() {
return nil
}
sub, ex := rh.res.GetRolesCondition(rh.u)
if ex != nil {
*dd = *dd.Where(ex)
} else if sub != nil {
table := dd.GetClauses().From().Columns()[0]
if alias, ok := table.(exp.AliasedExpression); ok {
*dd = *dd.Where(alias.GetAs().Col("id").In(sub.Select(db.T(rh.res.GetCode()).Col("id"))))
} else {
*dd = *dd.Where(db.L("?.id", table).In(sub.Select(db.T(rh.res.GetCode()).Col("id"))))
}
}
// 记录变更前快照(role 条件已应用,WHERE 与实际 DELETE 一致)
if rh.isTracked() {
rh.captureSnapshot(dd.GetClauses().Where())
}
return nil
}
+100
View File
@@ -0,0 +1,100 @@
package base_test
import (
"os"
"testing"
"git.fsdpf.net/go/contracts/base"
"git.fsdpf.net/go/db"
"git.fsdpf.net/go/db/engine"
"git.fsdpf.net/go/req"
"github.com/samber/do/v2"
"github.com/stretchr/testify/suite"
_ "git.fsdpf.net/go/db/schema/dialect/mysql"
_ "git.fsdpf.net/go/db/schema/dialect/sqlite3"
)
type resourceTest struct {
suite.Suite
res *base.Resource
}
func TestResource(t *testing.T) {
suite.Run(t, new(resourceTest))
}
func (t *resourceTest) SetupSuite() {
app := do.New()
do.Provide(app, func(container do.Injector) (engine.Engine, error) {
return engine.Open(map[string]engine.DBConfig{
"default": engine.NewDBConfig("mysql",
engine.WithHost(os.Getenv("MYSQL_HOST")),
engine.WithPort(os.Getenv("MYSQL_PORT")),
engine.WithDatabase(os.Getenv("MYSQL_DB")),
engine.WithUsername(os.Getenv("MYSQL_USER")),
engine.WithPassword(os.Getenv("MYSQL_PASSWD")),
),
}), nil
})
fields := []base.ResField{
{Name: "字段", Code: "fields", DataType: req.ResJson, Default: "{}"},
}
res := &base.Resource{
Uuid: "4bf3a311-cbe1-4236-bdda-c64ab04ae9b1",
Code: "User",
Name: "用户",
Table: "users",
Primarykey: "id",
Fields: fields,
}
res.InitContainer(app)
t.res = res
}
func (t *resourceTest) TestInsertRows() {
type Temp struct {
Name string `db:"name"`
Fields map[string]any `db:"fields"`
}
result := "INSERT INTO `users` AS `User` (`created_user`, `fields`, `name`, `owned_user`) " +
"VALUES ('00000000-0000-0000-0000-000000000000', '{\\\"aa\\\":1}', '张三', '00000000-0000-0000-0000-000000000000')"
sql, _, _ := t.res.GetDBTable(base.GetAnonymous()).Insert().Rows(
db.Record{"name": "张三", "id": 1, "fields": map[string]any{"aa": 1}},
).Executor().ToSQL()
t.Equal(result, sql)
sql, _, _ = t.res.GetDBTable(base.GetAnonymous()).Insert().Rows(
&Temp{Name: "张三", Fields: map[string]any{"aa": 1}},
).Executor().ToSQL()
t.Equal(result, sql)
sql, _, _ = t.res.GetDBTable(base.GetAnonymous()).Insert().Rows(
Temp{Name: "张三", Fields: map[string]any{"aa": 1}},
).Executor().ToSQL()
t.Equal(result, sql)
}
func (t *resourceTest) TestInsertColsVals() {
result := "INSERT INTO `users` AS `User` (`name`, `fields`, `created_user`, `owned_user`) VALUES " +
"('李四', '{\\\"aa\\\":1}', '00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000000'), " +
"('王五', '{\\\"aa\\\":3}', '00000000-0000-0000-0000-000000000000', '00000000-0000-0000-0000-000000000000')"
sql, _, _ := t.res.GetDBTable(base.GetAnonymous()).Insert().Cols("name", "fields").Vals(
db.Vals{"李四", map[string]any{"aa": 1}},
db.Vals{"王五", map[string]any{"aa": 3}},
).Executor().ToSQL()
_ = result
_ = sql
// t.Equal(result, sql)
}
func (*resourceTest) TestInsertColsFromQuery() {}
+1
View File
@@ -15,6 +15,7 @@ type Route struct {
Exact bool `db:"exact"` Exact bool `db:"exact"`
Platform string `db:"platform"` Platform string `db:"platform"`
Extra map[string]any `db:"extra"` Extra map[string]any `db:"extra"`
Roles []string `db:"roles"`
UpdatedAt string `db:"updated_at"` UpdatedAt string `db:"updated_at"`
CreatedAt string `db:"created_at"` CreatedAt string `db:"created_at"`
} }
+2 -2
View File
@@ -51,7 +51,7 @@ func (this user) Runtime() req.UserRuntime {
return this.runtime return this.runtime
} }
// 获取匿名用户 // GetAnonymous 获取匿名用户
func GetAnonymous(opts ...req.UserRuntimeOption) req.User { func GetAnonymous(opts ...req.UserRuntimeOption) req.User {
u := user{ u := user{
id: 0, id: 0,
@@ -69,7 +69,7 @@ func GetAnonymous(opts ...req.UserRuntimeOption) req.User {
return u return u
} }
// 系统用户 // GetSystemUser 系统用户
func GetSystemUser(opts ...req.UserRuntimeOption) req.User { func GetSystemUser(opts ...req.UserRuntimeOption) req.User {
u := user{ u := user{
id: -1, id: -1,
+23
View File
@@ -0,0 +1,23 @@
package base
import (
"git.fsdpf.net/go/req"
)
type UserAsset struct {
ID int64 `db:"id"`
Name string `db:"name"`
FileName string `db:"filename"`
File string `db:"file"`
Mime string `db:"mime"`
Size int64 `db:"size"`
Ext string `db:"ext"`
Hit int64 `db:"hit"`
IsPrivate bool `db:"isPrivate"`
UpdatedAt string `db:"updated_at"`
CreatedAt string `db:"created_at"`
}
type GetUserAsset func(user req.User, code string) (UserAsset, bool)
type UploadUserAsset func(user req.User, file string, name string) (UserAsset, error)
+32
View File
@@ -0,0 +1,32 @@
package contracts
import (
"git.fsdpf.net/go/reflux"
"git.fsdpf.net/go/req"
"github.com/samber/do/v2"
)
type CondflowService interface {
Run(name string, input reflux.R, u req.User) error
}
type FlowDecision interface {
Controller
Res() req.Resource
}
type BaseFlowDecision struct {
Controller
res string
}
func (be BaseFlowDecision) Res() req.Resource {
return do.MustInvoke[MustResource](be.Container())(be.res)
}
func NewBaseFlowDecision(container do.Injector, res string) *BaseFlowDecision {
return &BaseFlowDecision{
Controller: &BaseController{container},
res: res,
}
}
+5 -5
View File
@@ -2,23 +2,23 @@ package contracts
import ( import (
"git.fsdpf.net/go/req" "git.fsdpf.net/go/req"
"github.com/samber/do" "github.com/samber/do/v2"
) )
type Controller interface { type Controller interface {
// 初始化 // 初始化
Init() error Init() error
// 获取 Container // 获取 Container
Container() *do.Injector Container() do.Injector
// 派遣一个任务 // 派遣一个任务
Dispatch(job string, payload any, u req.User) error Dispatch(job string, payload any, u req.User) error
} }
type BaseController struct { type BaseController struct {
container *do.Injector container do.Injector
} }
func (this BaseController) Container() *do.Injector { func (this BaseController) Container() do.Injector {
return this.container return this.container
} }
@@ -30,6 +30,6 @@ func (this BaseController) Dispatch(job string, payload any, u req.User) error {
return do.MustInvoke[JobService](this.Container()).Dispatch(job, payload, u) return do.MustInvoke[JobService](this.Container()).Dispatch(job, payload, u)
} }
func NewBaseController(container *do.Injector) Controller { func NewBaseController(container do.Injector) Controller {
return &BaseController{container} return &BaseController{container}
} }
+67
View File
@@ -0,0 +1,67 @@
package contracts
import (
"io"
"net/http"
"git.fsdpf.net/go/req"
"github.com/samber/do/v2"
)
type EventStream interface {
Controller
// 获取请求信息
Request() *http.Request
// 请求处理
Execute(req.GlobalParams) error
// 获取用户信息
User() req.User
// 路由信息
Route() req.Route
// 发送数据
Sent(data, event string) error
}
type EsHandleController struct {
Controller
w http.ResponseWriter
flusher http.Flusher
request *http.Request
}
func (this EsHandleController) Request() *http.Request {
return this.request
}
func (this EsHandleController) Route() req.Route {
return this.Request().Context().Value(req.RouteCtx{Name: "Route"}).(req.Route)
}
func (this EsHandleController) User() req.User {
return this.Request().Context().Value(req.RouteCtx{Name: "User"}).(req.User)
}
func (this EsHandleController) Execute(req.GlobalParams) error {
return ErrFuncNotImplemented
}
func (this EsHandleController) Sent(data, event string) error {
if _, err := io.WriteString(this.w, "event: "+event+"\ndata: "+data+"\n\n"); err != nil {
return err
}
this.flusher.Flush()
return nil
}
func NewEventStreamController(container do.Injector, request *http.Request, w http.ResponseWriter) EventStream {
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
return &EsHandleController{
Controller: &BaseController{container},
request: request,
w: w,
flusher: w.(http.Flusher),
}
}
+27
View File
@@ -0,0 +1,27 @@
package contracts
import (
"git.fsdpf.net/go/req"
"github.com/samber/do/v2"
)
type Executor interface {
Controller
Res() req.Resource
}
type BaseExecutor struct {
Controller
res string
}
func (be BaseExecutor) Res() req.Resource {
return do.MustInvoke[MustResource](be.Container())(be.res)
}
func NewBaseExecutor(container do.Injector, res string) *BaseExecutor {
return &BaseExecutor{
Controller: &BaseController{container},
res: res,
}
}
+41 -26
View File
@@ -1,56 +1,71 @@
module git.fsdpf.net/go/contracts module git.fsdpf.net/go/contracts
go 1.21 go 1.24
toolchain go1.24.10
replace (
git.fsdpf.net/go/condition => ../condition-v2
git.fsdpf.net/go/db => ../db-v2
git.fsdpf.net/go/reflux => ../reflux
git.fsdpf.net/go/req => ../req-v2
)
require ( require (
git.fsdpf.net/go/condition v0.0.0-20241126093638-50e1b3630e6b git.fsdpf.net/go/condition v0.0.0-00010101000000-000000000000
git.fsdpf.net/go/db v0.0.0-20241125095839-f186c88c5c2a git.fsdpf.net/go/db v0.0.0-00010101000000-000000000000
github.com/go-chi/chi/v5 v5.0.12 git.fsdpf.net/go/reflux v0.0.0-00010101000000-000000000000
github.com/golang/protobuf v1.5.2 git.fsdpf.net/go/req v0.0.0-00010101000000-000000000000
github.com/google/uuid v1.3.0 git.fsdpf.net/go/utils v0.0.0-20240509025914-c03a9cb48aff
github.com/gorilla/websocket v1.5.0 github.com/samber/do/v2 v2.0.0
github.com/jhump/protoreflect v1.15.1 github.com/stretchr/testify v1.11.1
github.com/lestrrat-go/jwx v1.2.25 )
github.com/samber/do v1.6.0
github.com/samber/lo v1.39.0 require (
github.com/spf13/cast v1.6.0 github.com/davecgh/go-spew v1.1.1 // indirect
github.com/spf13/viper v1.15.0 github.com/go-sql-driver/mysql v1.7.1 // indirect
github.com/tidwall/gjson v1.17.1 github.com/mattn/go-sqlite3 v1.14.42 // indirect
github.com/tidwall/sjson v1.2.5 github.com/pmezard/go-difflib v1.0.0 // indirect
google.golang.org/grpc v1.52.0 github.com/samber/go-type-to-string v1.8.0 // indirect
) )
require ( require (
git.fsdpf.net/go/req v0.0.0-20241126093511-f1774e5ca2f0 // indirect
git.fsdpf.net/go/utils v0.0.0-20240509025914-c03a9cb48aff // indirect
github.com/bufbuild/protocompile v0.4.0 // indirect github.com/bufbuild/protocompile v0.4.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/go-chi/chi/v5 v5.0.12 // indirect
github.com/goccy/go-json v0.9.7 // indirect github.com/goccy/go-json v0.10.5 // indirect
github.com/golang/protobuf v1.5.2
github.com/gorilla/websocket v1.5.0
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/jhump/protoreflect v1.15.1
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
github.com/lestrrat-go/blackmagic v1.0.0 // indirect github.com/lestrrat-go/blackmagic v1.0.0 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/iter v1.0.1 // indirect github.com/lestrrat-go/iter v1.0.1 // indirect
github.com/lestrrat-go/jwx v1.2.25
github.com/lestrrat-go/option v1.0.0 // indirect github.com/lestrrat-go/option v1.0.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-sqlite3 v1.14.16 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/samber/lo v1.49.1
github.com/spf13/afero v1.9.3 // indirect github.com/spf13/afero v1.9.3 // indirect
github.com/spf13/cast v1.10.0
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.15.0
github.com/subosito/gotenv v1.4.2 // indirect github.com/subosito/gotenv v1.4.2 // indirect
github.com/tidwall/gjson v1.17.1 // indirect
github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect github.com/tidwall/pretty v1.2.0 // indirect
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect github.com/tidwall/sjson v1.2.5 // indirect
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect golang.org/x/crypto v0.38.0 // indirect
golang.org/x/net v0.7.0 // indirect golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.5.0 // indirect golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.7.0 // indirect golang.org/x/text v0.25.0 // indirect
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect
google.golang.org/grpc v1.52.0
google.golang.org/protobuf v1.28.2-0.20230222093303-bc1253ad3743 // indirect google.golang.org/protobuf v1.28.2-0.20230222093303-bc1253ad3743 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
+44 -77
View File
@@ -36,50 +36,12 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
git.fsdpf.net/go/condition v0.0.0-20240509022115-37018837d1b0 h1:MHjkN+zrSCqb7Qswn9fFrHT4XaoDnGbXz0ssSKnWnQk=
git.fsdpf.net/go/condition v0.0.0-20240509022115-37018837d1b0/go.mod h1:Zj9yrMa/A3WICTc9zn3W55ociuoBompEsVPo6EUXW5Q=
git.fsdpf.net/go/condition v0.0.0-20240509023514-a871ccfa75ac h1:pJQ0sFjPtv/RCy8k0mC/Kpn/j6As5bSqXyG0MWaylhc=
git.fsdpf.net/go/condition v0.0.0-20240509023514-a871ccfa75ac/go.mod h1:Zj9yrMa/A3WICTc9zn3W55ociuoBompEsVPo6EUXW5Q=
git.fsdpf.net/go/condition v0.0.0-20240509024751-9694d9033266 h1:s5On5PKF1ZiIGgq6R2Nk/2GcURuZxDAVK1Sru54Ybn0=
git.fsdpf.net/go/condition v0.0.0-20240509024751-9694d9033266/go.mod h1:Zj9yrMa/A3WICTc9zn3W55ociuoBompEsVPo6EUXW5Q=
git.fsdpf.net/go/condition v0.0.0-20240509030058-b0e337133e44 h1:XHJxWJuofEL7TLMDBsywi48nciaon8bEFOJLMYkOrNk=
git.fsdpf.net/go/condition v0.0.0-20240509030058-b0e337133e44/go.mod h1:xgX0fc3LEgrPk9tihGQqoQND4EWlNgYbLCi70TuCvvM=
git.fsdpf.net/go/condition v0.0.0-20240509034445-afc243ea916e h1:ZfwjqZS5URF4Y0JfQeytSdmXimJ4l74ooWRppza+3Tk=
git.fsdpf.net/go/condition v0.0.0-20240509034445-afc243ea916e/go.mod h1:K5ocyKVxgC5mI1k/crgrEozK0r51qAavWg0hweXo6PU=
git.fsdpf.net/go/condition v0.0.0-20241126084857-bb134581146e h1:GR7OmszSIcutwYfYkL0wGJXKlLzOHCszjN7Oy2jAcRU=
git.fsdpf.net/go/condition v0.0.0-20241126084857-bb134581146e/go.mod h1:cQhj0R7Q1GP4qxOvqRAwpGPSdYCX/c6dN3WSHhjmMvw=
git.fsdpf.net/go/condition v0.0.0-20241126090116-c3471706f4d4 h1:BMO+OujDukHDKD0RLUEu7w1ZZyHvtz8k69pHUiP7yPE=
git.fsdpf.net/go/condition v0.0.0-20241126090116-c3471706f4d4/go.mod h1:uKIEYgBdHFFnNQs6y7cvwsi4b/0Jk1QDneDgemo3Oa0=
git.fsdpf.net/go/condition v0.0.0-20241126093638-50e1b3630e6b h1:0Ez7ny8HNlZTMhYdlsGbSDcc9vkWt/V/OWlPi+sDw/s=
git.fsdpf.net/go/condition v0.0.0-20241126093638-50e1b3630e6b/go.mod h1:5N2xtaawQLquSsF8jJYPvOXhYNplBK+Iib783hTRg8c=
git.fsdpf.net/go/db v0.0.0-20230621051209-5740d112407f h1:H+/H6j+hyFMgSy+tUNmymRy7/PWQRjjp8qY/DrhwrsY=
git.fsdpf.net/go/db v0.0.0-20230621051209-5740d112407f/go.mod h1:397Sdx1cJS0OlHtTX1bVl//9k3Xn0Klnc6jC4MAkb6w=
git.fsdpf.net/go/db v0.0.0-20230731125324-11651ea6640b h1:fRgWNOQ9dAYuUZHQ24oi1XqRbJIcJvZGbnQDaAKI7IY=
git.fsdpf.net/go/db v0.0.0-20230731125324-11651ea6640b/go.mod h1:397Sdx1cJS0OlHtTX1bVl//9k3Xn0Klnc6jC4MAkb6w=
git.fsdpf.net/go/db v0.0.0-20241125095839-f186c88c5c2a h1:upXLpdDk7Oz7rNoMLx4uDRx50P3tLvwwlslywSkdygk=
git.fsdpf.net/go/db v0.0.0-20241125095839-f186c88c5c2a/go.mod h1:397Sdx1cJS0OlHtTX1bVl//9k3Xn0Klnc6jC4MAkb6w=
git.fsdpf.net/go/req v0.0.0-20240508133526-672fc634ef20 h1:PhjRtac2r0Vn1WBBzhRhz4//bJhHgkf/SHZe3mQYPto=
git.fsdpf.net/go/req v0.0.0-20240508133526-672fc634ef20/go.mod h1:S+p7t3XclKxsvaXQng7AMmqEGq11FSnMzxOtv1z8JUw=
git.fsdpf.net/go/req v0.0.0-20240509031332-f00b993f9c7d h1:TKNhQWDKyL50eVC/ptur1YuRkjRnKOlMsE1Y9tFCVbk=
git.fsdpf.net/go/req v0.0.0-20240509031332-f00b993f9c7d/go.mod h1:S+p7t3XclKxsvaXQng7AMmqEGq11FSnMzxOtv1z8JUw=
git.fsdpf.net/go/req v0.0.0-20240509031626-6501b5ed0f2b h1:KlVBAuWnFt1lcRTCcBU165MIldJqleO/LahWrd3FvUA=
git.fsdpf.net/go/req v0.0.0-20240509031626-6501b5ed0f2b/go.mod h1:S+p7t3XclKxsvaXQng7AMmqEGq11FSnMzxOtv1z8JUw=
git.fsdpf.net/go/req v0.0.0-20240509033523-c8cda2d8db56 h1:7ElgXUzt75brzOr44mItipTQTk++kZNoGOyTWHLxkTU=
git.fsdpf.net/go/req v0.0.0-20240509033523-c8cda2d8db56/go.mod h1:S+p7t3XclKxsvaXQng7AMmqEGq11FSnMzxOtv1z8JUw=
git.fsdpf.net/go/req v0.0.0-20240509033936-f8a11598db60 h1:hHrEcUSbtajC2LAo2fvEo7yrSSDeM/s5FAuQluP7yZQ=
git.fsdpf.net/go/req v0.0.0-20240509033936-f8a11598db60/go.mod h1:S+p7t3XclKxsvaXQng7AMmqEGq11FSnMzxOtv1z8JUw=
git.fsdpf.net/go/req v0.0.0-20240509034400-4ef8c130a758 h1:nHys8Bwb29b/FvnE100KVH62aD2j5CEPxTQ/RRHo27k=
git.fsdpf.net/go/req v0.0.0-20240509034400-4ef8c130a758/go.mod h1:S+p7t3XclKxsvaXQng7AMmqEGq11FSnMzxOtv1z8JUw=
git.fsdpf.net/go/req v0.0.0-20241126084545-fd3625802b40 h1:Ik3m4G4fY0kEHGX7qg01L2Q7OXjXAvRRqg8ErwQd2Hs=
git.fsdpf.net/go/req v0.0.0-20241126084545-fd3625802b40/go.mod h1:S+p7t3XclKxsvaXQng7AMmqEGq11FSnMzxOtv1z8JUw=
git.fsdpf.net/go/req v0.0.0-20241126090024-bccb0382d886 h1:LOKgXrnJT4xmxf9KkvZBOZDQYEhNz3h0TZl13R2GCtk=
git.fsdpf.net/go/req v0.0.0-20241126090024-bccb0382d886/go.mod h1:S+p7t3XclKxsvaXQng7AMmqEGq11FSnMzxOtv1z8JUw=
git.fsdpf.net/go/req v0.0.0-20241126093511-f1774e5ca2f0 h1:XevOuJLH63dzRiE6Sni/M0xOUw+bmrqH5Rcum+Aayjo=
git.fsdpf.net/go/req v0.0.0-20241126093511-f1774e5ca2f0/go.mod h1:S+p7t3XclKxsvaXQng7AMmqEGq11FSnMzxOtv1z8JUw=
git.fsdpf.net/go/utils v0.0.0-20240509025914-c03a9cb48aff h1:1hokQGKGIstoI7WDBir2N24OGgnussZ3GmucK4TQRuI= git.fsdpf.net/go/utils v0.0.0-20240509025914-c03a9cb48aff h1:1hokQGKGIstoI7WDBir2N24OGgnussZ3GmucK4TQRuI=
git.fsdpf.net/go/utils v0.0.0-20240509025914-c03a9cb48aff/go.mod h1:NUoyQtFr905YT+pi850mvSf4YX0WghQIcMQkTvize5o= git.fsdpf.net/go/utils v0.0.0-20240509025914-c03a9cb48aff/go.mod h1:NUoyQtFr905YT+pi850mvSf4YX0WghQIcMQkTvize5o=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -102,20 +64,20 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 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-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -157,6 +119,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -172,8 +135,6 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 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.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
@@ -192,10 +153,12 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A=
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
github.com/lestrrat-go/blackmagic v1.0.0 h1:XzdxDbuQTz0RZZEmdU7cnQxUtFUzgCSPq8RCz4BxIi4= github.com/lestrrat-go/blackmagic v1.0.0 h1:XzdxDbuQTz0RZZEmdU7cnQxUtFUzgCSPq8RCz4BxIi4=
@@ -208,12 +171,16 @@ github.com/lestrrat-go/jwx v1.2.25 h1:tAx93jN2SdPvFn08fHNAhqFJazn5mBBOB8Zli0g0ot
github.com/lestrrat-go/jwx v1.2.25/go.mod h1:zoNuZymNl5lgdcu6P7K6ie2QRll5HVfF4xwxBBK1NxY= github.com/lestrrat-go/jwx v1.2.25/go.mod h1:zoNuZymNl5lgdcu6P7K6ie2QRll5HVfF4xwxBBK1NxY=
github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4= github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.42 h1:MigqEP4ZmHw3aIdIT7T+9TLa90Z6smwcthx+Azv4Cgo=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.42/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -223,19 +190,18 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/samber/do v1.6.0 h1:Jy/N++BXINDB6lAx5wBlbpHlUdl0FKpLWgGEV9YWqaU= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/samber/do v1.6.0/go.mod h1:DWqBvumy8dyb2vEnYZE7D7zaVEB64J45B0NjTlY/M4k= github.com/samber/do/v2 v2.0.0 h1:tnunwWaoqSfJ9hxVIaJawIo7JXHQlqT9d9YBXlE9Keg=
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= github.com/samber/do/v2 v2.0.0/go.mod h1:ZSBCE7Xr6nTNIOVo4DBrkl2+ydUbIOzJjjdV8En5XO4=
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/samber/go-type-to-string v1.8.0 h1:5z6tDTjtXxkIAoAuHAZYMYR8mkBZjVgeSH7jcSLqc8w=
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= github.com/samber/go-type-to-string v1.8.0/go.mod h1:jpU77vIDoIxkahknKDoEx9C8bQ1ADnh2sotZ8I4QqBU=
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@@ -245,6 +211,8 @@ github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jH
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
@@ -252,13 +220,12 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
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/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U= github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
@@ -285,8 +252,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -297,8 +264,6 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM=
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -354,8 +319,8 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -375,7 +340,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -412,8 +378,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -422,8 +388,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -575,8 +541,9 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
google.golang.org/protobuf v1.28.2-0.20230222093303-bc1253ad3743 h1:yqElulDvOF26oZ2O+2/aoX7mQ8DY/6+p39neytrycd8= google.golang.org/protobuf v1.28.2-0.20230222093303-bc1253ad3743 h1:yqElulDvOF26oZ2O+2/aoX7mQ8DY/6+p39neytrycd8=
google.golang.org/protobuf v1.28.2-0.20230222093303-bc1253ad3743/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.2-0.20230222093303-bc1253ad3743/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+2 -2
View File
@@ -5,7 +5,7 @@ import (
"net/http" "net/http"
"git.fsdpf.net/go/req" "git.fsdpf.net/go/req"
"github.com/samber/do" "github.com/samber/do/v2"
) )
var defaultWsClientGroup WsClientGroup = "__DEFAULT__" var defaultWsClientGroup WsClientGroup = "__DEFAULT__"
@@ -95,7 +95,7 @@ func (HttpHandleController) AuthDB() req.ResAuthDB {
return req.ResAuthOn return req.ResAuthOn
} }
func NewHttpController(container *do.Injector, request *http.Request) HttpController { func NewHttpController(container do.Injector, request *http.Request) HttpController {
return &HttpHandleController{ return &HttpHandleController{
Controller: &BaseController{container}, Controller: &BaseController{container},
request: request, request: request,
+2 -2
View File
@@ -4,11 +4,11 @@ import (
"net/http" "net/http"
"github.com/lestrrat-go/jwx/jwt" "github.com/lestrrat-go/jwx/jwt"
"github.com/samber/do" "github.com/samber/do/v2"
) )
type JWTAuth interface { type JWTAuth interface {
HttpMiddleware(container *do.Injector) func(next http.Handler) http.Handler HttpMiddleware(container do.Injector) func(next http.Handler) http.Handler
Encode(claims map[string]interface{}) (token string, err error) Encode(claims map[string]interface{}) (token string, err error)
Decode(token string) (jwt.Token, error) Decode(token string) (jwt.Token, error)
} }
+7
View File
@@ -0,0 +1,7 @@
package contracts
import "net/http"
type MCPService interface {
ServeHTTP(w http.ResponseWriter, r *http.Request)
}
+2 -2
View File
@@ -2,7 +2,7 @@ package contracts
import ( import (
"git.fsdpf.net/go/req" "git.fsdpf.net/go/req"
"github.com/samber/do" "github.com/samber/do/v2"
) )
type MqttService interface { type MqttService interface {
@@ -88,7 +88,7 @@ func (this MqttHandle) OnPublish(g req.GlobalParams, topic string, retain bool,
return nil, ErrFuncNotImplemented return nil, ErrFuncNotImplemented
} }
func NewMqttController(container *do.Injector) Mqtt { func NewMqttController(container do.Injector) Mqtt {
return &MqttHandle{ return &MqttHandle{
Controller: &BaseController{container}, Controller: &BaseController{container},
} }
+47 -41
View File
@@ -1,10 +1,13 @@
package contracts package contracts
import ( import (
"context"
"reflect" "reflect"
"git.fsdpf.net/go/condition" "git.fsdpf.net/go/condition"
"git.fsdpf.net/go/db" "git.fsdpf.net/go/db"
"git.fsdpf.net/go/db/exp"
"git.fsdpf.net/go/reflux"
"git.fsdpf.net/go/req" "git.fsdpf.net/go/req"
) )
@@ -12,39 +15,35 @@ type OrmExecute int
type RelationType string type RelationType string
type OrderByDirection string
const ( const (
OrmExecute_Query OrmExecute = iota OrmQuery OrmExecute = iota
OrmExecute_Show OrmShow
OrmExecute_Store OrmStore
OrmExecute_Destroy OrmDestroy
) )
const ( const (
RelationType_HasOne RelationType = "hasOne" OrmHasOne RelationType = "hasOne"
RelationType_HasMany RelationType = "hasMany" OrmHasMany RelationType = "hasMany"
RelationType_Inner RelationType = "inner" OrmInnerJoin RelationType = "inner"
RelationType_Left RelationType = "left" OrmLeftJoin RelationType = "left"
RelationType_Right RelationType = "right" OrmRightJoin RelationType = "right"
) )
const ( type ModelDecorator func(Model) Model
OrderByDirection_ASC OrderByDirection = db.ORDER_ASC
OrderByDirection_DESC OrderByDirection = db.ORDER_DESC
)
type Orm interface { type Orm interface {
SetGlobalParams(g req.GlobalParams) Orm SetGlobalParams(g req.UserAccessor) Orm
GetModel(params ...string) Model GetModel(params ...string) Model
SetRelationModel(m Model) Model SetRelationModel(m Model) Model
SetQueryField(qf req.QueryField) error SetQueryField(qf req.QueryField) error
SetOrderBy(params []OrderBy) error SetOrderBy(params []OrderBy) error
SetCondition(cond *condition.Condition) error SetCondition(cond *condition.Condition) error
SetController(ctr OrmController) error SetController(ctr OrmController) error
Execute(OrmExecute) (any, error) Controller() reflect.Value
Clone(tx *db.Transaction) Orm Execute(OrmExecute) (reflux.R, error)
Count() int Clone(tx *db.TxDatabase) Orm
Count() int64
// Exists() bool // Exists() bool
} }
@@ -54,7 +53,6 @@ type Relation interface {
Type() RelationType Type() RelationType
SetType(RelationType) SetType(RelationType)
SetCondition(*condition.Condition) *condition.Condition SetCondition(*condition.Condition) *condition.Condition
SetPrependCondition(*condition.Condition) *condition.Condition
GetCondition() *condition.Condition GetCondition() *condition.Condition
GetRelationResource() string GetRelationResource() string
GetRelationField() string GetRelationField() string
@@ -66,8 +64,9 @@ type Join interface {
GetCode() string GetCode() string
GetResource() req.Resource GetResource() req.Resource
// GetDependencies(items []ResRelation, dependencies ...string) []string // GetDependencies(items []ResRelation, dependencies ...string) []string
Inject(dbBuilder *db.Builder, m Model) Inject(dbBuilder *db.SelectDataset, m Model)
} }
type Model interface { type Model interface {
Relation Relation
condition.TokenValue condition.TokenValue
@@ -76,28 +75,30 @@ type Model interface {
GetPrimaryKey() string GetPrimaryKey() string
GetQueryFieldsStruct(extends ...reflect.StructField) any GetQueryFieldsStruct(extends ...reflect.StructField) any
GetQueryFieldsSliceStruct(fields ...reflect.StructField) any GetQueryFieldsSliceStruct(fields ...reflect.StructField) any
GetAttribute() map[string]any GetAttribute() reflux.R
GetResult() reflect.Value SetRelationAttribute(string, reflux.R) error
GetResult() reflux.R
GetResource() req.Resource GetResource() req.Resource
QueryBuilder() *db.Builder GetTransaction() *db.TxDatabase
GetTransaction() *db.Transaction SetQueryField(...req.QueryField)
SetQueryField(req.QueryField) // SetRelationResult(code string, items []reflux.R)
SetRelationResult(code string, vItems reflect.Value)
GetJoinsCode() []string GetJoinsCode() []string
SetAuthDB(req.ResAuthDB) SetAuthDB(req.ResAuthDB)
SetOrderBy(params ...OrderBy) Model SetOrderBy(params ...OrderBy) Model
SetGroupBy(params ...GroupBy) Model SetGroupBy(params ...GroupBy) Model
SetJoin(Join) Model SetJoin(Join) Model
SetLimit(int) Model SetLimit(uint) Model
SetOffset(int) Model SetOffset(uint) Model
SetAttribute(data any) Model // SetAttribute(data any) Model
SetPrimaryKey(string) Model SetPrimaryKey(string) Model
SetWithRecursive(QueryWithRecursive) Model SetWithRecursive(QueryWithRecursive) Model
DelQueryField(string) DelQueryField(string)
SelectDataset() *db.SelectDataset
SelectDatasetWithUser(u req.User) *db.SelectDataset
Query() error Query() error
Store() error Store() error
Destroy() error Destroy() error
Count() (int, error) Count() (int64, error)
// Exists() (bool, error) // Exists() (bool, error)
} }
@@ -106,18 +107,18 @@ type OrmController interface {
} }
type OrderBy interface { type OrderBy interface {
ToSql() db.Expression ToSql() exp.OrderedExpression
Inject(dbBuilder *db.Builder, m Model) Inject(sd *db.SelectDataset, m Model)
} }
type GroupBy interface { type GroupBy interface {
ToSql() db.Expression ToSql() exp.ColumnListExpression
Inject(dbBuilder *db.Builder, m Model) Inject(sd *db.SelectDataset, m Model)
} }
type QueryWithRecursive interface { type QueryWithRecursive interface {
ToQueryBuilder(*db.Builder, Model) (*db.Builder, any) ToQueryBuilder(*db.SelectDataset, Model) (*db.SelectDataset, any)
ToTreeData(any) any ToTreeData([]reflux.R) reflux.R
} }
// @title 创建关联模型 // @title 创建关联模型
@@ -130,18 +131,23 @@ type NewOrmRelation func(t RelationType, m Model, rResource, rField, rForeignKey
type NewOrmJoin func(t RelationType, res req.Resource, alias, rResource, rField, rForeignKey string) Join type NewOrmJoin func(t RelationType, res req.Resource, alias, rResource, rField, rForeignKey string) Join
type NewOrmQueryWithRecursive func(pField, cField string, root any, isWithoutCondition bool, depth int) QueryWithRecursive type NewOrmQueryWithRecursive func(pField, cField exp.IdentifierExpression, root any, isWithoutCondition bool, depth uint) QueryWithRecursive
type NewOrmOrderBy func(sql string, direction OrderByDirection) OrderBy type NewOrmOrderBy func(sql db.Expression, direction exp.SortDirection) OrderBy
type NewOrmGroupBy func(sql string) GroupBy type NewOrmGroupBy func(db.Expression) GroupBy
type NewOrmModel func(res req.Resource, code, name string) Model type NewOrmModel func(res req.Resource, code, name string) Model
type NewOrm func(res req.Resource, tx *db.Transaction) Orm type NewOrm func(res req.Resource, opts ...OrmOption) Orm
type GetOrmConditions func(categoryUuid string, opts ...condition.Option) *condition.Condition type GetOrmConditions func(categoryUuid string, opts ...condition.Option) *condition.Condition
type GetOrmOrderBy func(categoryUuid string) []OrderBy type GetOrmOrderBy func(categoryUuid string) []OrderBy
type GetOrmGroupBy func(categoryUuid string) []GroupBy type GetOrmGroupBy func(categoryUuid string) []GroupBy
type OrmOption func(Orm)
type WithTx func(tx *db.TxDatabase) OrmOption
type WithContext func(ctx context.Context) OrmOption
type WithUserAccessor func(ua req.UserAccessor) OrmOption
+3 -3
View File
@@ -2,7 +2,7 @@ package contracts
import ( import (
"git.fsdpf.net/go/req" "git.fsdpf.net/go/req"
"github.com/samber/do" "github.com/samber/do/v2"
"github.com/samber/lo" "github.com/samber/lo"
) )
@@ -21,7 +21,7 @@ type ResListener interface {
} }
type BaseListener struct { type BaseListener struct {
Container *do.Injector Container do.Injector
res req.Resource // 监听资源 res req.Resource // 监听资源
code string // code code string // code
events []string // 监听动作 events []string // 监听动作
@@ -51,6 +51,6 @@ func (BaseListener) Delete(old map[string]any, u req.User) error {
return nil return nil
} }
func NewBaseListener(code string, res req.Resource, container *do.Injector) *BaseListener { func NewBaseListener(code string, res req.Resource, container do.Injector) *BaseListener {
return &BaseListener{code: code, res: res, Container: container} return &BaseListener{code: code, res: res, Container: container}
} }
-27
View File
@@ -1,27 +0,0 @@
package res_type
import (
"database/sql/driver"
"encoding/json"
"fmt"
)
// 解析map ----------------------------------
type ResFieldByMap map[string]any
func (c *ResFieldByMap) Scan(value any) error {
if value == nil {
return nil
}
if err := json.Unmarshal(value.([]byte), c); err != nil {
return fmt.Errorf("ResFieldByMap json.Unmarshal Error, %s", err)
}
return nil
}
func (c ResFieldByMap) Value() (driver.Value, error) {
b, err := json.Marshal(c)
return string(b), err
}
-3
View File
@@ -1,3 +0,0 @@
package res_type
type ResFieldByFloat float64
-3
View File
@@ -1,3 +0,0 @@
package res_type
type ResFieldByInteger int64
-38
View File
@@ -1,38 +0,0 @@
package res_type
import (
"database/sql/driver"
"encoding/json"
"fmt"
)
// 切片 ------------------------------------
type ResFieldByAnys []any
func (c *ResFieldByAnys) Scan(value any) error {
if value == nil {
return nil
}
switch v := value.(type) {
case []byte:
if len(v) == 0 {
return nil
}
if rune('[') != rune(v[0]) {
return nil
}
default:
}
if err := json.Unmarshal(value.([]byte), c); err != nil {
return fmt.Errorf("ResFieldByAnys json.Unmarshal Error, %s, %s", value, err)
}
return nil
}
func (c ResFieldByAnys) Value() (driver.Value, error) {
b, err := json.Marshal(c)
return string(b), err
}
-29
View File
@@ -1,29 +0,0 @@
package res_type
import (
"database/sql/driver"
"github.com/spf13/cast"
)
// Number 类型 --------------------------------
type ResFieldByNumber float64
func (this *ResFieldByNumber) Scan(value any) error {
if value == nil {
return nil
}
switch s := value.(type) {
case []byte:
*this = ResFieldByNumber(cast.ToFloat64(string(s)))
default:
*this = ResFieldByNumber(cast.ToFloat64(s))
}
return nil
}
func (this ResFieldByNumber) Value() (driver.Value, error) {
return this, nil
}
-28
View File
@@ -1,28 +0,0 @@
package res_type
import (
"database/sql/driver"
"fmt"
)
// 字符串 -------------------------------------
type ResFieldByString string
func (this *ResFieldByString) Scan(value any) error {
if value == nil {
return nil
}
switch s := value.(type) {
case []byte:
*this = ResFieldByString(s)
default:
*this = ResFieldByString(fmt.Sprintf("%v", s))
}
return nil
}
func (c ResFieldByString) Value() (driver.Value, error) {
return c, nil
}
+99
View File
@@ -0,0 +1,99 @@
package contracts
import (
"git.fsdpf.net/go/db"
"git.fsdpf.net/go/reflux"
"git.fsdpf.net/go/req"
"github.com/samber/do/v2"
)
// ResVirtualTable 虚拟表接口,由业务层实现,框架通过 SQLite vtab 机制调用。
// Select 提供列表查询,Fetch 提供单条完整查询(含 Detail 补充字段),
// Insert/Update/Delete 支持写操作并自动同步缓存。
type ResVirtualTable interface {
Controller
GetResource() req.Resource
// Fetch 按主键获取单条完整数据,内部负责调用远端接口并补充 Detail 专属字段。
// 框架在 item cache miss 时调用,结果写入 item cache 供后续查询复用。
Fetch(pk any) (map[string]any, error)
// Detail 对已有的 item 数据进行补充,填充列表接口不返回的详情字段。
// changed=true 时框架会将修改后的数据回写 item cache。
Detail(item reflux.R) (changed bool, err error)
// Select 查询列表数据,filter 为过滤条件,pagesize/page 控制分页。
// 返回的 items 每项为 map[string]anytotal 为总记录数。
Select(filter reflux.R, pagesize, page int) (items []any, total int64, err error)
// Insert 新增一条记录,返回新记录的 rowid。
Insert(item reflux.R) (rowid int64, err error)
// Update 按 rowid 更新记录。
Update(rowid any, item reflux.R) error
// Delete 按 rowid 删除记录。
Delete(rowid any) error
}
type BaseResVirtualTable struct {
Controller
res req.Resource
cache *db.Database
}
func (b *BaseResVirtualTable) Cache() *db.Database {
// CREATE TABLE IF NOT EXISTS ` + res.Table + ` (
// id INTEGER PRIMARY KEY AUTOINCREMENT,
// kind VARCHAR(8) NOT NULL DEFAULT 'list',
// key VARCHAR(255) NOT NULL,
// lmt INTEGER NOT NULL DEFAULT 0,
// page INTEGER NOT NULL DEFAULT 0,
// data TEXT NOT NULL,
// total INTEGER NOT NULL DEFAULT 0,
// expires_at BIGINT NOT NULL,
// UNIQUE(kind, key, lmt, page)
// )
return b.cache
}
func (b *BaseResVirtualTable) GetResource() req.Resource {
return b.res
}
func (b *BaseResVirtualTable) GetExtraFields() []req.ResField {
return nil
}
func (b *BaseResVirtualTable) Fetch(pk any) (map[string]any, error) {
return nil, nil
}
func (b *BaseResVirtualTable) Detail(detail reflux.R) (changed bool, err error) {
return false, nil
}
func (b *BaseResVirtualTable) Select(filter reflux.R, pagesize, page int) (items []any, total int64, err error) {
return nil, 0, ErrFuncNotImplemented
}
func (b *BaseResVirtualTable) Insert(item reflux.R) (rowid int64, err error) {
return 0, ErrFuncNotImplemented
}
func (b *BaseResVirtualTable) Update(rowid any, item reflux.R) error {
return ErrFuncNotImplemented
}
func (b *BaseResVirtualTable) Delete(rowid any) error {
return ErrFuncNotImplemented
}
func NewBaseResVirtualTable(container do.Injector, res req.Resource, conn *db.Database) ResVirtualTable {
return &BaseResVirtualTable{
Controller: &BaseController{container},
res: res,
cache: conn,
}
}
+8 -3
View File
@@ -1,9 +1,14 @@
package contracts package contracts
import "git.fsdpf.net/go/req" import (
"git.fsdpf.net/go/req"
)
// 资源 // GetResource 资源
type GetResource func(code string) (req.Resource, bool) type GetResource func(code string) (req.Resource, bool)
// 获取用户 // MustResource 资源, 没有则抛出错误
type MustResource func(code string) req.Resource
// GetUser 获取用户
type GetUser func(uuid string, options ...req.UserRuntimeOption) (req.User, error) type GetUser func(uuid string, options ...req.UserRuntimeOption) (req.User, error)
+5 -4
View File
@@ -8,8 +8,9 @@ import (
"git.fsdpf.net/go/condition" "git.fsdpf.net/go/condition"
"git.fsdpf.net/go/contracts" "git.fsdpf.net/go/contracts"
"git.fsdpf.net/go/db" "git.fsdpf.net/go/db"
"git.fsdpf.net/go/db/exp"
"git.fsdpf.net/go/utils" "git.fsdpf.net/go/utils"
"github.com/samber/do" "github.com/samber/do/v2"
"github.com/samber/lo" "github.com/samber/lo"
"github.com/spf13/cast" "github.com/spf13/cast"
) )
@@ -37,7 +38,7 @@ type OperationAccess struct {
data map[string][]any data map[string][]any
} }
func (o Operation) ToDBColumn(model contracts.Model, container *do.Injector) (column db.Expression, field reflect.StructField) { func (o Operation) ToDBColumn(model contracts.Model, container do.Injector) (column db.Expression, field reflect.StructField) {
GetOrmConditions := do.MustInvoke[contracts.GetOrmConditions](container) GetOrmConditions := do.MustInvoke[contracts.GetOrmConditions](container)
uuid := strings.ReplaceAll(o.Uuid, "-", "_") uuid := strings.ReplaceAll(o.Uuid, "-", "_")
@@ -52,9 +53,9 @@ func (o Operation) ToDBColumn(model contracts.Model, container *do.Injector) (co
} }
if cond.IsEmpty() { if cond.IsEmpty() {
column = db.Raw(pk + " as `u_" + uuid + "`") column = db.I(pk).As("u_" + uuid)
} else { } else {
column = db.Raw("if(" + string(cond.ToSql(model)) + ", " + pk + ", 0) as `u_" + uuid + "`") column = exp.NewSQLFunctionExpression("IF", cond.ToSql(model), db.I(pk), 0).As("u_" + uuid)
} }
// 主键类型 // 主键类型
+3 -3
View File
@@ -37,7 +37,7 @@ var wsUpgrader = websocket.Upgrader{
func (this HttpStatusResponse) Get(path ...string) req.GlobalParams { func (this HttpStatusResponse) Get(path ...string) req.GlobalParams {
if this.body == nil { if this.body == nil {
return req.NewGlobalParam("", nil) return req.NewGlobalParams("", nil)
} }
return this.body.Get(path...) return this.body.Get(path...)
} }
@@ -50,7 +50,7 @@ func (this HttpStatusResponse) Send(w http.ResponseWriter, r *http.Request) {
} }
func (this RawResponse) Get(path ...string) req.GlobalParams { func (this RawResponse) Get(path ...string) req.GlobalParams {
return lo.Ternary(len(path) == 0, req.NewGlobalParam(string(this.raw), nil), req.NewGlobalParam(string(this.raw), nil).Get(strings.Join(path, "."))) return lo.Ternary(len(path) == 0, req.NewGlobalParams(string(this.raw), nil), req.NewGlobalParams(string(this.raw), nil).Get(strings.Join(path, ".")))
} }
func (this RawResponse) Send(w http.ResponseWriter, r *http.Request) { func (this RawResponse) Send(w http.ResponseWriter, r *http.Request) {
@@ -72,7 +72,7 @@ func (this FileResponse) Get(path ...string) req.GlobalParams {
"name": this.name, "name": this.name,
"disposition": this.disposition, "disposition": this.disposition,
}); err == nil { }); err == nil {
return lo.Ternary(len(path) == 0, req.NewGlobalParam(string(resp), nil), req.NewGlobalParam(string(resp), nil).Get(strings.Join(path, "."))) return lo.Ternary(len(path) == 0, req.NewGlobalParams(string(resp), nil), req.NewGlobalParams(string(resp), nil).Get(strings.Join(path, ".")))
} }
return nil return nil
+1 -1
View File
@@ -56,7 +56,7 @@ type UserApproval interface {
// 判断发起审批请求权限 // 判断发起审批请求权限
HasUserRoles(req.User) bool HasUserRoles(req.User) bool
// 获取审批列表标记字段值 // 获取审批列表标记字段值
Execute(id int64, status UserApprovalExecute, r *http.Request, tx *db.Transaction) (current, next UserApprovalFlow, err error) Execute(id int64, status UserApprovalExecute, r *http.Request, tx *db.TxDatabase) (current, next UserApprovalFlow, err error)
} }
type UserApprovalFlow interface { type UserApprovalFlow interface {
+2 -2
View File
@@ -5,7 +5,7 @@ import (
"git.fsdpf.net/go/req" "git.fsdpf.net/go/req"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/samber/do" "github.com/samber/do/v2"
) )
type WsClientGroup string type WsClientGroup string
@@ -90,7 +90,7 @@ func (WsHandleController) ClientGroup(req.GlobalParams) WsClientGroup {
return defaultWsClientGroup return defaultWsClientGroup
} }
func NewWsController(container *do.Injector, request *http.Request, ws WsClient) WsController { func NewWsController(container do.Injector, request *http.Request, ws WsClient) WsController {
return &WsHandleController{ return &WsHandleController{
Controller: &BaseController{container}, Controller: &BaseController{container},
request: request, request: request,