diff --git a/.task/checksum/generate b/.task/checksum/generate index d47083b..9e85368 100644 --- a/.task/checksum/generate +++ b/.task/checksum/generate @@ -1 +1 @@ -3c1808b7a88ab24b1cacf9a132073105 +3cf236a901d03e42352790df844d58c5 diff --git a/Taskfile.yml b/Taskfile.yml index a4b7fa1..44d99d8 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -43,14 +43,12 @@ tasks: - "$GOBIN/golangci-lint run ./..." deps: - generate - - mocks test: run: once cmds: - go test ./internal/... deps: - generate - - mocks build_web: cmds: - go build -o $GOBIN/kuriousweb -v -ldflags '{{.LDFLAGS}}' cmd/kuriweb/*.go diff --git a/cmd/cli/main.go b/cmd/cli/main.go deleted file mode 100644 index 4f5f4e5..0000000 --- a/cmd/cli/main.go +++ /dev/null @@ -1,5 +0,0 @@ -package main - -func main() { - println("oh well") -} diff --git a/cmd/kuriweb/config.go b/cmd/kuriweb/config.go index bd9f83b..afd3a71 100644 --- a/cmd/kuriweb/config.go +++ b/cmd/kuriweb/config.go @@ -9,9 +9,12 @@ import ( ) type Config struct { - Log config.Log `json:"log"` - YDB config.YDB `json:"ydb"` - HTTP config.HTTP `json:"http"` + Log config.Log `json:"log"` + YDB config.YDB `json:"ydb"` + Sqlite config.Sqlite `json:"sqlite"` + HTTP config.HTTP `json:"http"` + Tracing config.Trace `json:"tracing"` + DBEngine string `json:"db_engine"` } func readFromFile(path string, defaultConfigF func() Config) (Config, error) { diff --git a/cmd/kuriweb/http.go b/cmd/kuriweb/http.go index 08e2a07..6814419 100644 --- a/cmd/kuriweb/http.go +++ b/cmd/kuriweb/http.go @@ -11,8 +11,12 @@ import ( "git.loyso.art/frx/kurious/internal/common/generator" "git.loyso.art/frx/kurious/internal/common/xcontext" xhttp "git.loyso.art/frx/kurious/internal/kurious/ports/http" + "git.loyso.art/frx/kurious/pkg/xdefault" "github.com/gorilla/mux" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" ) func makePathTemplate(params ...string) string { @@ -29,50 +33,32 @@ func makePathTemplate(params ...string) string { return sb.String() } -func setupHTTPWithTempl(srv xhttp.Server, router *mux.Router, log *slog.Logger) { - coursesRouter := router.PathPrefix("/courses").Subrouter().StrictSlash(true) +func setupCoursesHTTP(srv xhttp.Server, router *mux.Router, _ *slog.Logger) { + coursesAPI := srv.Courses() - coursesAPI := srv.CoursesByTempl() - - coursesRouter.HandleFunc("/", coursesAPI.List).Methods(http.MethodGet) - coursesListLearningOnlyPath := makePathTemplate(xhttp.LearningTypePathParam) - coursesRouter.HandleFunc(coursesListLearningOnlyPath, coursesAPI.List).Methods(http.MethodGet) - coursesListFullPath := makePathTemplate(xhttp.LearningTypePathParam, xhttp.ThematicTypePathParam) - coursesRouter.HandleFunc(coursesListFullPath, coursesAPI.List).Methods(http.MethodGet) -} - -func setupHTTPWithGoTemplates(srv xhttp.Server, router *mux.Router, log *slog.Logger) { - coursesAPI := srv.Courses(true) + router.Handle("/", http.RedirectHandler("/courses", http.StatusPermanentRedirect)) coursesRouter := router.PathPrefix("/courses").Subrouter().StrictSlash(true) - coursesRouter.HandleFunc("/", coursesAPI.List).Methods(http.MethodGet) - coursesListLearningOnlyPath := makePathTemplate(xhttp.LearningTypePathParam) - coursesRouter.HandleFunc(coursesListLearningOnlyPath, coursesAPI.List).Methods(http.MethodGet) - coursesListFullPath := makePathTemplate(xhttp.LearningTypePathParam, xhttp.ThematicTypePathParam) - coursesRouter.HandleFunc(coursesListFullPath, coursesAPI.List).Methods(http.MethodGet) - courseRouter := router.PathPrefix("/course").PathPrefix("/{course_id}").Subrouter() - courseRouter.HandleFunc("/", coursesAPI.Get).Methods(http.MethodGet) - courseRouter.HandleFunc("/short", coursesAPI.GetShort).Methods(http.MethodGet) - courseRouter.HandleFunc("/editdesc", coursesAPI.RenderEditDescription).Methods(http.MethodGet) - courseRouter.HandleFunc("/description", coursesAPI.UpdateCourseDescription).Methods(http.MethodPut) + coursesListLearningOnlyPath := makePathTemplate(xhttp.LearningTypePathParam) + coursesListFullPath := makePathTemplate(xhttp.LearningTypePathParam, xhttp.ThematicTypePathParam) + + muxHandleFunc(coursesRouter, "/", coursesAPI.Index).Methods(http.MethodGet) + muxHandleFunc(coursesRouter, coursesListLearningOnlyPath, coursesAPI.List).Methods(http.MethodGet) + muxHandleFunc(coursesRouter, coursesListFullPath, coursesAPI.List).Methods(http.MethodGet) } func setupHTTP(cfg config.HTTP, srv xhttp.Server, log *slog.Logger) *http.Server { router := mux.NewRouter() - router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - }) + router.Use( + middlewareCustomWriterInjector(), + mux.CORSMethodMiddleware(router), + middlewareLogger(log), + middlewareTrace(), + ) - router.Use(mux.CORSMethodMiddleware(router)) - router.Use(middlewareLogger(log, cfg.Engine)) - - if cfg.Engine == "templ" { - setupHTTPWithTempl(srv, router, log) - } else { - setupHTTPWithGoTemplates(srv, router, log) - } + setupCoursesHTTP(srv, router, log) if cfg.MountLive { fs := http.FileServer(http.Dir("./assets/kurious/static/")) @@ -103,7 +89,7 @@ func setupHTTP(cfg config.HTTP, srv xhttp.Server, log *slog.Logger) *http.Server } } else { fs := kurious.AsHTTPFileHandler() - router.PathPrefix("/").Handler(fs).Methods(http.MethodGet) + router.PathPrefix("/*").Handler(fs).Methods(http.MethodGet) } return &http.Server{ @@ -112,7 +98,46 @@ func setupHTTP(cfg config.HTTP, srv xhttp.Server, log *slog.Logger) *http.Server } } -func middlewareLogger(log *slog.Logger, engine string) mux.MiddlewareFunc { +func middlewareCustomWriterInjector() mux.MiddlewareFunc { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + wr := wrapWithCustomWriter(w) + next.ServeHTTP(wr, r) + }) + } +} + +func middlewareTrace() mux.MiddlewareFunc { + reqidAttr := attribute.Key("http.request_id") + statusAttr := attribute.Key("http.status_code") + payloadAttr := attribute.Key("http.payload_size") + + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + reqid := xcontext.GetRequestID(ctx) + + var span trace.Span + ctx, span = webtracer.Start(ctx, r.URL.String(), trace.WithAttributes(reqidAttr.String(reqid))) + defer span.End() + + next.ServeHTTP(w, r.WithContext(ctx)) + + if wr, ok := w.(*customResponseWriter); ok { + statusCode := xdefault.WithFallback(wr.statusCode, http.StatusOK) + span.SetAttributes( + statusAttr.Int(statusCode), + payloadAttr.Int(wr.wroteBytes), + ) + if statusCode > 399 { + span.SetStatus(codes.Error, "error during request") + } + } + }) + } +} + +func middlewareLogger(log *slog.Logger) mux.MiddlewareFunc { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -120,11 +145,12 @@ func middlewareLogger(log *slog.Logger, engine string) mux.MiddlewareFunc { if requestID == "" { requestID = generator.RandomInt64ID() } + ctx = xcontext.WithLogFields( ctx, slog.String("request_id", requestID), - slog.String("engine", engine), ) + ctx = xcontext.WithRequestID(ctx, requestID) xcontext.LogInfo( ctx, log, "incoming request", @@ -134,12 +160,45 @@ func middlewareLogger(log *slog.Logger, engine string) mux.MiddlewareFunc { start := time.Now() next.ServeHTTP(w, r.WithContext(ctx)) - elapsed := time.Since(start).Truncate(time.Millisecond) + elapsed := slog.Duration("elapsed", time.Since(start).Truncate(time.Millisecond)) - xcontext.LogInfo( - ctx, log, "request processed", - slog.Duration("elapsed", elapsed), - ) + logfields := make([]slog.Attr, 0, 3) + logfields = append(logfields, elapsed) + if wr, ok := w.(*customResponseWriter); ok { + statusCode := xdefault.WithFallback(wr.statusCode, http.StatusOK) + logfields = append( + logfields, + slog.Int("status_code", statusCode), + slog.Int("bytes_wrote", wr.wroteBytes), + ) + } + + xcontext.LogInfo(ctx, log, "request processed", logfields...) }) } } + +func wrapWithCustomWriter(origin http.ResponseWriter) *customResponseWriter { + return &customResponseWriter{ + ResponseWriter: origin, + } +} + +type customResponseWriter struct { + http.ResponseWriter + + statusCode int + wroteBytes int +} + +func (w *customResponseWriter) WriteHeader(statusCode int) { + w.statusCode = statusCode + w.ResponseWriter.WriteHeader(statusCode) +} + +func (w *customResponseWriter) Write(data []byte) (n int, err error) { + n, err = w.ResponseWriter.Write(data) + w.wroteBytes += n + + return n, err +} diff --git a/cmd/kuriweb/main.go b/cmd/kuriweb/main.go index 51b1cac..0c131d0 100644 --- a/cmd/kuriweb/main.go +++ b/cmd/kuriweb/main.go @@ -46,6 +46,17 @@ func app(ctx context.Context) error { log := config.NewSLogger(cfg.Log) + shutdownOtel, err := setupOtelSDK(ctx, cfg.Tracing) + if err != nil { + return fmt.Errorf("setting up otel sdk: %w", err) + } + defer func() { + err := shutdownOtel(ctx) + if err != nil { + xcontext.LogWithError(ctx, log, err, "shutting down sdk") + } + }() + sravniClient, err := sravni.NewClient(ctx, log, false) if err != nil { return fmt.Errorf("unable to make new sravni client: %w", err) @@ -71,9 +82,20 @@ func app(ctx context.Context) error { mapper := adapters.NewMemoryMapper(courseThematcisMapped, learningTypeMapped) + var dbengine service.RepositoryEngine + switch cfg.DBEngine { + case "ydb": + dbengine = service.RepositoryEngineYDB + case "sqlite": + dbengine = service.RepositoryEngineSqlite + default: + dbengine = service.RepositoryEngineUnknown + } app, err := service.NewApplication(ctx, service.ApplicationConfig{ LogConfig: cfg.Log, YDB: cfg.YDB, + Sqlite: cfg.Sqlite, + Engine: dbengine, }, mapper) if err != nil { return fmt.Errorf("making new application: %w", err) @@ -113,6 +135,11 @@ func app(ctx context.Context) error { xcontext.LogInfo(ctx, log, "server closed successfuly") + err = shutdownOtel(sdctx) + if err != nil { + return fmt.Errorf("shutting down sdk: %w", err) + } + return nil }) diff --git a/cmd/kuriweb/trace.go b/cmd/kuriweb/trace.go new file mode 100644 index 0000000..c1ac2e9 --- /dev/null +++ b/cmd/kuriweb/trace.go @@ -0,0 +1,132 @@ +package main + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "git.loyso.art/frx/kurious/internal/common/config" + "git.loyso.art/frx/kurious/pkg/xdefault" + + "github.com/gorilla/mux" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" + "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/resource" + "go.opentelemetry.io/otel/sdk/trace" +) + +var webtracer = otel.Tracer("kuriweb") + +type shutdownFunc func(context.Context) error + +func setupOtelSDK(ctx context.Context, cfg config.Trace) (shutdown shutdownFunc, err error) { + var shutdownFuncs []shutdownFunc + + shutdown = func(ctx context.Context) error { + var err error + for _, f := range shutdownFuncs { + err = errors.Join(err, f(ctx)) + } + shutdownFuncs = nil + return err + } + + handleError := func(inErr error) error { + err = errors.Join(inErr, shutdown(ctx)) + return err + } + + prop := newPropagator() + otel.SetTextMapPropagator(prop) + + tracerProvider, err := newTraceProvider(ctx, cfg.Endpoint, cfg.LicenseKey) + if err != nil { + return nil, handleError(err) + } + shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown) + otel.SetTracerProvider(tracerProvider) + + meterProvider, err := newMeterProvider() + if err != nil { + return nil, handleError(err) + } + shutdownFuncs = append(shutdownFuncs, meterProvider.Shutdown) + otel.SetMeterProvider(meterProvider) + + return shutdown, nil +} + +func newPropagator() propagation.TextMapPropagator { + return propagation.NewCompositeTextMapPropagator( + propagation.TraceContext{}, + propagation.Baggage{}, + ) +} + +const defaultNewRelicEndpoint = "otlp.eu01.nr-data.net:443" + +func newTraceProvider(ctx context.Context, endpoint, licensekey string) (traceProvider *trace.TracerProvider, err error) { + opts := make([]trace.TracerProviderOption, 0, 2) + opts = append( + opts, + trace.WithSampler(trace.AlwaysSample()), + trace.WithResource(resource.Default()), + ) + + if licensekey != "" { + endpoint = xdefault.WithFallback(endpoint, defaultNewRelicEndpoint) + client, err := otlptracegrpc.New( + ctx, + otlptracegrpc.WithEndpoint(endpoint), + otlptracegrpc.WithHeaders(map[string]string{ + "api-key": licensekey, + }), + ) + if err != nil { + return nil, fmt.Errorf("making grpc client: %w", err) + } + + opts = append(opts, trace.WithBatcher(client, trace.WithBatchTimeout(time.Second*10))) + } else { + traceExporter, err := stdouttrace.New( + stdouttrace.WithPrettyPrint()) + if err != nil { + return nil, err + } + + opts = append( + opts, + trace.WithBatcher(traceExporter, trace.WithBatchTimeout(time.Second*5)), + ) + } + + traceProvider = trace.NewTracerProvider(opts...) + + return traceProvider, nil +} + +func newMeterProvider() (*metric.MeterProvider, error) { + metricExporter, err := stdoutmetric.New() + if err != nil { + return nil, err + } + + meterProvider := metric.NewMeterProvider( + metric.WithReader(metric.NewPeriodicReader(metricExporter, + // Default is 1m. Set to 3s for demonstrative purposes. + metric.WithInterval(60*time.Second))), + ) + return meterProvider, nil +} + +func muxHandleFunc(router *mux.Router, path string, hf http.HandlerFunc) *mux.Route { + h := otelhttp.WithRouteTag(path, hf) + return router.Handle(path, h) +} diff --git a/go.mod b/go.mod index 5c57410..f650aa3 100644 --- a/go.mod +++ b/go.mod @@ -12,18 +12,31 @@ require ( github.com/teris-io/cli v1.0.1 github.com/ydb-platform/ydb-go-sdk/v3 v3.54.2 github.com/ydb-platform/ydb-go-yc v0.12.1 - golang.org/x/net v0.18.0 - golang.org/x/sync v0.5.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 + go.opentelemetry.io/otel v1.24.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.24.0 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 + go.opentelemetry.io/otel/sdk v1.24.0 + go.opentelemetry.io/otel/sdk/metric v1.24.0 + go.opentelemetry.io/otel/trace v1.24.0 + golang.org/x/net v0.22.0 + golang.org/x/sync v0.6.0 golang.org/x/time v0.5.0 modernc.org/sqlite v1.29.3 ) require ( + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/google/uuid v1.4.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/jonboulle/clockwork v0.4.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -34,13 +47,15 @@ require ( github.com/yandex-cloud/go-genproto v0.0.0-20231120081503-a21e9fe75162 // indirect github.com/ydb-platform/ydb-go-genproto v0.0.0-20231012155159-f85a672542fd // indirect github.com/ydb-platform/ydb-go-yc-metadata v0.6.1 // indirect - golang.org/x/sys v0.16.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/proto/otlp v1.1.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect - google.golang.org/grpc v1.59.0 // indirect - google.golang.org/protobuf v1.31.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect + google.golang.org/grpc v1.62.1 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect modernc.org/libc v1.41.0 // indirect diff --git a/go.sum b/go.sum index a5de74e..173981b 100644 --- a/go.sum +++ b/go.sum @@ -527,6 +527,8 @@ github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0I github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= @@ -573,6 +575,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -586,6 +590,11 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-resty/resty/v2 v2.10.0 h1:Qla4W/+TMmv0fOeeRqzEpXPLfTUnR5HZ1+lGs+CkiCo= @@ -631,8 +640,9 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -680,8 +690,8 @@ github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S3 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= -github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= @@ -704,6 +714,8 @@ github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWS github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= @@ -728,8 +740,9 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02 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.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +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/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -774,8 +787,9 @@ github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -800,8 +814,6 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/teris-io/cli v1.0.1 h1:J6jnVHC552uqx7zT+Ux0++tIvLmJQULqxVhCid2u/Gk= github.com/teris-io/cli v1.0.1/go.mod h1:V9nVD5aZ873RU/tQXLSXO8FieVPQhQvuNohsdsKXsGw= -github.com/vektra/mockery/v2 v2.42.1 h1:z7l3O4jCzRZat3rm9jpHc8lzpR8bs1VBii7bYtl3KQs= -github.com/vektra/mockery/v2 v2.42.1/go.mod h1:XNTE9RIu3deGAGQRVjP1VZxGpQNm0YedZx4oDs3prr8= github.com/yandex-cloud/go-genproto v0.0.0-20211115083454-9ca41db5ed9e/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE= github.com/yandex-cloud/go-genproto v0.0.0-20231120081503-a21e9fe75162 h1:xCzizLC090MiLWEV3aziL5YIKrSVTRXX2DXlRGeQ6sA= github.com/yandex-cloud/go-genproto v0.0.0-20231120081503-a21e9fe75162/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE= @@ -834,9 +846,33 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.24.0 h1:JYE2HM7pZbOt5Jhk8ndWZTUWYOVift2cHjXVMkPdmdc= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.24.0/go.mod h1:yMb/8c6hVsnma0RpsBMNo0fEiQKeclawtgaIaOp2MLY= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 h1:s0PHtIkN+3xrbDOpt2M8OTG92cWqUESvzh2MxiR5xY8= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0/go.mod h1:hZlFbDbRt++MMPCCfSJfmhkGIWnX1h3XjkfxZUjLrIA= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= +go.opentelemetry.io/otel/sdk/metric v1.24.0 h1:yyMQrPzF+k88/DbH7o4FMAs80puqd+9osbiBrJrz/w8= +go.opentelemetry.io/otel/sdk/metric v1.24.0/go.mod h1:I6Y5FjH6rvEnTTAYQz3Mmv2kl6Ek5IIrmwTLqMrrOE0= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= +go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -964,8 +1000,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1010,8 +1046,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1090,8 +1126,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -1394,12 +1430,10 @@ google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA= google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= -google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f h1:Vn+VyHU5guc9KjB5KrjI2q0wCOWEOIh0OEsleqakHJg= -google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f/go.mod h1:nWSwAFPb+qfNJXsoeO3Io7zf4tMSfN8EA8RlDA04GhY= -google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f h1:2yNACc1O40tTnrsbk9Cv6oxiW8pxI/pXj0wRtdlYmgY= -google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= +google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda h1:b6F6WIV4xHHD0FA4oIyzU6mHWg2WI2X1RBehwa5QN38= +google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda/go.mod h1:AHcE/gZH76Bk/ROZhQphlRoWo5xKDEtz3eVEO1LfA8c= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1440,8 +1474,8 @@ google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsA google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= +google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -1459,8 +1493,8 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/htmlexamples/core.html b/htmlexamples/core.html index 99784c7..4f04308 100644 --- a/htmlexamples/core.html +++ b/htmlexamples/core.html @@ -1,208 +1,152 @@ - - Test page - - - - - - - -
- + + Test page + + + + + -
-
-
- -
-
- Lorem, ipsum dolor sit amet consectetur - adipisicing elit. -
-

- Lorem ipsum dolor sit amet consectetur - adipisicing elit. Enim omnis vero, reiciendis - obcaecati perferendis excepturi nostrum nobis - itaque modi dignissimos ... -

- -
- - -
-
- -
-
- -
-
- Lorem, ipsum dolor sit amet consectetur - adipisicing elit. -
-

- Lorem ipsum dolor sit amet consectetur - adipisicing elit. Enim omnis vero, reiciendis - obcaecati perferendis excepturi nostrum nobis - itaque modi dignissimos ... -

- Go somewhere -
- -
-
- -
-
- -
-
- Lorem, ipsum dolor sit amet consectetur - adipisicing elit. -
-

- Lorem ipsum dolor sit amet consectetur - adipisicing elit. Enim omnis vero, reiciendis - obcaecati perferendis excepturi nostrum nobis - itaque modi dignissimos ... -

- Go somewhere -
- -
-
- -
-
- -
-
- Lorem, ipsum dolor sit amet consectetur - adipisicing elit. -
-

- Lorem ipsum dolor sit amet consectetur - adipisicing elit. Enim omnis vero, reiciendis - obcaecati perferendis excepturi nostrum nobis - itaque modi dignissimos ... -

- Go somewhere -
- -
-
- -
-
- -
-
- Lorem, ipsum dolor sit amet consectetur - adipisicing elit. -
-

- Lorem ipsum dolor sit amet consectetur - adipisicing elit. Enim omnis vero, reiciendis - obcaecati perferendis excepturi nostrum nobis - itaque modi dignissimos ... -

- Go somewhere -
- -
-
-
+ + + +
+ + +
+
+
+ +
+
+ Lorem, ipsum dolor sit amet consectetur adipisicing elit. +
+

+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Enim + omnis vero, reiciendis obcaecati perferendis excepturi nostrum + nobis itaque modi dignissimos ... +

+ +
+ + +
+
+ +
+
+ +
+
+ Lorem, ipsum dolor sit amet consectetur adipisicing elit. +
+

+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Enim + omnis vero, reiciendis obcaecati perferendis excepturi nostrum + nobis itaque modi dignissimos ... +

+ Go somewhere +
+ +
+
+ +
+
+ +
+
+ Lorem, ipsum dolor sit amet consectetur adipisicing elit. +
+

+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Enim + omnis vero, reiciendis obcaecati perferendis excepturi nostrum + nobis itaque modi dignissimos ... +

+ Go somewhere +
+ +
+
+ +
+
+ +
+
+ Lorem, ipsum dolor sit amet consectetur adipisicing elit. +
+

+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Enim + omnis vero, reiciendis obcaecati perferendis excepturi nostrum + nobis itaque modi dignissimos ... +

+ Go somewhere +
+ +
+
+ +
+
+ +
+
+ Lorem, ipsum dolor sit amet consectetur adipisicing elit. +
+

+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Enim + omnis vero, reiciendis obcaecati perferendis excepturi nostrum + nobis itaque modi dignissimos ... +

+ Go somewhere +
+ +
+
+
+
+ + diff --git a/htmlexamples/courses.html b/htmlexamples/courses.html index 4508223..ae1dfdb 100644 --- a/htmlexamples/courses.html +++ b/htmlexamples/courses.html @@ -1,206 +1,142 @@ - - Test page - - - - - - - -
- -
+ + Test page + + + + + + -
-
- -
- -
-
-
- Filter categories - - - - - -
-
-
- -
-

Languages

-

- A languages category provides all courses to help learn - language -

- -
-
- -
- -
- -
- -
-
Promocodes
-
-
- -
-

Japanese

-

Looking for a course to learn japanese language?

- -
-
-
- ... -
-
- Card title with a long naming -
-
- Open > - 500$ -
-
-
-
-
-
- -
-
- -
-
-

(c) All right reserved

-
-
+ +
+ +
+ +
+
+ +
+ +
+
+
+ Filter categories + + + + + +
+
+
+ +
+

Languages

+

A languages category provides all courses to help learn language

+ +
+
+ +
+ +
+ +
+ +
+
Promocodes
+
+
+ +
+

Japanese

+

Looking for a course to learn japanese language?

+ +
+
+
+ ... +
+
Card title with a long naming
+
+ Open > + 500$ +
+
+
+
+
+
+ +
+
+ +
+
+

(c) All right reserved

+
+
+
+ + diff --git a/htmlexamples/index.html b/htmlexamples/index.html index f3b7fe1..c67f8a1 100644 --- a/htmlexamples/index.html +++ b/htmlexamples/index.html @@ -1,126 +1,183 @@ - - Test page - - - - - - - -
- -
+ + Test page + + + + + + -
-
-

Some header about courses

-
- -
-
-
-
    -

    Category 1

    -
  • -
    item
    -
  • -
  • -
    item
    -
  • -
  • -
    item
    -
  • -
-
-
-
-
-
    -

    Category 2

    -
  • -
    item
    -
  • -
  • -
    item
    -
  • -
  • -
    item
    -
  • -
-
-
-
-
-
    -

    Category 3

    -
  • -
    item
    -
  • -
  • -
    item
    -
  • -
  • -
    item
    -
  • -
-
-
-
+ +
+ +
+ +
+
+

Here you can find course for any taste

+
+ +
+ +
+ +
+
+
+
Programming
+
+

In this category you can find courses of types such as

+

web-development, backend development, frontend developent

+

This category contains 128 courses.

+
+ + Open + + 128 items +
+
+
+
+ + +
+
+
+
Programming
+
+

In this category you can find courses of types such as

+

web-development, backend development, frontend developent

+

This category contains 128 courses.

+
+ + Open + + 128 items +
+
+
+
+ + +
+
+
+
Programming
+
+

In this category you can find courses of types such as

+

web-development, backend development, frontend developent

+

This category contains 128 courses.

+
+ + Open + + 128 items +
+
+
+
+ + +
+
+
+
Programming
+
+

In this category you can find courses of types such as

+

web-development, backend development, frontend developent

+

This category contains 128 courses.

+
+ + Open + + 128 items +
+
+
+
+ +
+ +
+ +
+
+
+
    +

    Category 1

    +
  • +
    item
    +
  • +
  • +
    item
    +
  • +
  • +
    item
    +
  • +
+
+
+
+
+
    +

    Category 2

    +
  • +
    item
    +
  • +
  • +
    item
    +
  • +
  • +
    item
    +
  • +
+
+
+
+
+
    +

    Category 3

    +
  • +
    item
    +
  • +
  • +
    item
    +
  • +
  • +
    item
    +
  • +
+
+
+
+
+ + diff --git a/internal/common/config/http.go b/internal/common/config/http.go index fff7915..ad36b8e 100644 --- a/internal/common/config/http.go +++ b/internal/common/config/http.go @@ -3,5 +3,4 @@ package config type HTTP struct { ListenAddr string `json:"listen_addr"` MountLive bool `json:"mount_live"` - Engine string `json:"engine"` } diff --git a/internal/common/config/trace.go b/internal/common/config/trace.go new file mode 100644 index 0000000..5749c84 --- /dev/null +++ b/internal/common/config/trace.go @@ -0,0 +1,6 @@ +package config + +type Trace struct { + Endpoint string `json:"endpoint"` + LicenseKey string `json:"license_key"` +} diff --git a/internal/common/decorator/logging.go b/internal/common/decorator/logging.go index f3fd05d..28d72e7 100644 --- a/internal/common/decorator/logging.go +++ b/internal/common/decorator/logging.go @@ -2,11 +2,24 @@ package decorator import ( "context" + "encoding/json" "fmt" "log/slog" + "strings" "time" "git.loyso.art/frx/kurious/internal/common/xcontext" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" +) + +var ( + commandAttribute = attribute.Key("command_name") + queryAttribute = attribute.Key("query_name") + argsAttribute = attribute.Key("args") + + apiTracer = otel.Tracer("cq") ) type commandLoggingDecorator[T any] struct { @@ -18,6 +31,17 @@ func (c commandLoggingDecorator[T]) Handle(ctx context.Context, cmd T) (err erro handlerName := getTypeName[T]() ctx = xcontext.WithLogFields(ctx, slog.String("handler", handlerName)) + + var argsBuilder strings.Builder + _ = json.NewEncoder(&argsBuilder).Encode(cmd) + + var span trace.Span + ctx, span = apiTracer.Start(ctx, handlerName) + span.SetAttributes( + commandAttribute.String(handlerName), + argsAttribute.String(argsBuilder.String()), + ) + xcontext.LogDebug(ctx, c.log, "executing command") start := time.Now() @@ -27,7 +51,9 @@ func (c commandLoggingDecorator[T]) Handle(ctx context.Context, cmd T) (err erro xcontext.LogInfo(ctx, c.log, "command executed successfuly", elapsed) } else { xcontext.LogError(ctx, c.log, "command execution failed", elapsed, slog.Any("err", err)) + span.RecordError(err) } + span.End() }() return c.base.Handle(ctx, cmd) @@ -41,6 +67,17 @@ type queryLoggingDecorator[Q, U any] struct { func (q queryLoggingDecorator[Q, U]) Handle(ctx context.Context, query Q) (entity U, err error) { handlerName := getTypeName[Q]() ctx = xcontext.WithLogFields(ctx, slog.String("handler", handlerName)) + + var argsBuilder strings.Builder + _ = json.NewEncoder(&argsBuilder).Encode(query) + + var span trace.Span + ctx, span = apiTracer.Start(ctx, handlerName) + span.SetAttributes( + queryAttribute.String(handlerName), + argsAttribute.String(argsBuilder.String()), + ) + xcontext.LogDebug(ctx, q.log, "executing command") start := time.Now() @@ -50,7 +87,10 @@ func (q queryLoggingDecorator[Q, U]) Handle(ctx context.Context, query Q) (entit xcontext.LogInfo(ctx, q.log, "command executed successfuly", elapsed) } else { xcontext.LogError(ctx, q.log, "command execution failed", elapsed, slog.Any("err", err)) + span.RecordError(err) } + now := time.Now() + span.End(trace.WithTimestamp(now)) }() return q.base.Handle(ctx, query) diff --git a/internal/common/xcontext/log.go b/internal/common/xcontext/log.go index 35dd992..4452167 100644 --- a/internal/common/xcontext/log.go +++ b/internal/common/xcontext/log.go @@ -6,6 +6,16 @@ import ( ) type ctxLogKey struct{} +type ctxRequestID struct{} + +func WithRequestID(ctx context.Context, requestID string) context.Context { + return context.WithValue(ctx, ctxRequestID{}, requestID) +} + +func GetRequestID(ctx context.Context) string { + reqid, _ := ctx.Value(ctxRequestID{}).(string) + return reqid +} type ctxLogAttrStore struct { attrs []slog.Attr diff --git a/internal/common/xslices/foreach.go b/internal/common/xslices/foreach.go index 2eb8096..41dfb27 100644 --- a/internal/common/xslices/foreach.go +++ b/internal/common/xslices/foreach.go @@ -1,5 +1,10 @@ package xslices +import ( + "crypto/rand" + "math/big" +) + func ForEach[T any](items []T, f func(T)) { for _, item := range items { f(item) @@ -13,3 +18,12 @@ func AsMap[T any, U comparable](items []T, f func(T) U) map[U]struct{} { }) return out } + +func Shuffle[T any](items []T) { + maxnum := big.NewInt(int64(len(items))) + for i := range items { + swapWith, _ := rand.Int(rand.Reader, maxnum) + swapWithIdx := int(swapWith.Int64()) + items[i], items[swapWithIdx] = items[swapWithIdx], items[i] + } +} diff --git a/internal/kurious/ports/http/bootstrap/common.templ b/internal/kurious/ports/http/bootstrap/common.templ index 1f77076..d613ebf 100644 --- a/internal/kurious/ports/http/bootstrap/common.templ +++ b/internal/kurious/ports/http/bootstrap/common.templ @@ -17,6 +17,6 @@ templ buttonRedirect(id, title string, linkTo string) { script onclickRedirect(id, to string) { document.getElementById(id).onclick = () => { - location.href = to + location.href = to } } diff --git a/internal/kurious/ports/http/bootstrap/common_templ.go b/internal/kurious/ports/http/bootstrap/common_templ.go index 9ce5bb5..0a6b61e 100644 --- a/internal/kurious/ports/http/bootstrap/common_templ.go +++ b/internal/kurious/ports/http/bootstrap/common_templ.go @@ -106,11 +106,11 @@ func buttonRedirect(id, title string, linkTo string) templ.Component { func onclickRedirect(id, to string) templ.ComponentScript { return templ.ComponentScript{ - Name: `__templ_onclickRedirect_47ae`, - Function: `function __templ_onclickRedirect_47ae(id, to){document.getElementById(id).onclick = () => { - location.href = to + Name: `__templ_onclickRedirect_5c43`, + Function: `function __templ_onclickRedirect_5c43(id, to){document.getElementById(id).onclick = () => { + location.href = to }}`, - Call: templ.SafeScript(`__templ_onclickRedirect_47ae`, id, to), - CallInline: templ.SafeScriptInline(`__templ_onclickRedirect_47ae`, id, to), + Call: templ.SafeScript(`__templ_onclickRedirect_5c43`, id, to), + CallInline: templ.SafeScriptInline(`__templ_onclickRedirect_5c43`, id, to), } } diff --git a/internal/kurious/ports/http/bootstrap/list.templ b/internal/kurious/ports/http/bootstrap/list.templ index a102ce4..ed0b885 100644 --- a/internal/kurious/ports/http/bootstrap/list.templ +++ b/internal/kurious/ports/http/bootstrap/list.templ @@ -4,31 +4,31 @@ import "path" import "strconv" script breadcrumbsLoad() { - const formFilterOnSubmit = event => { - event.preventDefault(); + const formFilterOnSubmit = event => { + event.preventDefault(); - const lt = document.getElementById('learning-type-filter'); - const ct = document.getElementById('course-thematic-filter'); + const lt = document.getElementById('learning-type-filter'); + const ct = document.getElementById('course-thematic-filter'); - const prefix = (lt !== null && lt.value !== '') ? `/courses/${lt.value}` : `/courses`; - const out = (ct !== null && ct.value !== '') ? `${prefix}/${ct.value}` : prefix; + const prefix = (lt !== null && lt.value !== '') ? `/courses/${lt.value}` : `/courses`; + const out = (ct !== null && ct.value !== '') ? `${prefix}/${ct.value}` : prefix; - document.location.assign(out); - return false; - }; + document.location.assign(out); + return false; + }; - document.addEventListener('DOMContentLoaded', () => { - const ff = document.getElementById('filter-form'); - if (ff === null) return; - ff.addEventListener('submit', formFilterOnSubmit); - }); + document.addEventListener('DOMContentLoaded', () => { + const ff = document.getElementById('filter-form'); + if (ff === null) return; + ff.addEventListener('submit', formFilterOnSubmit); + }); } templ breadcrumbsItem(text, link string, isActive bool) { -
  • +
  • if link != "" { @@ -39,9 +39,8 @@ templ breadcrumbsItem(text, link string, isActive bool) { } templ breadcrumNode(params BreadcrumbsParams) { - // TODO: add divider to nav style