diff --git a/.gitignore b/.gitignore index ad4e1f2..7c73ff3 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ go.work *.svg .qodo .history +services/examples/config diff --git a/examples/email_notification_dag.go b/examples/email_notification_dag.go index 0e0da58..a5598aa 100644 --- a/examples/email_notification_dag.go +++ b/examples/email_notification_dag.go @@ -66,7 +66,8 @@ func loginDAG() *dag.DAG { }, mq.WithSyncMode(true), mq.WithLogger(nil)) renderHTML := handlers.NewRenderHTMLNode("render-html") renderHTML.Payload.Data = map[string]any{ - "schema_file": "login.json", + "schema_file": "login.json", + "template_file": "app/templates/basic.html", } flow.AddNode(dag.Page, "Login Form", "LoginForm", renderHTML, true) flow.AddNode(dag.Function, "Validate Login", "ValidateLogin", &ValidateLoginNode{}) @@ -87,7 +88,8 @@ func main() { renderHTML := handlers.NewRenderHTMLNode("render-html") renderHTML.Payload.Data = map[string]any{ - "schema_file": "schema.json", + "schema_file": "schema.json", + "template_file": "app/templates/basic.html", } flow.AddDAGNode(dag.Page, "Check Login", "Login", loginDAG(), true) flow.AddNode(dag.Page, "Contact Form", "ContactForm", renderHTML) diff --git a/go.mod b/go.mod index 3b0ea69..7ffef39 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/oarkflow/expr v0.0.11 github.com/oarkflow/form v0.0.0-20241203111156-b1be5636af43 github.com/oarkflow/jet v0.0.4 - github.com/oarkflow/json v0.0.21 + github.com/oarkflow/json v0.0.28 github.com/oarkflow/log v1.0.79 github.com/oarkflow/xid v1.2.8 golang.org/x/crypto v0.33.0 diff --git a/go.sum b/go.sum index 10396a8..782da54 100644 --- a/go.sum +++ b/go.sum @@ -40,8 +40,8 @@ github.com/oarkflow/form v0.0.0-20241203111156-b1be5636af43 h1:AjNCAnpzDi6BYVUfX github.com/oarkflow/form v0.0.0-20241203111156-b1be5636af43/go.mod h1:fYwqhq8Sig9y0cmgO6q6WN8SP/rrsi7h2Yyk+Ufrne8= github.com/oarkflow/jet v0.0.4 h1:rs0nTzodye/9zhrSX7FlR80Gjaty6ei2Ln0pmaUrdwg= github.com/oarkflow/jet v0.0.4/go.mod h1:YXIc47aYyx1xKpnmuz1Z9o88cxxa47r7X3lfUAxZ0Qg= -github.com/oarkflow/json v0.0.21 h1:tBx4ufwC48UAd3fUCqLVH/dERpnZ85Dgw5/h7H2HMoM= -github.com/oarkflow/json v0.0.21/go.mod h1:maoLmQZJ/8pF1MugtpVqzHJ59dH1Z7xFSNkhl9BQjYo= +github.com/oarkflow/json v0.0.28 h1:pCt7yezRDJeSdSu2OZ6Aai0F4J9qCwmPWRsCmfaH8Ds= +github.com/oarkflow/json v0.0.28/go.mod h1:E6Mg4LoY1PHCntfAegZmECc6Ux24sBpXJAu2lwZUe74= github.com/oarkflow/jsonschema v0.0.4 h1:n5Sb7WVb7NNQzn/ei9++4VPqKXCPJhhsHeTGJkIuwmM= github.com/oarkflow/jsonschema v0.0.4/go.mod h1:AxNG3Nk7KZxnnjRJlHLmS1wE9brtARu5caTFuicCtnA= github.com/oarkflow/log v1.0.79 h1:DxhtkBGG+pUu6cudSVw5g75FbKEQJkij5w7n5AEN00M= diff --git a/services/dto.go b/services/dto.go new file mode 100644 index 0000000..8419012 --- /dev/null +++ b/services/dto.go @@ -0,0 +1,439 @@ +package services + +import ( + "fmt" + "reflect" + "strings" + + "github.com/oarkflow/json" + + "github.com/oarkflow/errors" +) + +type structValueMap = map[string]reflect.Value + +// Marker type for functions with no receiver +type nilRecvT struct{} + +var nilRecvRfType = reflect.TypeOf(nilRecvT{}) +var errorRfType = reflect.TypeOf((*error)(nil)).Elem() +var mapperPtrRfType = reflect.TypeOf((*Mapper)(nil)) + +type convertFuncClosure = func(reflect.Value, *Mapper) (reflect.Value, error) +type inspectFuncClosure = func(reflect.Value, reflect.Value, *Mapper) error + +const structTag = "dto" + +// NoValidMappingError indicates that no valid mapping was found +type NoValidMappingError struct { + ToType reflect.Type + FromType reflect.Type +} + +func (nvme NoValidMappingError) Error() string { + return fmt.Sprintf("No valid mapping found for %v from %v", nvme.ToType, nvme.FromType) +} + +// Mapper contains conversion and inspect functions +type Mapper struct { + // linear search might be faster than nested maps + convFunc map[reflect.Type]map[reflect.Type]convertFuncClosure + postFunc map[reflect.Type]map[reflect.Type][]inspectFuncClosure +} + +// ==================================== utils ================================= + +// Collect all struct fields (including anonymous) into a structValueMap +func collectStructFields(rfValue reflect.Value, rfType reflect.Type, fields structValueMap) { + for i := 0; i < rfType.NumField(); i++ { + fieldValue := rfValue.Field(i) + fieldType := rfType.Field(i) + if tags, ok := fieldType.Tag.Lookup(structTag); ok { + if strings.Contains(tags, "ignore") { + continue + } + } + if fieldType.Anonymous { + collectStructFields(fieldValue, fieldType.Type, fields) + } else { + fields[fieldType.Name] = fieldValue + } + } +} + +// Return reflect.Value with pointer removed (first layer only) +func reflectValueRemovePtr(v any) reflect.Value { + rv := reflect.ValueOf(v) + if rv.Type().Kind() == reflect.Ptr { + return rv.Elem() + } + return rv +} + +// Maps an error from a reflect value +// Panics if the value is non nill and not an error +func errorFromReflectValue(rv reflect.Value) error { + if rv.IsNil() { + return nil + } + err, ok := rv.Interface().(error) + if !ok { + panic("Failed to map error from reflect.Value") + } + return err +} + +// ==================================== Conversion and inspection functions === + +// Run inspect functions for (dst-src) pair +func (m *Mapper) runInspectFuncs(dstRv, srcRv reflect.Value) error { + toMap, ok := m.postFunc[dstRv.Type()] + if !ok { + return nil + } + for _, recvType := range []reflect.Type{srcRv.Type(), nilRecvRfType} { + funcs, ok := toMap[recvType] + if !ok { + continue + } + for _, fun := range funcs { + if err := fun(dstRv.Addr(), srcRv, m); err != nil { + return err + } + } + } + return nil +} + +// Run convert function for (dst-src) pair +// Returns (error, true) if a valid function was found, (nil, false) otherwise +func (m *Mapper) runConvFuncs(dstRv, srcRv reflect.Value) (bool, error) { + toMap, ok := m.convFunc[srcRv.Type()] + if !ok { + return false, nil + } + if convertFunc, ok := toMap[dstRv.Type()]; ok { + val, err := convertFunc(srcRv, m) + if err != nil { + return true, err + } + dstRv.Set(val) + return true, nil + } + return false, nil +} + +// HasCustomFuncs returns true if the Mapper has custom functions defined +func (m *Mapper) HasCustomFuncs() bool { + return len(m.convFunc)+len(m.postFunc) > 0 +} + +// AddConvFunc adds a conversion function to the Mapper +// +// Panics if f is not a valid conversion function +// Overwrites previous functions with the same type pair +func (m *Mapper) AddConvFunc(f any) { + rt := reflect.TypeOf(f) + + // check basic argument invariant + if rt.NumOut() < 1 || rt.NumIn() < 1 { + panic("Bad conversion function") + } + + // check if to inject mapper + takesMapper := false + if rt.NumIn() > 1 && rt.In(1) == mapperPtrRfType { + takesMapper = true + } + + // check if returns an error + returnsError := false + outType := rt.Out(0) + if rt.NumOut() > 1 && rt.Out(1).Implements(errorRfType) { + returnsError = true + } + + inType := rt.In(0) + + // create maps + if len(m.convFunc) == 0 { + m.convFunc = make(map[reflect.Type]map[reflect.Type]convertFuncClosure) + } + if len(m.convFunc[inType]) == 0 { + m.convFunc[inType] = make(map[reflect.Type]convertFuncClosure) + } + + // register closure + m.convFunc[inType][outType] = func(from reflect.Value, m *Mapper) (reflect.Value, error) { + args := []reflect.Value{from} + if takesMapper { + args = append(args, reflect.ValueOf(m)) + } + out := reflect.ValueOf(f).Call(args) + if returnsError { + return out[0], errorFromReflectValue(out[1]) + } + return out[0], nil + } +} + +// AddInspectFunc adds an inspection function to the Mapper +// +// Panics if f is not a valid inspection function +func (m *Mapper) AddInspectFunc(f any) { + ft := reflect.TypeOf(f) + inType := ft.In(0).Elem() + + // check if takes from + fromType := nilRecvRfType + if ft.NumIn() > 1 { + fromType = ft.In(1) + } + + // check if takes mapper + takesMapper := false + if ft.NumIn() > 2 && ft.In(2) == reflect.TypeOf(m) { + takesMapper = true + } + + // check if returns error + returnsError := false + if ft.NumOut() > 0 && ft.Out(0).Implements(errorRfType) { + returnsError = true + } + + // create map path + if len(m.postFunc) == 0 { + m.postFunc = make(map[reflect.Type]map[reflect.Type][]inspectFuncClosure) + } + if len(m.postFunc[inType]) == 0 { + m.postFunc[inType] = make(map[reflect.Type][]inspectFuncClosure) + } + + // register closure + m.postFunc[inType][fromType] = append(m.postFunc[inType][fromType], + func(v1, v2 reflect.Value, m *Mapper) error { + args := []reflect.Value{v1} + if fromType != nilRecvRfType { + args = append(args, v2) + } + if takesMapper { + args = append(args, reflect.ValueOf(m)) + } + + out := reflect.ValueOf(f).Call(args) + if returnsError { + return errorFromReflectValue(out[0]) + } + return nil + }, + ) +} + +// ==================================== Mapping functions ===================== + +// Map slices +// Panics if arguments are not slices +func (m *Mapper) mapSlice(toRv, fromRv reflect.Value) error { + toRv.Set(reflect.MakeSlice(toRv.Type(), fromRv.Len(), fromRv.Len())) + for i := 0; i < fromRv.Len(); i++ { + if err := m.mapValue(toRv.Index(i), fromRv.Index(i)); err != nil { + return err + } + } + return nil +} + +// Map maps +// Panics if arguments are not maps +func (m *Mapper) mapMap(dstRv, srcRv reflect.Value) error { + dstRv.Set(reflect.MakeMapWithSize(dstRv.Type(), srcRv.Len())) + // Map values + mapIt := srcRv.MapRange() + for mapIt.Next() { + toKey := reflect.New(dstRv.Type().Key()).Elem() + toValue := reflect.New(dstRv.Type().Elem()).Elem() + if err := m.mapValue(toKey, mapIt.Key()); err != nil { + return err + } + if err := m.mapValue(toValue, mapIt.Value()); err != nil { + return err + } + dstRv.SetMapIndex(toKey, toValue) + } + return nil +} + +// Map structs +// Panics if arguments are not structs +func (m *Mapper) mapStructs(dstRv, srcRv reflect.Value) error { + toFields := make(structValueMap) + collectStructFields(dstRv, dstRv.Type(), toFields) + + fromFields := make(structValueMap) + collectStructFields(srcRv, srcRv.Type(), fromFields) + + for fieldName, toValue := range toFields { + fromValue, ok := fromFields[fieldName] + if !ok { + continue + } + err := m.mapValue(toValue, fromValue) + if err != nil { + return err + } + } + + return nil +} + +// Map map values to slice +// Panics if arguments are not slice and map accordingly +func (m *Mapper) mapMapToSlice(dstRv, srcRv reflect.Value) error { + dstRv.Set(reflect.MakeSlice(dstRv.Type(), srcRv.Len(), srcRv.Len())) + i := 0 + mapIt := srcRv.MapRange() + for mapIt.Next() { + if err := m.mapValue(dstRv.Index(i), mapIt.Value()); err != nil { + return err + } + i++ + } + return nil +} + +// Map a map of slices to slice +// Panics of arguments are not a map of slices and a slice accordingly +func (m *Mapper) mapMapSlicesToSlice(dstRv, srcRv reflect.Value) error { + // calculate length + sumLen := 0 + mapIt := srcRv.MapRange() + for mapIt.Next() { + sumLen += mapIt.Value().Len() + } + + dstRv.Set(reflect.MakeSlice(dstRv.Type(), sumLen, sumLen)) + + i := 0 + mapIt = srcRv.MapRange() + for mapIt.Next() { + mapSlice := mapIt.Value() + for j := 0; j < mapSlice.Len(); i, j = i+1, j+1 { + if err := m.mapValue(dstRv.Index(i), mapSlice.Index(j)); err != nil { + return err + } + } + } + + return nil +} + +// Try to map any value +func (m *Mapper) mapValue(dstRv, srcRv reflect.Value) (returnError error) { + tk, fk := dstRv.Type().Kind(), srcRv.Type().Kind() + + // Defer inspect functions + defer func() { + if returnError != nil { + return + } + returnError = m.runInspectFuncs(dstRv, srcRv) + }() + + // 1. Check conversion functions + converted, err := m.runConvFuncs(dstRv, srcRv) + if converted { + return err + } + + // 2. Check direct assignment + if srcRv.Type().AssignableTo(dstRv.Type()) { + dstRv.Set(srcRv) + return + } + + // 3. Check conversion + if srcRv.Type().ConvertibleTo(dstRv.Type()) { + dstRv.Set(srcRv.Convert(dstRv.Type())) + return + } + + // 4. Handle pointers by dereferencing from + if fk == reflect.Ptr { + // Skip null pointers + if srcRv.IsNil() { + return nil + } + return m.mapValue(dstRv, srcRv.Elem()) + } + + // 5. Handle pointers by dereferencing to + if tk == reflect.Ptr { + // Allocate new value if nil + if dstRv.IsNil() { + dstRv.Set(reflect.New(dstRv.Type().Elem())) + } + return m.mapValue(dstRv.Elem(), srcRv) + } + + // 6. Handle sructs + if tk == reflect.Struct && fk == reflect.Struct { + return m.mapStructs(dstRv, srcRv) + } + + // 7. Handle slices + if tk == reflect.Slice && fk == reflect.Slice { + return m.mapSlice(dstRv, srcRv) + } + + // 8. Handle maps + if tk == reflect.Map && fk == reflect.Map { + return m.mapMap(dstRv, srcRv) + } + + // 9. Handle map to slice + if tk == reflect.Slice && fk == reflect.Map { + err := m.mapMapToSlice(dstRv, srcRv) + + // 9. Handle map of slices to slice + mapElemK := srcRv.Type().Elem().Kind() + if errors.As(err, &NoValidMappingError{}) && mapElemK == reflect.Slice { + // dont propagate errors + if errFlatten := m.mapMapSlicesToSlice(dstRv, srcRv); errFlatten == nil { + return + } + } + + return err + } + + return NoValidMappingError{ + ToType: dstRv.Type(), + FromType: srcRv.Type(), + } +} + +// ==================================== Public helpers ======================== + +// Map transfers values from src to dst +func (m *Mapper) Map(dst, src any) error { + return m.mapValue(reflectValueRemovePtr(dst), reflectValueRemovePtr(src)) +} + +// Map transfers values from src to dst +func Map(dst, src any) error { + m := Mapper{} + err := m.Map(dst, src) + if err != nil { + return MapJson(dst, src) + } + return err +} + +func MapJson(dst, src any) error { + bt, err := json.Marshal(src) + if err != nil { + return err + } + return json.Unmarshal(bt, dst) +} diff --git a/services/examples/main.go b/services/examples/main.go new file mode 100644 index 0000000..e521084 --- /dev/null +++ b/services/examples/main.go @@ -0,0 +1,13 @@ +package main + +import ( + "fmt" + + "github.com/oarkflow/mq/services" +) + +func main() { + loader := services.NewLoader("config") + loader.Load() + fmt.Println(loader.UserConfig) +} diff --git a/services/generic_command.go b/services/generic_command.go new file mode 100644 index 0000000..6c3425a --- /dev/null +++ b/services/generic_command.go @@ -0,0 +1,60 @@ +package services + +import ( + "github.com/oarkflow/cli/contracts" + "github.com/oarkflow/errors" +) + +type Flag struct { + Name string `json:"name"` + Value string `json:"value"` + Usage string `json:"usage"` + Aliases []string `json:"aliases"` + Required bool `json:"required"` +} +type GenericCommand struct { + handler func(ctx contracts.Context) error + Command string `json:"signature"` + Desc string `json:"description"` + Handler Handler `json:"handler"` + HandlerKey string `json:"handler_key"` + Flags []Flag `json:"flags"` +} + +// Signature The name and signature of the console command. +func (receiver *GenericCommand) Signature() string { + return receiver.Command +} + +// Description The console command description. +func (receiver *GenericCommand) Description() string { + return receiver.Desc +} + +// Extend The console command extend. +func (receiver *GenericCommand) Extend() contracts.Extend { + var flags []contracts.Flag + for _, flag := range receiver.Flags { + flags = append(flags, contracts.Flag{ + Name: flag.Name, + Aliases: flag.Aliases, + Usage: flag.Usage, + Required: flag.Required, + Value: flag.Value, + }) + } + return contracts.Extend{Flags: flags} +} + +// Handle Execute the console command. +func (receiver *GenericCommand) Handle(ctx contracts.Context) error { + if receiver.handler == nil { + return errors.New("Handler not found") + } + return receiver.handler(ctx) +} + +// SetHandler Execute the console command. +func (receiver *GenericCommand) SetHandler(handler func(ctx contracts.Context) error) { + receiver.handler = handler +} diff --git a/services/go.mod b/services/go.mod new file mode 100644 index 0000000..7b8b92c --- /dev/null +++ b/services/go.mod @@ -0,0 +1,67 @@ +module github.com/oarkflow/mq/services + +go 1.24.2 + +require ( + github.com/oarkflow/cli v0.0.0-20250313133305-8d14a63c1883 + github.com/oarkflow/errors v0.0.6 + github.com/oarkflow/filters v0.0.36 + github.com/oarkflow/jenv v0.0.2 + github.com/oarkflow/json v0.0.28 + github.com/oarkflow/jsonschema v0.0.4 + github.com/oarkflow/log v1.0.83 + github.com/oarkflow/metadata v0.0.78 + github.com/oarkflow/mq v0.0.17 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/andybalholm/brotli v1.1.1 // indirect + github.com/bytedance/gopkg v0.1.1 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-reflect v1.2.0 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect + github.com/gofiber/fiber/v2 v2.52.6 // indirect + github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect + github.com/golang-sql/sqlexp v0.1.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/gotnospirit/makeplural v0.0.0-20180622080156-a5f48d94d976 // indirect + github.com/gotnospirit/messageformat v0.0.0-20221001023931-dfe49f1eb092 // indirect + github.com/hetiansu5/urlquery v1.2.7 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.7.2 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/kaptinlin/go-i18n v0.1.4 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/microsoft/go-mssqldb v1.8.0 // indirect + github.com/oarkflow/convert v0.0.5 // indirect + github.com/oarkflow/date v0.0.4 // indirect + github.com/oarkflow/dipper v0.0.6 // indirect + github.com/oarkflow/expr v0.0.11 // indirect + github.com/oarkflow/form v0.0.0-20241203111156-b1be5636af43 // indirect + github.com/oarkflow/jet v0.0.4 // indirect + github.com/oarkflow/protocol v0.0.16 // indirect + github.com/oarkflow/render v0.0.1 // indirect + github.com/oarkflow/squealx v0.0.36 // indirect + github.com/oarkflow/xid v1.2.8 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/toorop/go-dkim v0.0.0-20240103092955-90b7d1423f92 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.59.0 // indirect + github.com/xhit/go-simple-mail/v2 v2.16.0 // indirect + golang.org/x/crypto v0.33.0 // indirect + golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect + golang.org/x/sync v0.14.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.25.0 // indirect + golang.org/x/time v0.11.0 // indirect +) diff --git a/services/go.sum b/services/go.sum new file mode 100644 index 0000000..0db7f21 --- /dev/null +++ b/services/go.sum @@ -0,0 +1,162 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 h1:jBQA3cKT4L2rWMpgE7Yt3Hwh2aUj8KXjIGLxjHeYNNo= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/bytedance/gopkg v0.1.1 h1:3azzgSkiaw79u24a+w9arfH8OfnQQ4MHUt9lJFREEaE= +github.com/bytedance/gopkg v0.1.1/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= +github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +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/goccy/go-reflect v1.2.0 h1:O0T8rZCuNmGXewnATuKYnkL0xm6o8UNOJZd/gOkb9ms= +github.com/goccy/go-reflect v1.2.0/go.mod h1:n0oYZn8VcV2CkWTxi8B9QjkCoq6GTtCEdfmR66YhFtE= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI= +github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gotnospirit/makeplural v0.0.0-20180622080156-a5f48d94d976 h1:b70jEaX2iaJSPZULSUxKtm73LBfsCrMsIlYCUgNGSIs= +github.com/gotnospirit/makeplural v0.0.0-20180622080156-a5f48d94d976/go.mod h1:ZGQeOwybjD8lkCjIyJfqR5LD2wMVHJ31d6GdPxoTsWY= +github.com/gotnospirit/messageformat v0.0.0-20221001023931-dfe49f1eb092 h1:c7gcNWTSr1gtLp6PyYi3wzvFCEcHJ4YRobDgqmIgf7Q= +github.com/gotnospirit/messageformat v0.0.0-20221001023931-dfe49f1eb092/go.mod h1:ZZAN4fkkful3l1lpJwF8JbW41ZiG9TwJ2ZlqzQovBNU= +github.com/hetiansu5/urlquery v1.2.7 h1:jn0h+9pIRqUziSPnRdK/gJK8S5TCnk+HZZx5fRHf8K0= +github.com/hetiansu5/urlquery v1.2.7/go.mod h1:wFpZdTHRdwt7mk0EM/DdZEWtEN4xf8HJoH/BLXm/PG0= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI= +github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/kaptinlin/go-i18n v0.1.4 h1:wCiwAn1LOcvymvWIVAM4m5dUAMiHunTdEubLDk4hTGs= +github.com/kaptinlin/go-i18n v0.1.4/go.mod h1:g1fn1GvTgT4CiLE8/fFE1hboHWJ6erivrDpiDtCcFKg= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/microsoft/go-mssqldb v1.8.0 h1:7cyZ/AT7ycDsEoWPIXibd+aVKFtteUNhDGf3aobP+tw= +github.com/microsoft/go-mssqldb v1.8.0/go.mod h1:6znkekS3T2vp0waiMhen4GPU1BiAsrP+iXHcE7a7rFo= +github.com/oarkflow/cli v0.0.0-20250313133305-8d14a63c1883 h1:N0Mroo3lyZTcVUyPAAO40mhgVicrweifMNnOrXQxt+A= +github.com/oarkflow/cli v0.0.0-20250313133305-8d14a63c1883/go.mod h1:8APSp4HJyCyh7R17XHQYSNz69N7+73S1z+/N8xhC6vo= +github.com/oarkflow/convert v0.0.5 h1:5s5DlnZLSUweB+EDUjynj0Eput7AkGUEQUvs/j4CWmM= +github.com/oarkflow/convert v0.0.5/go.mod h1:/N8EEJiKUCtYS//+UfS3IX3ej7K1wIPgW/1nBhbeEXs= +github.com/oarkflow/date v0.0.4 h1:EwY/wiS3CqZNBx7b2x+3kkJwVNuGk+G0dls76kL/fhU= +github.com/oarkflow/date v0.0.4/go.mod h1:xQTFc6p6O5VX6J75ZrPJbelIFGca1ASmhpgirFqL8vM= +github.com/oarkflow/dipper v0.0.6 h1:E+ak9i4R1lxx0B04CjfG5DTLTmwuWA1nrdS6KIHdUxQ= +github.com/oarkflow/dipper v0.0.6/go.mod h1:bnXQ6465eP8WZ9U3M7R24zeBG3P6IU5SASuvpAyCD9w= +github.com/oarkflow/errors v0.0.6 h1:qTBzVblrX6bFbqYLfatsrZHMBPchOZiIE3pfVzh1+k8= +github.com/oarkflow/errors v0.0.6/go.mod h1:UETn0Q55PJ+YUbpR4QImIoBavd6QvJtyW/oeTT7ghZM= +github.com/oarkflow/expr v0.0.11 h1:H6h+dIUlU+xDlijMXKQCh7TdE6MGVoFPpZU7q/dziRI= +github.com/oarkflow/expr v0.0.11/go.mod h1:WgMZqP44h7SBwKyuGZwC15vj46lHtI0/QpKdEZpRVE4= +github.com/oarkflow/filters v0.0.36 h1:7jVfQ/CBOc9+KKa8IOsKjGvPMgNpfkObS7DQAKpcImQ= +github.com/oarkflow/filters v0.0.36/go.mod h1:aNd+dCtqa6kjhMJgzkMkT7oRE/JkwMpR5vq0dSsDHpY= +github.com/oarkflow/form v0.0.0-20241203111156-b1be5636af43 h1:AjNCAnpzDi6BYVUfXUUuIdWruRu4npSSTrR3eZ6Vppw= +github.com/oarkflow/form v0.0.0-20241203111156-b1be5636af43/go.mod h1:fYwqhq8Sig9y0cmgO6q6WN8SP/rrsi7h2Yyk+Ufrne8= +github.com/oarkflow/jenv v0.0.2 h1:NrzvRkauJ4UDXDiyY6TorNv1fcEMf5J8HZNCtVSr4zo= +github.com/oarkflow/jenv v0.0.2/go.mod h1:jJS3oDWYtc8TbLGRiRC4f60P0jzDXvPIvqS5yTiwvSg= +github.com/oarkflow/jet v0.0.4 h1:rs0nTzodye/9zhrSX7FlR80Gjaty6ei2Ln0pmaUrdwg= +github.com/oarkflow/jet v0.0.4/go.mod h1:YXIc47aYyx1xKpnmuz1Z9o88cxxa47r7X3lfUAxZ0Qg= +github.com/oarkflow/json v0.0.28 h1:pCt7yezRDJeSdSu2OZ6Aai0F4J9qCwmPWRsCmfaH8Ds= +github.com/oarkflow/json v0.0.28/go.mod h1:E6Mg4LoY1PHCntfAegZmECc6Ux24sBpXJAu2lwZUe74= +github.com/oarkflow/jsonschema v0.0.4 h1:n5Sb7WVb7NNQzn/ei9++4VPqKXCPJhhsHeTGJkIuwmM= +github.com/oarkflow/jsonschema v0.0.4/go.mod h1:AxNG3Nk7KZxnnjRJlHLmS1wE9brtARu5caTFuicCtnA= +github.com/oarkflow/log v1.0.83 h1:T/38wvjuNeVJ9PDo0wJDTnTUQZ5XeqlcvpbCItuFFJo= +github.com/oarkflow/log v1.0.83/go.mod h1:dMn57z9uq11Y264cx9c9Ac7ska9qM+EBhn4qf9CNlsM= +github.com/oarkflow/metadata v0.0.78 h1:ciKbtzQGXYvSlxaFYtDX1CocCkchHskreAldVIkHIMg= +github.com/oarkflow/metadata v0.0.78/go.mod h1:T6Bcsq2FVjrJYMJpMluQTw+/xkqUwax7m/qGHTDCyaw= +github.com/oarkflow/mq v0.0.17 h1:krNZW4Gi3CO90HYhAhsskVhNoObWhGjmsMLqcTuNjLQ= +github.com/oarkflow/mq v0.0.17/go.mod h1:nD3C1f4qniuGKl6pmp+BrzKcjYOZ8d+gmEUkDSOrG0Y= +github.com/oarkflow/protocol v0.0.16 h1:3qNn9gwoJOpdz+owyAmW4fNMpQplqHVIjzsWM4r0pcA= +github.com/oarkflow/protocol v0.0.16/go.mod h1:iKP/I+3/FIWlZ6OphAo8c60JO2qgwethOMR+NMsMI28= +github.com/oarkflow/render v0.0.1 h1:Caw74Yu8OE/tjCjurhbUkS0Fi9zE/mzVvQa1Cw7m7R4= +github.com/oarkflow/render v0.0.1/go.mod h1:nnRhxhKn9NCPtTfbsaLuyCt86Iv9hMbNPDFQoPucQYI= +github.com/oarkflow/squealx v0.0.36 h1:lEyvHOd+A2pxogyRvMdGAM8hw5sLY4SztdLcx+Rzer8= +github.com/oarkflow/squealx v0.0.36/go.mod h1:lXhv4tBTxRVAZfqft9En1V8LdEy42778AjjTiN04ctg= +github.com/oarkflow/xid v1.2.8 h1:uCIX61Binq2RPMsqImZM6pPGzoZTmRyD6jguxF9aAA0= +github.com/oarkflow/xid v1.2.8/go.mod h1:jG4YBh+swbjlWApGWDBYnsJEa7hi3CCpmuqhB3RAxVo= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns= +github.com/toorop/go-dkim v0.0.0-20240103092955-90b7d1423f92 h1:flbMkdl6HxQkLs6DDhH1UkcnFpNBOu70391STjMS0O4= +github.com/toorop/go-dkim v0.0.0-20240103092955-90b7d1423f92/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.59.0 h1:Qu0qYHfXvPk1mSLNqcFtEk6DpxgA26hy6bmydotDpRI= +github.com/valyala/fasthttp v1.59.0/go.mod h1:GTxNb9Bc6r2a9D0TWNSPwDz78UxnTGBViY3xZNEqyYU= +github.com/xhit/go-simple-mail/v2 v2.16.0 h1:ouGy/Ww4kuaqu2E2UrDw7SvLaziWTB60ICLkIkNVccA= +github.com/xhit/go-simple-mail/v2 v2.16.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/services/loader.go b/services/loader.go new file mode 100644 index 0000000..49919b8 --- /dev/null +++ b/services/loader.go @@ -0,0 +1,460 @@ +package services + +import ( + "errors" + "os" + "path/filepath" + "slices" + "strings" + + "github.com/oarkflow/filters" + "github.com/oarkflow/jenv" + "github.com/oarkflow/json" + "github.com/oarkflow/metadata" + "gopkg.in/yaml.v3" +) + +type Loader struct { + path string + configFile string + ParsedPath string + UserConfig *UserConfig +} + +func NewLoader(path string, configFiles ...string) *Loader { + var configFile string + if len(configFiles) > 0 { + configFile = configFiles[0] + } + return &Loader{ + path: path, + configFile: configFile, + } +} + +func (l *Loader) Prefix() string { + return "" +} + +func (l *Loader) Load() { + l.ParsedPath = l.prepareConfigPath() + cfg, err := l.loadConfig() + if err != nil { + panic(err) + } + l.UserConfig = cfg +} + +func (l *Loader) prepareConfigPath() string { + path := l.path + if !filepath.IsAbs(path) { + b, err := filepath.Abs(path) + if err == nil { + path = b + } + } + return path +} + +func (l *Loader) loadConfig() (*UserConfig, error) { + cfg := &UserConfig{} + configFile := l.configFile + if configFile != "" { + err := readFile(configFile, cfg) + if err != nil { + return nil, err + } + initializeConfig(cfg) + } + if l.ParsedPath != "" { + err := readPath(l.ParsedPath, cfg) + if err != nil { + return nil, err + } + } + return cfg, nil +} + +func readPath(path string, cfg *UserConfig) error { + readers := []func(string, *UserConfig) error{ + readConfig, readCredentials, readConditions, readHandlers, + readModels, readApplicationRules, readCommands, readBackgroundTasks, + readWeb, readRenderer, readApis, + } + + for _, read := range readers { + if err := read(path, cfg); err != nil && !os.IsNotExist(err) { + return err + } + } + return nil +} + +func readFile(path string, cfg any) error { + content, err := os.ReadFile(path) + if err != nil { + return err + } + return unmarshalConfig(content, path, cfg) +} + +func unmarshalConfig(data []byte, path string, cfg any) error { + ext := filepath.Ext(path) + switch ext { + case ".json": + return jenv.UnmarshalJSON(data, cfg) + case ".yaml", ".yml": + return jenv.UnmarshalYAML(data, cfg) + default: + return errors.New("unsupported file format. Only yaml and json supported") + } +} + +func initializeConfig(cfg *UserConfig) { + // Initialize Application Rules + for i, applicationRule := range cfg.Policy.ApplicationRules { + if applicationRule.Rule != nil { + applicationRule.BuildRuleFromRequest(cfg.GetCondition) + cfg.Policy.ApplicationRules[i] = applicationRule + } + } + // Initialize Background Handlers + for i, command := range cfg.Policy.BackgroundHandlers { + if command.HandlerKey != "" { + if handler := cfg.GetHandler(command.HandlerKey); handler != nil { + command.Handler = *handler + cfg.Policy.BackgroundHandlers[i] = command + } + } + } + // Initialize API Handlers + for i, api := range cfg.Policy.Web.Apis { + for j, route := range api.Routes { + if route.HandlerKey != "" { + if handler := cfg.GetHandler(route.HandlerKey); handler != nil { + route.Handler = *handler + api.Routes[j] = route + cfg.Policy.Web.Apis[i] = api + } + } + } + } +} + +// Helper function to read either JSON or YAML config files +func readConfigFile[T any](path string, appendFn func(T)) error { + var out T + var err error + if content, readErr := os.ReadFile(path + ".json"); readErr == nil { + err = json.Unmarshal(content, &out) + } else if content, readErr := os.ReadFile(path + ".yaml"); readErr == nil { + err = yaml.Unmarshal(content, &out) + } else { + err = readErr // Set to the most recent read error if both files are missing + } + if err != nil { + return err + } + appendFn(out) + return nil +} + +func unmarshalContent[T any](content []byte, dataType string, appendFn func(T)) error { + var out T + var err error + switch dataType { + case "json": + err = jenv.UnmarshalJSON(content, &out) + case "yaml": + err = jenv.UnmarshalYAML(content, &out) + } + if err != nil { + return err + } + appendFn(out) + return nil +} + +// Helper function to read either JSON or YAML config files +func readArrayConfigFile[T any](path string, appendFn func(T)) error { + var err error + var data []json.RawMessage + var dataType string + if content, readErr := os.ReadFile(path + ".json"); readErr == nil { + dataType = "json" + err = json.Unmarshal(content, &data) + } else if content, readErr := os.ReadFile(path + ".yaml"); readErr == nil { + dataType = "yaml" + err = yaml.Unmarshal(content, &data) + } else { + err = readErr // Set to the most recent read error if both files are missing + } + if err != nil { + return err + } + for _, d := range data { + err = unmarshalContent(d, dataType, appendFn) + if err != nil { + return err + } + } + return nil +} + +// Sample read function with YAML/JSON support +func readConditions(path string, cfg *UserConfig) error { + path = filepath.Join(path, "policies", "conditions") + return readConfigFile(path, func(data []*filters.Filter) { + cfg.Policy.Conditions = append(cfg.Policy.Conditions, data...) + }) +} + +func readApplicationRules(path string, cfg *UserConfig) error { + modelsPath := filepath.Join(path, "policies", "application_rules") + entries, err := os.ReadDir(modelsPath) + if err != nil { + return err + } + for _, entry := range entries { + if !entry.IsDir() && isSupportedExt(filepath.Ext(entry.Name())) { + file := filepath.Join(modelsPath, entry.Name()) + var applicationRule *filters.ApplicationRule + content, err := os.ReadFile(file) + if err != nil { + return err + } + err = unmarshalConfig(content, file, &applicationRule) + if err != nil { + return err + } + if applicationRule.Rule != nil { + applicationRule.BuildRuleFromRequest(cfg.GetCondition) + cfg.Policy.ApplicationRules = append(cfg.Policy.ApplicationRules, applicationRule) + } + } + } + return nil +} + +func readCommands(path string, cfg *UserConfig) error { + modelsPath := filepath.Join(path, "policies", "commands") + entries, err := os.ReadDir(modelsPath) + if err != nil { + return err + } + for _, entry := range entries { + if !entry.IsDir() && isSupportedExt(filepath.Ext(entry.Name())) { + file := filepath.Join(modelsPath, entry.Name()) + var command GenericCommand + content, err := os.ReadFile(file) + if err != nil { + return err + } + err = unmarshalConfig(content, file, &command) + if err != nil { + return err + } + if command.HandlerKey != "" { + if handler := cfg.GetHandler(command.HandlerKey); handler != nil { + command.Handler = *handler + } + } + cfg.Policy.Commands = append(cfg.Policy.Commands, &command) + } + } + return nil +} + +func readDatabases(path string, cfg *UserConfig) error { + path = filepath.Join(path, "credentials", "databases") + return readArrayConfigFile(path, func(data metadata.Config) { + cfg.Core.Credentials.Databases = append(cfg.Core.Credentials.Databases, data) + }) +} + +func readStorages(path string, cfg *UserConfig) error { + path = filepath.Join(path, "credentials", "storages") + return readArrayConfigFile(path, func(data Storage) { + cfg.Core.Credentials.Storages = append(cfg.Core.Credentials.Storages, data) + }) +} + +func readCaches(path string, cfg *UserConfig) error { + path = filepath.Join(path, "credentials", "caches") + return readArrayConfigFile(path, func(data Cache) { + cfg.Core.Credentials.Caches = append(cfg.Core.Credentials.Caches, data) + }) +} + +func readCredentials(path string, cfg *UserConfig) error { + if err := readDatabases(path, cfg); err != nil { + return err + } + if err := readStorages(path, cfg); err != nil { + return err + } + return readCaches(path, cfg) +} + +func readConfig(path string, cfg *UserConfig) error { + path = filepath.Join(path, "conf") + return readConfigFile(path, func(coreData Core) { + if cfg.Core.Enums == nil { + cfg.Core.Enums = make(map[string]map[string]any) + } + for key, val := range coreData.Enums { + cfg.Core.Enums[key] = val + } + if cfg.Core.Consts == nil { + cfg.Core.Consts = make(map[string]any) + } + for key, val := range coreData.Consts { + cfg.Core.Consts[key] = val + } + }) +} + +func readModels(path string, cfg *UserConfig) error { + modelsPath := filepath.Join(path, "policies", "models") + entries, err := os.ReadDir(modelsPath) + if err != nil { + return err + } + for _, entry := range entries { + if !entry.IsDir() && isSupportedExt(filepath.Ext(entry.Name())) { + file := filepath.Join(modelsPath, entry.Name()) + var model Model + content, err := os.ReadFile(file) + if err != nil { + return err + } + err = unmarshalConfig(content, file, &model) + if err != nil { + return err + } + fileName := strings.TrimSuffix(entry.Name(), filepath.Ext(entry.Name())) + if model.Name == "" { + model.Name = fileName + } + if cfg.GetModel(model.Name) == nil { + cfg.Policy.Models = append(cfg.Policy.Models, model) + } + } + } + return nil +} + +func readHandlers(path string, cfg *UserConfig) error { + modelsPath := filepath.Join(path, "policies", "handlers") + entries, err := os.ReadDir(modelsPath) + if err != nil { + return err + } + for _, entry := range entries { + if !entry.IsDir() && isSupportedExt(filepath.Ext(entry.Name())) { + file := filepath.Join(modelsPath, entry.Name()) + var handler Handler + content, err := os.ReadFile(file) + if err != nil { + return err + } + err = unmarshalConfig(content, file, &handler) + if err != nil { + return err + } + cfg.Policy.Handlers = append(cfg.Policy.Handlers, handler) + } + } + return nil +} + +func readApis(path string, cfg *UserConfig) error { + modelsPath := filepath.Join(path, "policies", "apis") + entries, err := os.ReadDir(modelsPath) + if err != nil { + return err + } + for _, entry := range entries { + if !entry.IsDir() && isSupportedExt(filepath.Ext(entry.Name())) { + file := filepath.Join(modelsPath, entry.Name()) + var api Api + content, err := os.ReadFile(file) + if err != nil { + return err + } + err = unmarshalConfig(content, file, &api) + if err != nil { + return err + } + for i, route := range api.Routes { + if route.HandlerKey != "" { + if handler := cfg.GetHandler(route.HandlerKey); handler != nil { + route.Handler = *handler + api.Routes[i] = route + } + } + } + cfg.Policy.Web.Apis = append(cfg.Policy.Web.Apis, api) + } + } + return nil +} + +func readBackgroundTasks(path string, cfg *UserConfig) error { + modelsPath := filepath.Join(path, "policies", "background") + entries, err := os.ReadDir(modelsPath) + if err != nil { + return err + } + for _, entry := range entries { + if !entry.IsDir() && isSupportedExt(filepath.Ext(entry.Name())) { + file := filepath.Join(modelsPath, entry.Name()) + var background BackgroundHandler + content, err := os.ReadFile(file) + if err != nil { + return err + } + err = unmarshalConfig(content, file, &background) + if err != nil { + return err + } + cfg.Policy.BackgroundHandlers = append(cfg.Policy.BackgroundHandlers, &background) + } + } + return nil +} + +func readRenderer(path string, cfg *UserConfig) error { + modelsPath := filepath.Join(path, "policies", "renderer") + entries, err := os.ReadDir(modelsPath) + if err != nil { + return err + } + for _, entry := range entries { + if !entry.IsDir() && isSupportedExt(filepath.Ext(entry.Name())) { + file := filepath.Join(modelsPath, entry.Name()) + var background RenderConfig + content, err := os.ReadFile(file) + if err != nil { + return err + } + err = unmarshalConfig(content, file, &background) + if err != nil { + return err + } + cfg.Policy.Web.Render = append(cfg.Policy.Web.Render, &background) + } + } + return nil +} + +func readWeb(path string, cfg *UserConfig) error { + path = filepath.Join(path, "policies", "web") + return readConfigFile(path, func(data Web) { + cfg.Policy.Web = data + }) +} + +func isSupportedExt(ext string) bool { + return slices.Contains([]string{".json", ".yaml"}, ext) +} diff --git a/services/setup.go b/services/setup.go new file mode 100644 index 0000000..35e995b --- /dev/null +++ b/services/setup.go @@ -0,0 +1,118 @@ +package services + +import ( + "errors" + "fmt" + + "github.com/oarkflow/filters" + "github.com/oarkflow/log" + "github.com/oarkflow/mq" + "github.com/oarkflow/mq/dag" +) + +func SetupHandler(handler Handler, brokerAddr string, async ...bool) *dag.DAG { + syncMode := true + if len(async) > 0 { + syncMode = async[0] + } + key := fmt.Sprintf(`%s-%v`, handler.Key, syncMode) + existingDAG := dag.GetDAG(key) + if existingDAG != nil { + return existingDAG + } + flow := dag.NewDAG(handler.Name, handler.Key, nil, mq.WithSyncMode(syncMode), mq.WithBrokerURL(brokerAddr)) + for _, node := range handler.Nodes { + err := prepareNode(flow, node) + if err != nil { + flow.Error = err + return flow + } + } + for _, edge := range handler.Edges { + if edge.Label == "" { + edge.Label = fmt.Sprintf("edge-%s", edge.Source) + } + flow.AddEdge(dag.Simple, edge.Label, edge.Source, edge.Target...) + if flow.Error != nil { + return flow + } + } + for _, edge := range handler.Loops { + if edge.Label == "" { + edge.Label = fmt.Sprintf("loop-%s", edge.Source) + } + flow.AddEdge(dag.Iterator, edge.Label, edge.Source, edge.Target...) + } + err := flow.Validate() + if err != nil { + flow.Error = err + } + dag.AddDAG(key, flow) + return flow +} + +type Filter struct { + Filter *filters.Filter `json:"condition"` + Node string `json:"node"` + ID string `json:"id"` +} + +func prepareNode(flow *dag.DAG, node Node) error { + newHandler := dag.GetHandler(node.Node) + if newHandler == nil { + return errors.New("Handler not found " + node.Node) + } + nodeHandler := newHandler(node.ID) + providers := mapProviders(node.Data.Providers) + switch nodeHandler := nodeHandler.(type) { + case dag.ConditionProcessor: + nodeHandler.SetConfig(dag.Payload{ + Mapping: node.Data.Mapping, + Data: node.Data.AdditionalData, + GeneratedFields: node.Data.GeneratedFields, + Providers: providers, + }) + if s, ok := node.Data.AdditionalData["conditions"]; ok { + var fil map[string]*Filter + err := Map(&fil, s) + if err != nil { + return err + } + condition := make(map[string]string) + conditions := make(map[string]dag.Condition) + for key, cond := range fil { + condition[key] = cond.Node + conditions[key] = cond.Filter + } + flow.AddCondition(node.ID, condition) + nodeHandler.SetConditions(conditions) + } + case dag.Processor: + nodeHandler.SetConfig(dag.Payload{ + Mapping: node.Data.Mapping, + Data: node.Data.AdditionalData, + GeneratedFields: node.Data.GeneratedFields, + Providers: providers, + }) + } + var nodeType dag.NodeType + if nodeHandler.GetType() == "Function" { + nodeType = dag.Function + } else if nodeHandler.GetType() == "Page" { + nodeType = dag.Page + } + if node.Name == "" { + node.Name = node.ID + } + flow.AddNode(nodeType, node.Name, node.ID, nodeHandler, node.FirstNode) + return nil +} + +func mapProviders(dataProviders interface{}) []dag.Provider { + var providers []dag.Provider + err := Map(&providers, dataProviders) + if err != nil { + log.Warn().Err(err).Msg("Unable to map providers") + } + return providers +} diff --git a/services/user_config.go b/services/user_config.go new file mode 100644 index 0000000..4e9e294 --- /dev/null +++ b/services/user_config.go @@ -0,0 +1,430 @@ +package services + +import ( + "github.com/oarkflow/json" + v2 "github.com/oarkflow/jsonschema" + + "github.com/oarkflow/filters" + + "github.com/oarkflow/metadata" +) + +type Storage struct { + Name string `json:"name" yaml:"name"` + Key string `json:"key" yaml:"key"` + Driver string `json:"driver" yaml:"driver"` + Database string `json:"database" yaml:"database"` + Host string `json:"host" yaml:"host"` + Username string `json:"username" yaml:"username"` + Password string `json:"password" yaml:"password"` + Port int `json:"port" yaml:"port"` + IndexCache int `json:"index_cache" yaml:"index_cache"` + BlockCache int `json:"block_cache" yaml:"block_cache"` + InMemory bool `json:"in_memory" yaml:"in_memory"` +} + +type Cache struct { + Name string `json:"name" yaml:"name"` + Key string `json:"key" yaml:"key"` + Driver string `json:"driver" yaml:"driver"` + Database string `json:"database" yaml:"database"` + Host string `json:"host" yaml:"host"` + Username string `json:"username" yaml:"username"` + Password string `json:"password" yaml:"password"` + Prefix string `json:"prefix" yaml:"prefix"` + Port int `json:"port" yaml:"port"` +} + +type Credentials struct { + Databases []metadata.Config `json:"databases" yaml:"databases"` + Storages []Storage `json:"storages" yaml:"storages"` + Caches []Cache `json:"caches" yaml:"caches"` +} + +type Core struct { + Consts map[string]any `json:"consts" yaml:"consts"` + Enums map[string]map[string]any `json:"enums" yaml:"enums"` + Credentials Credentials `json:"credentials" yaml:"credentials"` +} + +type Operation struct { + Role string `json:"role" yaml:"role"` + Actions []string `json:"actions" yaml:"actions"` +} + +type Constraint struct { + Indices []metadata.Indices `json:"indices" yaml:"indices"` + ForeignKeys []metadata.ForeignKey `json:"foreign" yaml:"foreign"` +} + +type Query struct { + File string `json:"file" yaml:"file"` + String string `json:"string" yaml:"string"` +} + +type Model struct { + Name string `json:"name" yaml:"name"` + OldName string `json:"old_name" yaml:"old_name"` + Key string `json:"key" yaml:"key"` + Title string `json:"title" yaml:"title"` + Database string `json:"database" yaml:"database"` + ModelType string `json:"model_type" yaml:"model_type"` + StoreFields []string `json:"store_fields" yaml:"store_fields"` + IndexFields []string `json:"index_fields" yaml:"index_fields"` + Query Query `json:"query" yaml:"query"` + Constraints Constraint `json:"constraints" yaml:"constraints"` + Fields []metadata.Field `json:"fields" yaml:"fields"` + Operations []Operation `json:"operations" yaml:"operations"` + Fulltext bool `json:"fulltext" yaml:"fulltext"` + Files map[string]bool `json:"files" yaml:"files"` + SortField string `json:"sort_field" yaml:"sort_field"` + SortOrder string `json:"sort_order" yaml:"sort_order"` + RestApi bool `json:"rest_api" yaml:"rest_api"` + Update bool `json:"update" yaml:"update"` +} + +type Property struct { + Properties map[string]Property `json:"properties,omitempty" yaml:"properties,omitempty"` + Items *RequestSchema `json:"items,omitempty" yaml:"items,omitempty"` + Type any `json:"type,omitempty" yaml:"type,omitempty"` + Default any `json:"default" yaml:"default"` + In string `json:"in,omitempty" yaml:"in,omitempty"` + AdditionalProperties bool `json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"` +} + +func (p *Property) UnmarshalJSON(data []byte) error { + type T Property + var pr T + err := json.Unmarshal(data, &pr) + if err != nil { + return err + } + p.Properties = pr.Properties + p.Items = pr.Items + p.In = pr.In + p.AdditionalProperties = pr.AdditionalProperties + switch pr.Type.(type) { + case string: + p.Type = pr.Type.(string) + case []any: + var v []string + for _, i := range pr.Type.([]any) { + v = append(v, i.(string)) + } + p.Type = v + } + return nil +} + +type Provider struct { + Mapping map[string]any `json:"mapping,omitempty" yaml:"mapping,omitempty"` + UpdateMapping map[string]any `json:"update_mapping,omitempty" yaml:"update_mapping,omitempty"` + InsertMapping map[string]any `json:"insert_mapping,omitempty" yaml:"insert_mapping,omitempty"` + Defaults map[string]any `json:"defaults,omitempty" yaml:"defaults,omitempty"` + ProviderType string `json:"provider_type,omitempty" yaml:"provider_type,omitempty"` + Database string `json:"database,omitempty" yaml:"database,omitempty"` + Source string `json:"source,omitempty" yaml:"source,omitempty"` +} + +type Data struct { + Mapping map[string]string `json:"mapping,omitempty" yaml:"mapping,omitempty"` + AdditionalData map[string]any `json:"additional_data,omitempty" yaml:"additional_data,omitempty"` + GeneratedFields []string `json:"generated_fields,omitempty" yaml:"generated_fields,omitempty"` + Providers []Provider `json:"providers,omitempty" yaml:"providers,omitempty"` +} + +type Node struct { + Name string `json:"name,omitempty" yaml:"name,omitempty"` + ID string `json:"id" yaml:"id"` + Node string `json:"node" yaml:"node"` + Data Data `json:"data" yaml:"data"` + FirstNode bool `json:"first_node" yaml:"first_node"` +} + +type Loop struct { + Label string `json:"label" yaml:"label"` + Source string `json:"source" yaml:"source"` + Target []string `json:"target" yaml:"target"` +} + +type Edge struct { + Label string `json:"label" yaml:"label"` + Source string `json:"source" yaml:"source"` + Target []string `json:"target" yaml:"target"` +} + +type Branch struct { + ConditionalNodes map[string]string `json:"conditional_nodes" yaml:"conditional_nodes"` + Key string `json:"key" yaml:"key"` +} + +type Handler struct { + Name string `json:"name" yaml:"name"` + Key string `json:"key" yaml:"key"` + Nodes []Node `json:"nodes,omitempty" yaml:"nodes,omitempty"` + Edges []Edge `json:"edges,omitempty" yaml:"edges,omitempty"` + Branches []Branch `json:"branches,omitempty" yaml:"branches,omitempty"` + Loops []Loop `json:"loops,omitempty" yaml:"loops,omitempty"` +} + +type RequestSchema struct { + Properties map[string]Property `json:"properties,omitempty" yaml:"properties,omitempty"` + Items *RequestSchema `json:"items,omitempty" yaml:"items,omitempty"` + Type string `json:"type,omitempty" yaml:"type,omitempty"` + MaxLength int `json:"maxLength,omitempty" yaml:"maxLength,omitempty"` + Required []string `json:"required,omitempty" yaml:"required,omitempty"` + PrimaryKeys []string `json:"primaryKeys,omitempty" yaml:"primaryKeys,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + AdditionalProperties bool `json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"` +} + +type RestrictedFields struct { + Fields []string `json:"fields" yaml:"fields"` + Roles []string `json:"roles" yaml:"roles"` +} + +type Route struct { + Name string `json:"name" yaml:"name"` + Description string `json:"description" yaml:"description"` + Uri string `json:"route_uri" yaml:"route_uri"` + HandlerKey string `json:"handler_key" yaml:"handler_key"` + Method string `json:"route_method" yaml:"route_method"` + Schema []byte `json:"schema" yaml:"schema"` + schema *v2.Schema + Rules map[string]string `json:"rules" yaml:"rules"` + CustomRules []string `json:"custom_rules" yaml:"custom_rules"` + Model string `json:"model" yaml:"model"` + Handler Handler `json:"handler" yaml:"handler"` + Middlewares []Middleware `json:"middlewares" yaml:"middlewares"` + Operation string `json:"operation" yaml:"operation"` + RestrictedFields RestrictedFields `json:"restricted_fields" yaml:"restricted_fields"` +} + +func (r *Route) GetSchema() *v2.Schema { + if r.schema != nil { + return r.schema + } + return nil +} + +func (r *Route) SetSchema(schema *v2.Schema) { + r.schema = schema +} + +type Schedule struct { + Enable bool `json:"enable" yaml:"enable"` + Interval string `json:"interval" yaml:"interval"` +} + +type BackgroundHandler struct { + Queue string `json:"queue" yaml:"queue"` + HandlerKey string `json:"handler_key" yaml:"handler_key"` + Payload json.RawMessage `json:"payload" yaml:"payload"` + Handler Handler `json:"handler" yaml:"handler"` + Schedule *Schedule `json:"schedule" yaml:"schedule"` +} + +type Api struct { + Prefix string `json:"prefix" yaml:"prefix"` + Middlewares []Middleware `json:"middlewares" yaml:"middlewares"` + Routes []*Route `json:"routes" yaml:"routes"` +} + +type AllowedStatus struct { + Source string `json:"source" yaml:"source"` + Target []string `yaml:"target"` +} + +type Transition struct { + Source string `json:"source" yaml:"source"` + Target string `json:"target" yaml:"target"` + Filters map[string]any `json:"filters" yaml:"filters"` + Validators []map[string]any `json:"validators" yaml:"validators"` + Triggers []map[string]any `json:"triggers" yaml:"triggers"` + Actions []map[string]any `json:"actions" yaml:"actions"` +} + +type FlowPipeline struct { + ID string `json:"id" yaml:"id"` + Statuses []string `json:"statuses" yaml:"statuses"` +} + +type Flow struct { + ID string `json:"id" yaml:"id"` + Service string `json:"service" yaml:"service"` + Entity string `json:"entity" yaml:"entity"` + Model string `json:"model" yaml:"model"` + StatusField string `json:"status_field" yaml:"status_field"` + AggregateBy []string `json:"aggregate_by" yaml:"aggregate_by"` + Statuses []string `json:"statuses" yaml:"statuses"` + AllowedStatuses []AllowedStatus `json:"allowed_statuses" yaml:"allowed_statuses"` + Transitions []Transition `json:"transitions" yaml:"transitions"` + Pipelines []FlowPipeline `json:"pipelines" yaml:"pipelines"` +} + +func (f *Flow) GetPipeline(key string) *FlowPipeline { + for _, pipeline := range f.Pipelines { + if pipeline.ID == key { + return &pipeline + } + } + return nil +} + +type Middleware struct { + Name string `json:"name" yaml:"name"` + Options json.RawMessage `json:"options" yaml:"options"` +} + +type Static struct { + Dir string `json:"dir" yaml:"dir"` + Prefix string `json:"prefix" yaml:"prefix"` + Options struct { + ByteRange bool `json:"byte_range" yaml:"byte_range"` + Compress bool `json:"compress" yaml:"compress"` + Browse bool `json:"browse" yaml:"browse"` + IndexFile string `json:"index_file" yaml:"index_file"` + } `json:"options" yaml:"options"` +} + +type RenderConfig struct { + ID string `json:"id" yaml:"id"` + Prefix string `json:"prefix" yaml:"prefix"` + Root string `json:"root" yaml:"root"` + Index string `json:"index" yaml:"index"` + UseIndex bool `json:"use_index" yaml:"use_index"` + Compress bool `json:"compress" yaml:"compress"` + Extension string `json:"extension" yaml:"extension"` +} + +type Web struct { + Prefix string `json:"prefix" yaml:"prefix"` + Static *Static `json:"static" yaml:"static"` + Render []*RenderConfig `json:"render" yaml:"render"` + Middlewares []Middleware `json:"middlewares" yaml:"middlewares"` + Apis []Api `json:"apis" yaml:"apis"` +} + +type Policy struct { + Web Web `json:"web" yaml:"web"` + Models []Model `json:"models" yaml:"models"` + BackgroundHandlers []*BackgroundHandler `json:"background_handlers" yaml:"background_handlers"` + Commands []*GenericCommand `json:"commands"` + Conditions []*filters.Filter `json:"conditions" yaml:"conditions"` + ApplicationRules []*filters.ApplicationRule `json:"application_rules" yaml:"application_rules"` + Handlers []Handler `json:"handlers" yaml:"handlers"` + Flows []Flow `json:"flows" yaml:"flows"` +} + +type UserConfig struct { + Core Core `json:"core" yaml:"core"` + Policy Policy `json:"policies" yaml:"policies"` +} + +func (c *UserConfig) GetModel(source string) *Model { + for _, model := range c.Policy.Models { + if model.Name == source { + return &model + } + } + return nil +} + +func (c *UserConfig) GetRenderConfig(source string) *RenderConfig { + for _, model := range c.Policy.Web.Render { + if model.ID == source { + return model + } + } + return nil +} + +func (c *UserConfig) GetEntities() (entities []string) { + for _, model := range c.Policy.Models { + entities = append(entities, model.Name) + } + return +} + +func (c *UserConfig) GetDatabase(db string) *metadata.Config { + for _, database := range c.Core.Credentials.Databases { + if database.Key == db { + return &database + } + } + return nil +} + +func (c *UserConfig) GetHandler(handlerName string) *Handler { + for _, handler := range c.Policy.Handlers { + if handler.Key == handlerName { + return &handler + } + } + return nil +} + +func (c *UserConfig) GetRoute(name string) *Route { + for _, routes := range c.Policy.Web.Apis { + for _, route := range routes.Routes { + if route.Name == name { + return route + } + } + } + return nil +} + +func (c *UserConfig) GetHandlerList() (handlers []string) { + for _, handler := range c.Policy.Handlers { + handlers = append(handlers, handler.Key) + } + return +} + +func (c *UserConfig) GetSourceDatabase(source string, db ...string) *metadata.Config { + if len(db) > 0 && db[0] != "" { + for _, database := range c.Core.Credentials.Databases { + if database.Key == db[0] { + return &database + } + } + } + model := c.GetModel(source) + if model == nil { + return nil + } + for _, database := range c.Core.Credentials.Databases { + if database.Key == model.Database { + return &database + } + } + return nil +} + +func (c *UserConfig) GetCondition(key string) *filters.Filter { + for _, condition := range c.Policy.Conditions { + if condition.Key == key { + return condition + } + } + return nil +} + +func (c *UserConfig) GetApplicationRule(key string) *filters.ApplicationRule { + for _, applicationRule := range c.Policy.ApplicationRules { + if applicationRule.Key == key { + return applicationRule + } + } + return nil +} + +func (c *UserConfig) GetFlow(key string) *Flow { + for _, flow := range c.Policy.Flows { + if flow.ID == key { + return &flow + } + } + return nil +}