diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..2eea525
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1 @@
+.env
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..7bc07ec
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,10 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Environment-dependent path to Maven home directory
+/mavenHomeManager.xml
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/golang.iml b/.idea/golang.iml
new file mode 100644
index 0000000..d6ebd48
--- /dev/null
+++ b/.idea/golang.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..ad900c8
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 5c6ddc4..448ffa9 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -4,12 +4,13 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
+
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
- "program": "${fileDirname}",
+ "program": "${workspaceFolder}/cmd/api",
"envFile": "/home/kocoder/src/js/golang/.env"
}
]
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..7768046
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,15 @@
+FROM golang:1.25.4
+
+WORKDIR /app
+
+COPY go.mod go.sum ./
+
+RUN go mod download
+
+COPY . ./
+
+RUN CGO_ENABLED=1 GOOS=linux go build -o /backend ./cmd/api/main.go
+
+EXPOSE 8080
+
+CMD ["/backend"]
\ No newline at end of file
diff --git a/buf.gen.yaml b/buf.gen.yaml
new file mode 100644
index 0000000..58e02f9
--- /dev/null
+++ b/buf.gen.yaml
@@ -0,0 +1,25 @@
+version: v2
+plugins:
+ - local: protoc-gen-go
+ out: gen
+ opt: paths=source_relative
+ - local: protoc-gen-connect-go
+ out: gen
+ opt:
+ - paths=source_relative
+ - simple
+ - local: protoc-gen-es
+ out: ../query/src/gen
+ include_imports: true
+ opt: target=ts
+ - local: protoc-gen-connect-query
+ out: "../query/src/gen"
+ opt: target=ts
+managed:
+ enabled: true
+ override:
+ - file_option: go_package_prefix
+ value: git.kocoder.xyz/kocoded/vt/gen
+ disable:
+ - file_option: go_package
+ module: buf.build/bufbuild/protovalidate
\ No newline at end of file
diff --git a/buf.yaml b/buf.yaml
new file mode 100644
index 0000000..9ff3911
--- /dev/null
+++ b/buf.yaml
@@ -0,0 +1,8 @@
+# For details on buf.yaml configuration, visit https://buf.build/docs/configuration/v2/buf-yaml
+version: v2
+lint:
+ use:
+ - STANDARD
+breaking:
+ use:
+ - FILE
diff --git a/cmd/api/main.go b/cmd/api/main.go
index 8867bf0..51ac027 100644
--- a/cmd/api/main.go
+++ b/cmd/api/main.go
@@ -1,63 +1,94 @@
package main
import (
- "context"
- "log"
- "log/slog"
- "os"
- "time"
+ "fmt"
+ "net/http"
- "git.kocoder.xyz/kocoded/vt/routers"
+ kfx "git.kocoder.xyz/kocoded/vt/fx"
+ "git.kocoder.xyz/kocoded/vt/fx/interfaces"
+ "git.kocoder.xyz/kocoded/vt/fx/interfaces/stores"
+ "git.kocoder.xyz/kocoded/vt/integration"
+ "git.kocoder.xyz/kocoded/vt/integration/mailing"
+ "git.kocoder.xyz/kocoded/vt/interceptors"
+ "git.kocoder.xyz/kocoded/vt/repositories"
+ mandantv1 "git.kocoder.xyz/kocoded/vt/routers/mandant/v1"
+ messagebusv1 "git.kocoder.xyz/kocoded/vt/routers/messagebus/v1"
+ projectv1 "git.kocoder.xyz/kocoded/vt/routers/project/v1"
+ todov1 "git.kocoder.xyz/kocoded/vt/routers/todo/v1"
"git.kocoder.xyz/kocoded/vt/utils"
- "github.com/gofiber/contrib/websocket"
- "github.com/gofiber/fiber/v2"
"github.com/joho/godotenv"
+ "go.uber.org/fx"
)
-func main() {
- logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
-
- err := godotenv.Load()
- if err != nil {
- logger.Error("Error Loading Environment variables! ", "error", err)
- }
-
- db := utils.SetupDatabase(os.Getenv("DB_DSN"), logger)
- cache, ok := utils.SetupCache(os.Getenv("VALKEY_HOST"), os.Getenv("VALKEY_PORT"), os.Getenv("VALKEY_USER"), os.Getenv("VALKEY_PASS"))
- if !ok {
- logger.Error("Configuring and connecting to Valkey failed!")
- }
-
- appCtx := utils.Application{Logger: logger, DB: db, Client: cache}
-
- app := fiber.New()
-
- utils.RegisterMiddlewares(app)
-
- utils.CreateOIDCClient(context.Background(), app, appCtx)
-
- routers.RegisterMandantRouter(app.Group("/v1/mandant"), appCtx)
- routers.RegisterAnsprechpartnerRouter(app.Group("/v1/ansprechpartner"), appCtx)
- routers.RegisterFirmaRouter(app.Group("/v1/firma"), appCtx)
- routers.RegisterProjectRouter(app.Group("/v1/projects"), appCtx)
- routers.RegisterUserRouter(app.Group("/v1/users"), appCtx)
-
- app.Use("/ws", func(c *fiber.Ctx) error {
- if websocket.IsWebSocketUpgrade(c) {
- c.Locals("allowed", true)
- return c.Next()
- }
- return fiber.ErrUpgradeRequired
- })
-
- app.Get("/ws", websocket.New(func(c *websocket.Conn) {
- go func() {
- time.Sleep(time.Second)
- }()
-
- utils.MessageBus.AddConn(1, c)
-
- }))
-
- log.Fatalln(app.Listen(":3000"))
+func AsIntegration(f any) any {
+ return fx.Annotate(
+ f,
+ fx.As(new(interfaces.BasicIntegration)),
+ fx.ResultTags(`group:"integration"`),
+ )
+}
+
+func AsRoute(f any) any {
+ return fx.Annotate(
+ f,
+ fx.As(new(kfx.Handler)),
+ fx.ResultTags(`group:"connectRoute"`),
+ )
+}
+
+func main() {
+ err := godotenv.Load()
+
+ if err != nil {
+ fmt.Println("Error loading env vars... %v", err)
+ }
+
+ fx.New(
+ fx.Provide(
+ kfx.NewHTTPServer,
+ utils.NewReflector,
+ utils.NewHealthchecker,
+
+ utils.NewDB,
+ utils.NewCache,
+
+ utils.NewOIDCProvider,
+ utils.NewOIDCVerifier,
+
+ fx.Annotate(repositories.NewSessionRepository, fx.As(new(stores.SessionStore))),
+ fx.Annotate(repositories.NewInMemeoryMessageRepository, fx.As(new(stores.MessageStore))),
+ interceptors.NewOIDCInterceptor,
+
+ AsIntegration(mailing.NewImapIntegration),
+ fx.Annotate(
+ integration.NewIntegrationHandler,
+ fx.ParamTags(`group:"integration"`),
+ ),
+
+ kfx.NewOtelLoggerProvider,
+ kfx.NewOtelMeterProvider,
+ kfx.NewOtelTracerProvider,
+ kfx.NewLogger,
+
+ AsRoute(utils.NewHealthCheckV1),
+
+ AsRoute(utils.NewReflectorV1),
+ AsRoute(utils.NewReflectorV1Alpha1),
+
+ AsRoute(kfx.NewEchoHandler),
+
+ AsRoute(messagebusv1.NewMessagebusRoute),
+
+ AsRoute(projectv1.NewProjectRoute),
+ AsRoute(todov1.NewTodoRoute),
+ AsRoute(mandantv1.NewMandantRoute),
+
+ fx.Annotate(
+ kfx.NewServeMux,
+ fx.ParamTags(`group:"connectRoute"`),
+ ),
+ ),
+
+ fx.Invoke(func(*integration.IntegrationHandler) {}, kfx.SetupOTelSDK, func(*http.Server) {}),
+ ).Run()
}
diff --git a/cmd/generate/main.go b/cmd/generate/main.go
new file mode 100644
index 0000000..2f4a480
--- /dev/null
+++ b/cmd/generate/main.go
@@ -0,0 +1,38 @@
+package main
+
+import (
+ "log/slog"
+ "os"
+
+ "git.kocoder.xyz/kocoded/vt/model"
+ "github.com/joho/godotenv"
+ "gorm.io/driver/postgres"
+ "gorm.io/gen"
+ "gorm.io/gorm"
+)
+
+func main() {
+ err := godotenv.Load()
+ if err != nil {
+ slog.Error("Error Loading Environment variables! ", "error", err)
+ }
+
+ db, err := gorm.Open(postgres.Open(os.Getenv("DB_DSN")), &gorm.Config{})
+ if err != nil {
+ slog.Error("Error connecting to the Database", "error", err)
+ }
+
+ g := gen.NewGenerator(gen.Config{
+ OutPath: "query",
+ Mode: gen.WithoutContext | gen.WithDefaultQuery | gen.WithQueryInterface, // generate mode
+ })
+
+ // gormdb, _ := gorm.Open(mysql.Open("root:@(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=True&loc=Local"))
+ g.UseDB(db) // reuse your gorm db
+
+ // Generate basic type-safe DAO API for struct `model.User` following conventions
+ g.ApplyBasic(model.Mandant{}, model.User{}, model.Ansprechpartner{}, model.Dokument{}, model.Firma{}, model.Kalender{}, model.Kalendereintrag{}, model.Kostenstelle{}, model.Lager{}, model.Lagerplatz{}, model.Material{}, model.Nachricht{}, model.Projekt{}, model.Rechnung{}, model.Rechnungsposition{}, model.Scanobject{}, model.User{}, model.Zahlung{}, model.FirmaAnsprechpartner{}, model.Task{})
+
+ // Generate the code
+ g.Execute()
+}
diff --git a/docker-compose.yaml b/docker-compose.yaml
new file mode 100644
index 0000000..06c06fb
--- /dev/null
+++ b/docker-compose.yaml
@@ -0,0 +1,22 @@
+services:
+ backend:
+ image: "git.kocoder.xyz/kocoded/vt-be"
+ build: .
+ environment:
+ - CLIENT_ID="golang-vt-backend"
+ - CLIENT_SECRET="awumIoacqNmwKTxRilQSM9cDmA7xA0j0"
+ - BACKEND_URI="http://10.8.0.3:3000"
+ - FRONTEND_URI="http://10.8.0.3:3001"
+
+ - OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="https://otel.kocoder.xyz/v1/traces"
+ - OTEL_EXPORTER_OTLP_METRICS_ENDPOINT="https://otel.kocoder.xyz/v1/metrics"
+ - OTEL_EXPORTER_OTLP_LOGS_ENDPOINT="https://otel.kocoder.xyz/v1/logs"
+
+ - DB_DSN="host=10.1.0.2 user=vt password=20a1c7809cd065bc5afe7c36fde26abf625316c8a83cc841b435c9acf3619b1f dbname=vt port=5432 sslmode=prefer TimeZone=Europe/Vienna"
+
+ - VALKEY_HOST="10.8.0.1"
+ - VALKEY_PORT="6379"
+ - VALKEY_USER="default"
+ - VALKEY_PASS="Konsti2007!"
+ ports:
+ - 3003:3002
diff --git a/fx/echo.go b/fx/echo.go
new file mode 100644
index 0000000..c1152eb
--- /dev/null
+++ b/fx/echo.go
@@ -0,0 +1,32 @@
+package fx
+
+import (
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+)
+
+// EchoHandler is an http.Handler that copies its request body
+// back to the response.
+type EchoHandler struct{}
+
+// NewEchoHandler builds a new EchoHandler.
+func NewEchoHandler() *EchoHandler {
+ return &EchoHandler{}
+}
+
+// ServeHTTP handles an HTTP request to the /echo endpoint.
+func (*EchoHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ if _, err := io.Copy(w, r.Body); err != nil {
+ fmt.Fprintln(os.Stderr, "Failed to handle request:", err)
+ }
+}
+
+func (e *EchoHandler) Handler() http.Handler {
+ return e
+}
+
+func (*EchoHandler) Path() string {
+ return "/echo"
+}
diff --git a/fx/interfaces/integration.go b/fx/interfaces/integration.go
new file mode 100644
index 0000000..47bad37
--- /dev/null
+++ b/fx/interfaces/integration.go
@@ -0,0 +1,9 @@
+package interfaces
+
+import "context"
+
+type BasicIntegration interface {
+ OnStart(context.Context) error
+ OnStop(context.Context) error
+ Invoke()
+}
diff --git a/fx/interfaces/stores/mandantstore.go b/fx/interfaces/stores/mandantstore.go
new file mode 100644
index 0000000..8209f73
--- /dev/null
+++ b/fx/interfaces/stores/mandantstore.go
@@ -0,0 +1 @@
+package stores
diff --git a/fx/interfaces/stores/messagestore.go b/fx/interfaces/stores/messagestore.go
new file mode 100644
index 0000000..1f16b2f
--- /dev/null
+++ b/fx/interfaces/stores/messagestore.go
@@ -0,0 +1,17 @@
+package stores
+
+import "context"
+
+type Message struct {
+ Id int
+ From string
+ Subject string
+ Body string
+}
+
+type MessageStore interface {
+ AddMessage(ctx context.Context, m *Message) error
+ GetMessage(ctx context.Context, id int) (*Message, error)
+ ListMessages(ctx context.Context) ([]*Message, error)
+ RemoveMessage(ctx context.Context, id int) error
+}
diff --git a/fx/interfaces/stores/projectstore.go b/fx/interfaces/stores/projectstore.go
new file mode 100644
index 0000000..8209f73
--- /dev/null
+++ b/fx/interfaces/stores/projectstore.go
@@ -0,0 +1 @@
+package stores
diff --git a/fx/interfaces/stores/sessionstore.go b/fx/interfaces/stores/sessionstore.go
new file mode 100644
index 0000000..3fd7fa9
--- /dev/null
+++ b/fx/interfaces/stores/sessionstore.go
@@ -0,0 +1,44 @@
+package stores
+
+import (
+ "errors"
+ "strconv"
+)
+
+type Session struct {
+ Token string
+ UserID uint
+ MandantId uint
+}
+
+func (s *Session) Deserialize(t string, m map[string]string) (*Session, error) {
+ userid, err := strconv.Atoi(m["userid"])
+ if err != nil {
+ return nil, errors.New("Userid from cache not an int")
+ }
+
+ mandantid, err := strconv.Atoi(m["mandantid"])
+ if err != nil {
+ return nil, errors.New("Mandantid from cache not an int")
+ }
+
+ s.Token = t
+ s.UserID = uint(userid)
+ s.MandantId = uint(mandantid)
+
+ return s, nil
+}
+
+func (s *Session) Serialize() map[string]string {
+ m := make(map[string]string)
+ m["userid"] = strconv.Itoa(int(s.UserID))
+ m["mandantid"] = strconv.Itoa(int(s.MandantId))
+ return m
+}
+
+type SessionStore interface {
+ AddSession(s *Session)
+ GetSessionFromToken(token string) (*Session, error)
+ RemoveSession(token string)
+ SetMandantInSession(token string, mandantId uint) error
+}
diff --git a/fx/interfaces/stores/todostore.go b/fx/interfaces/stores/todostore.go
new file mode 100644
index 0000000..8209f73
--- /dev/null
+++ b/fx/interfaces/stores/todostore.go
@@ -0,0 +1 @@
+package stores
diff --git a/fx/logger.go b/fx/logger.go
new file mode 100644
index 0000000..ac00f90
--- /dev/null
+++ b/fx/logger.go
@@ -0,0 +1,137 @@
+package fx
+
+import (
+ "context"
+ "log/slog"
+ "time"
+
+ "go.uber.org/fx"
+
+ "go.opentelemetry.io/contrib/bridges/otelslog"
+ "go.opentelemetry.io/otel"
+ "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp"
+ "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
+ "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
+ "go.opentelemetry.io/otel/log/global"
+ "go.opentelemetry.io/otel/propagation"
+ olog "go.opentelemetry.io/otel/sdk/log"
+ "go.opentelemetry.io/otel/sdk/metric"
+ "go.opentelemetry.io/otel/sdk/trace"
+)
+
+func NewOtelTracerProvider(lc fx.Lifecycle) *trace.TracerProvider {
+ tp, err := newTracerProvider()
+ if err != nil {
+ panic(err)
+ }
+
+ lc.Append(fx.Hook{
+ OnStop: func(ctx context.Context) error {
+ return tp.Shutdown(ctx)
+ },
+ })
+
+ return tp
+}
+
+func NewOtelMeterProvider(lc fx.Lifecycle) *metric.MeterProvider {
+ mp, err := newMeterProvider()
+ if err != nil {
+ panic(err)
+ }
+
+ lc.Append(fx.Hook{
+ OnStop: func(ctx context.Context) error {
+ return mp.Shutdown(ctx)
+ },
+ })
+
+ return mp
+}
+
+func NewOtelLoggerProvider(lc fx.Lifecycle) *olog.LoggerProvider {
+ lp, err := newLoggerProvider()
+ if err != nil {
+ panic(err)
+ }
+
+ lc.Append(fx.Hook{
+ OnStop: func(ctx context.Context) error {
+ return lp.Shutdown(ctx)
+ },
+ })
+
+ return lp
+}
+
+func NewLogger(loggerProvider *olog.LoggerProvider) *slog.Logger {
+ logger := otelslog.NewLogger("Application", otelslog.WithLoggerProvider(loggerProvider))
+ return logger
+}
+
+// setupOTelSDK bootstraps the OpenTelemetry pipeline.
+// If it does not return an error, make sure to call shutdown for proper cleanup.
+func SetupOTelSDK(tracerProvider *trace.TracerProvider, meterProvider *metric.MeterProvider, loggerProvider *olog.LoggerProvider) {
+ // Set up propagator.
+ prop := newPropagator()
+ otel.SetTextMapPropagator(prop)
+
+ // Set up trace provider.
+ otel.SetTracerProvider(tracerProvider)
+ otel.SetMeterProvider(meterProvider)
+
+ // Set up logger provider.
+ global.SetLoggerProvider(loggerProvider)
+}
+
+func newPropagator() propagation.TextMapPropagator {
+ return propagation.NewCompositeTextMapPropagator(
+ propagation.TraceContext{},
+ propagation.Baggage{},
+ )
+}
+
+func newTracerProvider() (*trace.TracerProvider, error) {
+
+ traceExporter, err := otlptracehttp.New(context.Background())
+ if err != nil {
+ return nil, err
+ }
+
+ tracerProvider := trace.NewTracerProvider(
+ trace.WithBatcher(traceExporter,
+ // Default is 5s. Set to 1s for demonstrative purposes.
+ trace.WithBatchTimeout(time.Second)),
+ )
+ return tracerProvider, nil
+}
+
+func newMeterProvider() (*metric.MeterProvider, error) {
+ metricExporter, err := otlpmetrichttp.New(context.Background())
+ 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(3*time.Second))),
+ )
+ return meterProvider, nil
+}
+
+func newLoggerProvider() (*olog.LoggerProvider, error) {
+ logExporter, err := otlploghttp.New(context.Background())
+ if err != nil {
+ return nil, err
+ }
+
+ loggerProvider := olog.NewLoggerProvider(
+ olog.WithProcessor(olog.NewBatchProcessor(logExporter)),
+ )
+
+ logger := otelslog.NewLogger("sloger", otelslog.WithLoggerProvider(loggerProvider))
+ logger.Info("Hello")
+
+ return loggerProvider, nil
+}
diff --git a/fx/routes.go b/fx/routes.go
new file mode 100644
index 0000000..e7d3e28
--- /dev/null
+++ b/fx/routes.go
@@ -0,0 +1,56 @@
+package fx
+
+import (
+ "log/slog"
+ "net/http"
+ "os"
+
+ connectcors "connectrpc.com/cors"
+ "github.com/rs/cors"
+)
+
+type Handler interface {
+ Handler() http.Handler
+ Path() string
+}
+
+type route struct {
+ path string
+ handler http.Handler
+}
+
+func NewRoute(path string, handler http.Handler) Handler {
+ return &route{
+ path: path,
+ handler: handler,
+ }
+}
+
+func (r *route) Handler() http.Handler {
+ return r.handler
+}
+
+func (r *route) Path() string {
+ return r.path
+}
+
+// NewServeMux builds a ServeMux that will route requests
+// to the given EchoHandler.
+func NewServeMux(handlers []Handler, logger *slog.Logger) http.Handler {
+ mux := http.NewServeMux()
+
+ for _, h := range handlers {
+ logger.Debug("Registering route", "path", h.Path())
+ mux.Handle(h.Path(), h.Handler())
+ }
+
+ handler := cors.New(cors.Options{
+ AllowedOrigins: []string{"http://10.8.0.3:3001", "http://10.8.0.3:3001/", os.Getenv("FRONTEND_URI")}, // replace with your domain
+ AllowedMethods: connectcors.AllowedMethods(),
+ AllowedHeaders: append(connectcors.AllowedHeaders(), "authentication", "Authentication"),
+ ExposedHeaders: connectcors.ExposedHeaders(),
+ AllowCredentials: true,
+ }).Handler(mux)
+
+ return handler
+}
diff --git a/fx/server.go b/fx/server.go
new file mode 100644
index 0000000..8a74abd
--- /dev/null
+++ b/fx/server.go
@@ -0,0 +1,40 @@
+package fx
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+
+ "go.uber.org/fx"
+)
+
+func NewHTTPServer(lc fx.Lifecycle, handler http.Handler) *http.Server {
+ p := new(http.Protocols)
+ p.SetHTTP1(true)
+ // Use h2c so we can serve HTTP/2 without TLS.
+ p.SetUnencryptedHTTP2(true)
+
+ srv := &http.Server{
+ Addr: ":3002",
+ Protocols: p,
+ Handler: handler,
+ }
+
+ lc.Append(fx.Hook{
+ OnStart: func(ctx context.Context) error {
+ go func() {
+ fmt.Println("Listening on :3002")
+ if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+ panic(err)
+ }
+ }()
+
+ return nil
+ },
+ OnStop: func(ctx context.Context) error {
+ return srv.Shutdown(ctx)
+ },
+ })
+
+ return srv
+}
diff --git a/gen/mandant/v1/mandant.pb.go b/gen/mandant/v1/mandant.pb.go
new file mode 100644
index 0000000..d6cf49d
--- /dev/null
+++ b/gen/mandant/v1/mandant.pb.go
@@ -0,0 +1,532 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.36.10
+// protoc (unknown)
+// source: mandant/v1/mandant.proto
+
+package mandantv1
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+ unsafe "unsafe"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type GetCurrentTenantRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *GetCurrentTenantRequest) Reset() {
+ *x = GetCurrentTenantRequest{}
+ mi := &file_mandant_v1_mandant_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *GetCurrentTenantRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetCurrentTenantRequest) ProtoMessage() {}
+
+func (x *GetCurrentTenantRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_mandant_v1_mandant_proto_msgTypes[0]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetCurrentTenantRequest.ProtoReflect.Descriptor instead.
+func (*GetCurrentTenantRequest) Descriptor() ([]byte, []int) {
+ return file_mandant_v1_mandant_proto_rawDescGZIP(), []int{0}
+}
+
+type GetTenantRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *GetTenantRequest) Reset() {
+ *x = GetTenantRequest{}
+ mi := &file_mandant_v1_mandant_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *GetTenantRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetTenantRequest) ProtoMessage() {}
+
+func (x *GetTenantRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_mandant_v1_mandant_proto_msgTypes[1]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetTenantRequest.ProtoReflect.Descriptor instead.
+func (*GetTenantRequest) Descriptor() ([]byte, []int) {
+ return file_mandant_v1_mandant_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *GetTenantRequest) GetId() int64 {
+ if x != nil {
+ return x.Id
+ }
+ return 0
+}
+
+type GetTenantResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
+ Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
+ Plan string `protobuf:"bytes,3,opt,name=plan,proto3" json:"plan,omitempty"`
+ Logo string `protobuf:"bytes,4,opt,name=logo,proto3" json:"logo,omitempty"`
+ Color string `protobuf:"bytes,5,opt,name=color,proto3" json:"color,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *GetTenantResponse) Reset() {
+ *x = GetTenantResponse{}
+ mi := &file_mandant_v1_mandant_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *GetTenantResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetTenantResponse) ProtoMessage() {}
+
+func (x *GetTenantResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_mandant_v1_mandant_proto_msgTypes[2]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetTenantResponse.ProtoReflect.Descriptor instead.
+func (*GetTenantResponse) Descriptor() ([]byte, []int) {
+ return file_mandant_v1_mandant_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *GetTenantResponse) GetId() int64 {
+ if x != nil {
+ return x.Id
+ }
+ return 0
+}
+
+func (x *GetTenantResponse) GetName() string {
+ if x != nil {
+ return x.Name
+ }
+ return ""
+}
+
+func (x *GetTenantResponse) GetPlan() string {
+ if x != nil {
+ return x.Plan
+ }
+ return ""
+}
+
+func (x *GetTenantResponse) GetLogo() string {
+ if x != nil {
+ return x.Logo
+ }
+ return ""
+}
+
+func (x *GetTenantResponse) GetColor() string {
+ if x != nil {
+ return x.Color
+ }
+ return ""
+}
+
+type ListTenantRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Page int32 `protobuf:"varint,1,opt,name=page,proto3" json:"page,omitempty"`
+ PerPage int32 `protobuf:"varint,2,opt,name=per_page,json=perPage,proto3" json:"per_page,omitempty"`
+ OrberBy string `protobuf:"bytes,3,opt,name=orber_by,json=orberBy,proto3" json:"orber_by,omitempty"`
+ Asc bool `protobuf:"varint,4,opt,name=asc,proto3" json:"asc,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ListTenantRequest) Reset() {
+ *x = ListTenantRequest{}
+ mi := &file_mandant_v1_mandant_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ListTenantRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ListTenantRequest) ProtoMessage() {}
+
+func (x *ListTenantRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_mandant_v1_mandant_proto_msgTypes[3]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ListTenantRequest.ProtoReflect.Descriptor instead.
+func (*ListTenantRequest) Descriptor() ([]byte, []int) {
+ return file_mandant_v1_mandant_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *ListTenantRequest) GetPage() int32 {
+ if x != nil {
+ return x.Page
+ }
+ return 0
+}
+
+func (x *ListTenantRequest) GetPerPage() int32 {
+ if x != nil {
+ return x.PerPage
+ }
+ return 0
+}
+
+func (x *ListTenantRequest) GetOrberBy() string {
+ if x != nil {
+ return x.OrberBy
+ }
+ return ""
+}
+
+func (x *ListTenantRequest) GetAsc() bool {
+ if x != nil {
+ return x.Asc
+ }
+ return false
+}
+
+type Metadata struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ TotalCount int32 `protobuf:"varint,1,opt,name=totalCount,proto3" json:"totalCount,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Metadata) Reset() {
+ *x = Metadata{}
+ mi := &file_mandant_v1_mandant_proto_msgTypes[4]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Metadata) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Metadata) ProtoMessage() {}
+
+func (x *Metadata) ProtoReflect() protoreflect.Message {
+ mi := &file_mandant_v1_mandant_proto_msgTypes[4]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Metadata.ProtoReflect.Descriptor instead.
+func (*Metadata) Descriptor() ([]byte, []int) {
+ return file_mandant_v1_mandant_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *Metadata) GetTotalCount() int32 {
+ if x != nil {
+ return x.TotalCount
+ }
+ return 0
+}
+
+type ListProjectsResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Data []*GetTenantResponse `protobuf:"bytes,1,rep,name=data,proto3" json:"data,omitempty"`
+ Meta *Metadata `protobuf:"bytes,2,opt,name=meta,proto3" json:"meta,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ListProjectsResponse) Reset() {
+ *x = ListProjectsResponse{}
+ mi := &file_mandant_v1_mandant_proto_msgTypes[5]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ListProjectsResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ListProjectsResponse) ProtoMessage() {}
+
+func (x *ListProjectsResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_mandant_v1_mandant_proto_msgTypes[5]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ListProjectsResponse.ProtoReflect.Descriptor instead.
+func (*ListProjectsResponse) Descriptor() ([]byte, []int) {
+ return file_mandant_v1_mandant_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *ListProjectsResponse) GetData() []*GetTenantResponse {
+ if x != nil {
+ return x.Data
+ }
+ return nil
+}
+
+func (x *ListProjectsResponse) GetMeta() *Metadata {
+ if x != nil {
+ return x.Meta
+ }
+ return nil
+}
+
+type SetCurrentTenantRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ TenantId int64 `protobuf:"varint,1,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *SetCurrentTenantRequest) Reset() {
+ *x = SetCurrentTenantRequest{}
+ mi := &file_mandant_v1_mandant_proto_msgTypes[6]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *SetCurrentTenantRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SetCurrentTenantRequest) ProtoMessage() {}
+
+func (x *SetCurrentTenantRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_mandant_v1_mandant_proto_msgTypes[6]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use SetCurrentTenantRequest.ProtoReflect.Descriptor instead.
+func (*SetCurrentTenantRequest) Descriptor() ([]byte, []int) {
+ return file_mandant_v1_mandant_proto_rawDescGZIP(), []int{6}
+}
+
+func (x *SetCurrentTenantRequest) GetTenantId() int64 {
+ if x != nil {
+ return x.TenantId
+ }
+ return 0
+}
+
+type SetCurrentTenantResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *SetCurrentTenantResponse) Reset() {
+ *x = SetCurrentTenantResponse{}
+ mi := &file_mandant_v1_mandant_proto_msgTypes[7]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *SetCurrentTenantResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SetCurrentTenantResponse) ProtoMessage() {}
+
+func (x *SetCurrentTenantResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_mandant_v1_mandant_proto_msgTypes[7]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use SetCurrentTenantResponse.ProtoReflect.Descriptor instead.
+func (*SetCurrentTenantResponse) Descriptor() ([]byte, []int) {
+ return file_mandant_v1_mandant_proto_rawDescGZIP(), []int{7}
+}
+
+func (x *SetCurrentTenantResponse) GetSuccess() bool {
+ if x != nil {
+ return x.Success
+ }
+ return false
+}
+
+var File_mandant_v1_mandant_proto protoreflect.FileDescriptor
+
+const file_mandant_v1_mandant_proto_rawDesc = "" +
+ "\n" +
+ "\x18mandant/v1/mandant.proto\x12\n" +
+ "mandant.v1\"\x19\n" +
+ "\x17GetCurrentTenantRequest\"\"\n" +
+ "\x10GetTenantRequest\x12\x0e\n" +
+ "\x02id\x18\x01 \x01(\x03R\x02id\"u\n" +
+ "\x11GetTenantResponse\x12\x0e\n" +
+ "\x02id\x18\x01 \x01(\x03R\x02id\x12\x12\n" +
+ "\x04name\x18\x02 \x01(\tR\x04name\x12\x12\n" +
+ "\x04plan\x18\x03 \x01(\tR\x04plan\x12\x12\n" +
+ "\x04logo\x18\x04 \x01(\tR\x04logo\x12\x14\n" +
+ "\x05color\x18\x05 \x01(\tR\x05color\"o\n" +
+ "\x11ListTenantRequest\x12\x12\n" +
+ "\x04page\x18\x01 \x01(\x05R\x04page\x12\x19\n" +
+ "\bper_page\x18\x02 \x01(\x05R\aperPage\x12\x19\n" +
+ "\borber_by\x18\x03 \x01(\tR\aorberBy\x12\x10\n" +
+ "\x03asc\x18\x04 \x01(\bR\x03asc\"*\n" +
+ "\bMetadata\x12\x1e\n" +
+ "\n" +
+ "totalCount\x18\x01 \x01(\x05R\n" +
+ "totalCount\"s\n" +
+ "\x14ListProjectsResponse\x121\n" +
+ "\x04data\x18\x01 \x03(\v2\x1d.mandant.v1.GetTenantResponseR\x04data\x12(\n" +
+ "\x04meta\x18\x02 \x01(\v2\x14.mandant.v1.MetadataR\x04meta\"6\n" +
+ "\x17SetCurrentTenantRequest\x12\x1b\n" +
+ "\ttenant_id\x18\x01 \x01(\x03R\btenantId\"4\n" +
+ "\x18SetCurrentTenantResponse\x12\x18\n" +
+ "\asuccess\x18\x01 \x01(\bR\asuccess2\x99\x02\n" +
+ "\x0eMandantService\x12V\n" +
+ "\x10GetCurrentTenant\x12#.mandant.v1.GetCurrentTenantRequest\x1a\x1d.mandant.v1.GetTenantResponse\x12P\n" +
+ "\rGetAllTenants\x12\x1d.mandant.v1.ListTenantRequest\x1a .mandant.v1.ListProjectsResponse\x12]\n" +
+ "\x10SetCurrentTenant\x12#.mandant.v1.SetCurrentTenantRequest\x1a$.mandant.v1.SetCurrentTenantResponseB\x9c\x01\n" +
+ "\x0ecom.mandant.v1B\fMandantProtoP\x01Z3git.kocoder.xyz/kocoded/vt/gen/mandant/v1;mandantv1\xa2\x02\x03MXX\xaa\x02\n" +
+ "Mandant.V1\xca\x02\n" +
+ "Mandant\\V1\xe2\x02\x16Mandant\\V1\\GPBMetadata\xea\x02\vMandant::V1b\x06proto3"
+
+var (
+ file_mandant_v1_mandant_proto_rawDescOnce sync.Once
+ file_mandant_v1_mandant_proto_rawDescData []byte
+)
+
+func file_mandant_v1_mandant_proto_rawDescGZIP() []byte {
+ file_mandant_v1_mandant_proto_rawDescOnce.Do(func() {
+ file_mandant_v1_mandant_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_mandant_v1_mandant_proto_rawDesc), len(file_mandant_v1_mandant_proto_rawDesc)))
+ })
+ return file_mandant_v1_mandant_proto_rawDescData
+}
+
+var file_mandant_v1_mandant_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
+var file_mandant_v1_mandant_proto_goTypes = []any{
+ (*GetCurrentTenantRequest)(nil), // 0: mandant.v1.GetCurrentTenantRequest
+ (*GetTenantRequest)(nil), // 1: mandant.v1.GetTenantRequest
+ (*GetTenantResponse)(nil), // 2: mandant.v1.GetTenantResponse
+ (*ListTenantRequest)(nil), // 3: mandant.v1.ListTenantRequest
+ (*Metadata)(nil), // 4: mandant.v1.Metadata
+ (*ListProjectsResponse)(nil), // 5: mandant.v1.ListProjectsResponse
+ (*SetCurrentTenantRequest)(nil), // 6: mandant.v1.SetCurrentTenantRequest
+ (*SetCurrentTenantResponse)(nil), // 7: mandant.v1.SetCurrentTenantResponse
+}
+var file_mandant_v1_mandant_proto_depIdxs = []int32{
+ 2, // 0: mandant.v1.ListProjectsResponse.data:type_name -> mandant.v1.GetTenantResponse
+ 4, // 1: mandant.v1.ListProjectsResponse.meta:type_name -> mandant.v1.Metadata
+ 0, // 2: mandant.v1.MandantService.GetCurrentTenant:input_type -> mandant.v1.GetCurrentTenantRequest
+ 3, // 3: mandant.v1.MandantService.GetAllTenants:input_type -> mandant.v1.ListTenantRequest
+ 6, // 4: mandant.v1.MandantService.SetCurrentTenant:input_type -> mandant.v1.SetCurrentTenantRequest
+ 2, // 5: mandant.v1.MandantService.GetCurrentTenant:output_type -> mandant.v1.GetTenantResponse
+ 5, // 6: mandant.v1.MandantService.GetAllTenants:output_type -> mandant.v1.ListProjectsResponse
+ 7, // 7: mandant.v1.MandantService.SetCurrentTenant:output_type -> mandant.v1.SetCurrentTenantResponse
+ 5, // [5:8] is the sub-list for method output_type
+ 2, // [2:5] is the sub-list for method input_type
+ 2, // [2:2] is the sub-list for extension type_name
+ 2, // [2:2] is the sub-list for extension extendee
+ 0, // [0:2] is the sub-list for field type_name
+}
+
+func init() { file_mandant_v1_mandant_proto_init() }
+func file_mandant_v1_mandant_proto_init() {
+ if File_mandant_v1_mandant_proto != nil {
+ return
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: unsafe.Slice(unsafe.StringData(file_mandant_v1_mandant_proto_rawDesc), len(file_mandant_v1_mandant_proto_rawDesc)),
+ NumEnums: 0,
+ NumMessages: 8,
+ NumExtensions: 0,
+ NumServices: 1,
+ },
+ GoTypes: file_mandant_v1_mandant_proto_goTypes,
+ DependencyIndexes: file_mandant_v1_mandant_proto_depIdxs,
+ MessageInfos: file_mandant_v1_mandant_proto_msgTypes,
+ }.Build()
+ File_mandant_v1_mandant_proto = out.File
+ file_mandant_v1_mandant_proto_goTypes = nil
+ file_mandant_v1_mandant_proto_depIdxs = nil
+}
diff --git a/gen/mandant/v1/mandantv1connect/mandant.connect.go b/gen/mandant/v1/mandantv1connect/mandant.connect.go
new file mode 100644
index 0000000..458e68d
--- /dev/null
+++ b/gen/mandant/v1/mandantv1connect/mandant.connect.go
@@ -0,0 +1,179 @@
+// Code generated by protoc-gen-connect-go. DO NOT EDIT.
+//
+// Source: mandant/v1/mandant.proto
+
+package mandantv1connect
+
+import (
+ connect "connectrpc.com/connect"
+ context "context"
+ errors "errors"
+ v1 "git.kocoder.xyz/kocoded/vt/gen/mandant/v1"
+ http "net/http"
+ strings "strings"
+)
+
+// This is a compile-time assertion to ensure that this generated file and the connect package are
+// compatible. If you get a compiler error that this constant is not defined, this code was
+// generated with a version of connect newer than the one compiled into your binary. You can fix the
+// problem by either regenerating this code with an older version of connect or updating the connect
+// version compiled into your binary.
+const _ = connect.IsAtLeastVersion1_13_0
+
+const (
+ // MandantServiceName is the fully-qualified name of the MandantService service.
+ MandantServiceName = "mandant.v1.MandantService"
+)
+
+// These constants are the fully-qualified names of the RPCs defined in this package. They're
+// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
+//
+// Note that these are different from the fully-qualified method names used by
+// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
+// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
+// period.
+const (
+ // MandantServiceGetCurrentTenantProcedure is the fully-qualified name of the MandantService's
+ // GetCurrentTenant RPC.
+ MandantServiceGetCurrentTenantProcedure = "/mandant.v1.MandantService/GetCurrentTenant"
+ // MandantServiceGetAllTenantsProcedure is the fully-qualified name of the MandantService's
+ // GetAllTenants RPC.
+ MandantServiceGetAllTenantsProcedure = "/mandant.v1.MandantService/GetAllTenants"
+ // MandantServiceSetCurrentTenantProcedure is the fully-qualified name of the MandantService's
+ // SetCurrentTenant RPC.
+ MandantServiceSetCurrentTenantProcedure = "/mandant.v1.MandantService/SetCurrentTenant"
+)
+
+// MandantServiceClient is a client for the mandant.v1.MandantService service.
+type MandantServiceClient interface {
+ GetCurrentTenant(context.Context, *v1.GetCurrentTenantRequest) (*v1.GetTenantResponse, error)
+ GetAllTenants(context.Context, *v1.ListTenantRequest) (*v1.ListProjectsResponse, error)
+ SetCurrentTenant(context.Context, *v1.SetCurrentTenantRequest) (*v1.SetCurrentTenantResponse, error)
+}
+
+// NewMandantServiceClient constructs a client for the mandant.v1.MandantService service. By
+// default, it uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses,
+// and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the
+// connect.WithGRPC() or connect.WithGRPCWeb() options.
+//
+// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
+// http://api.acme.com or https://acme.com/grpc).
+func NewMandantServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) MandantServiceClient {
+ baseURL = strings.TrimRight(baseURL, "/")
+ mandantServiceMethods := v1.File_mandant_v1_mandant_proto.Services().ByName("MandantService").Methods()
+ return &mandantServiceClient{
+ getCurrentTenant: connect.NewClient[v1.GetCurrentTenantRequest, v1.GetTenantResponse](
+ httpClient,
+ baseURL+MandantServiceGetCurrentTenantProcedure,
+ connect.WithSchema(mandantServiceMethods.ByName("GetCurrentTenant")),
+ connect.WithClientOptions(opts...),
+ ),
+ getAllTenants: connect.NewClient[v1.ListTenantRequest, v1.ListProjectsResponse](
+ httpClient,
+ baseURL+MandantServiceGetAllTenantsProcedure,
+ connect.WithSchema(mandantServiceMethods.ByName("GetAllTenants")),
+ connect.WithClientOptions(opts...),
+ ),
+ setCurrentTenant: connect.NewClient[v1.SetCurrentTenantRequest, v1.SetCurrentTenantResponse](
+ httpClient,
+ baseURL+MandantServiceSetCurrentTenantProcedure,
+ connect.WithSchema(mandantServiceMethods.ByName("SetCurrentTenant")),
+ connect.WithClientOptions(opts...),
+ ),
+ }
+}
+
+// mandantServiceClient implements MandantServiceClient.
+type mandantServiceClient struct {
+ getCurrentTenant *connect.Client[v1.GetCurrentTenantRequest, v1.GetTenantResponse]
+ getAllTenants *connect.Client[v1.ListTenantRequest, v1.ListProjectsResponse]
+ setCurrentTenant *connect.Client[v1.SetCurrentTenantRequest, v1.SetCurrentTenantResponse]
+}
+
+// GetCurrentTenant calls mandant.v1.MandantService.GetCurrentTenant.
+func (c *mandantServiceClient) GetCurrentTenant(ctx context.Context, req *v1.GetCurrentTenantRequest) (*v1.GetTenantResponse, error) {
+ response, err := c.getCurrentTenant.CallUnary(ctx, connect.NewRequest(req))
+ if response != nil {
+ return response.Msg, err
+ }
+ return nil, err
+}
+
+// GetAllTenants calls mandant.v1.MandantService.GetAllTenants.
+func (c *mandantServiceClient) GetAllTenants(ctx context.Context, req *v1.ListTenantRequest) (*v1.ListProjectsResponse, error) {
+ response, err := c.getAllTenants.CallUnary(ctx, connect.NewRequest(req))
+ if response != nil {
+ return response.Msg, err
+ }
+ return nil, err
+}
+
+// SetCurrentTenant calls mandant.v1.MandantService.SetCurrentTenant.
+func (c *mandantServiceClient) SetCurrentTenant(ctx context.Context, req *v1.SetCurrentTenantRequest) (*v1.SetCurrentTenantResponse, error) {
+ response, err := c.setCurrentTenant.CallUnary(ctx, connect.NewRequest(req))
+ if response != nil {
+ return response.Msg, err
+ }
+ return nil, err
+}
+
+// MandantServiceHandler is an implementation of the mandant.v1.MandantService service.
+type MandantServiceHandler interface {
+ GetCurrentTenant(context.Context, *v1.GetCurrentTenantRequest) (*v1.GetTenantResponse, error)
+ GetAllTenants(context.Context, *v1.ListTenantRequest) (*v1.ListProjectsResponse, error)
+ SetCurrentTenant(context.Context, *v1.SetCurrentTenantRequest) (*v1.SetCurrentTenantResponse, error)
+}
+
+// NewMandantServiceHandler builds an HTTP handler from the service implementation. It returns the
+// path on which to mount the handler and the handler itself.
+//
+// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
+// and JSON codecs. They also support gzip compression.
+func NewMandantServiceHandler(svc MandantServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
+ mandantServiceMethods := v1.File_mandant_v1_mandant_proto.Services().ByName("MandantService").Methods()
+ mandantServiceGetCurrentTenantHandler := connect.NewUnaryHandlerSimple(
+ MandantServiceGetCurrentTenantProcedure,
+ svc.GetCurrentTenant,
+ connect.WithSchema(mandantServiceMethods.ByName("GetCurrentTenant")),
+ connect.WithHandlerOptions(opts...),
+ )
+ mandantServiceGetAllTenantsHandler := connect.NewUnaryHandlerSimple(
+ MandantServiceGetAllTenantsProcedure,
+ svc.GetAllTenants,
+ connect.WithSchema(mandantServiceMethods.ByName("GetAllTenants")),
+ connect.WithHandlerOptions(opts...),
+ )
+ mandantServiceSetCurrentTenantHandler := connect.NewUnaryHandlerSimple(
+ MandantServiceSetCurrentTenantProcedure,
+ svc.SetCurrentTenant,
+ connect.WithSchema(mandantServiceMethods.ByName("SetCurrentTenant")),
+ connect.WithHandlerOptions(opts...),
+ )
+ return "/mandant.v1.MandantService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ switch r.URL.Path {
+ case MandantServiceGetCurrentTenantProcedure:
+ mandantServiceGetCurrentTenantHandler.ServeHTTP(w, r)
+ case MandantServiceGetAllTenantsProcedure:
+ mandantServiceGetAllTenantsHandler.ServeHTTP(w, r)
+ case MandantServiceSetCurrentTenantProcedure:
+ mandantServiceSetCurrentTenantHandler.ServeHTTP(w, r)
+ default:
+ http.NotFound(w, r)
+ }
+ })
+}
+
+// UnimplementedMandantServiceHandler returns CodeUnimplemented from all methods.
+type UnimplementedMandantServiceHandler struct{}
+
+func (UnimplementedMandantServiceHandler) GetCurrentTenant(context.Context, *v1.GetCurrentTenantRequest) (*v1.GetTenantResponse, error) {
+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mandant.v1.MandantService.GetCurrentTenant is not implemented"))
+}
+
+func (UnimplementedMandantServiceHandler) GetAllTenants(context.Context, *v1.ListTenantRequest) (*v1.ListProjectsResponse, error) {
+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mandant.v1.MandantService.GetAllTenants is not implemented"))
+}
+
+func (UnimplementedMandantServiceHandler) SetCurrentTenant(context.Context, *v1.SetCurrentTenantRequest) (*v1.SetCurrentTenantResponse, error) {
+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("mandant.v1.MandantService.SetCurrentTenant is not implemented"))
+}
diff --git a/gen/messagebus/v1/messagebus.pb.go b/gen/messagebus/v1/messagebus.pb.go
new file mode 100644
index 0000000..7780282
--- /dev/null
+++ b/gen/messagebus/v1/messagebus.pb.go
@@ -0,0 +1,217 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.36.10
+// protoc (unknown)
+// source: messagebus/v1/messagebus.proto
+
+package messagebusv1
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+ unsafe "unsafe"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type MessageBusEntityType int32
+
+const (
+ MessageBusEntityType_OTHER MessageBusEntityType = 0
+ MessageBusEntityType_INVALIDATION_REQUEST MessageBusEntityType = 1
+)
+
+// Enum value maps for MessageBusEntityType.
+var (
+ MessageBusEntityType_name = map[int32]string{
+ 0: "OTHER",
+ 1: "INVALIDATION_REQUEST",
+ }
+ MessageBusEntityType_value = map[string]int32{
+ "OTHER": 0,
+ "INVALIDATION_REQUEST": 1,
+ }
+)
+
+func (x MessageBusEntityType) Enum() *MessageBusEntityType {
+ p := new(MessageBusEntityType)
+ *p = x
+ return p
+}
+
+func (x MessageBusEntityType) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (MessageBusEntityType) Descriptor() protoreflect.EnumDescriptor {
+ return file_messagebus_v1_messagebus_proto_enumTypes[0].Descriptor()
+}
+
+func (MessageBusEntityType) Type() protoreflect.EnumType {
+ return &file_messagebus_v1_messagebus_proto_enumTypes[0]
+}
+
+func (x MessageBusEntityType) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use MessageBusEntityType.Descriptor instead.
+func (MessageBusEntityType) EnumDescriptor() ([]byte, []int) {
+ return file_messagebus_v1_messagebus_proto_rawDescGZIP(), []int{0}
+}
+
+type MessageBusEntity struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ QueryKey string `protobuf:"bytes,1,opt,name=queryKey,proto3" json:"queryKey,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *MessageBusEntity) Reset() {
+ *x = MessageBusEntity{}
+ mi := &file_messagebus_v1_messagebus_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *MessageBusEntity) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MessageBusEntity) ProtoMessage() {}
+
+func (x *MessageBusEntity) ProtoReflect() protoreflect.Message {
+ mi := &file_messagebus_v1_messagebus_proto_msgTypes[0]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use MessageBusEntity.ProtoReflect.Descriptor instead.
+func (*MessageBusEntity) Descriptor() ([]byte, []int) {
+ return file_messagebus_v1_messagebus_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *MessageBusEntity) GetQueryKey() string {
+ if x != nil {
+ return x.QueryKey
+ }
+ return ""
+}
+
+type SubscribeToConnectInvalidationRequestsRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *SubscribeToConnectInvalidationRequestsRequest) Reset() {
+ *x = SubscribeToConnectInvalidationRequestsRequest{}
+ mi := &file_messagebus_v1_messagebus_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *SubscribeToConnectInvalidationRequestsRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SubscribeToConnectInvalidationRequestsRequest) ProtoMessage() {}
+
+func (x *SubscribeToConnectInvalidationRequestsRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_messagebus_v1_messagebus_proto_msgTypes[1]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use SubscribeToConnectInvalidationRequestsRequest.ProtoReflect.Descriptor instead.
+func (*SubscribeToConnectInvalidationRequestsRequest) Descriptor() ([]byte, []int) {
+ return file_messagebus_v1_messagebus_proto_rawDescGZIP(), []int{1}
+}
+
+var File_messagebus_v1_messagebus_proto protoreflect.FileDescriptor
+
+const file_messagebus_v1_messagebus_proto_rawDesc = "" +
+ "\n" +
+ "\x1emessagebus/v1/messagebus.proto\x12\rmessagebus.v1\".\n" +
+ "\x10MessageBusEntity\x12\x1a\n" +
+ "\bqueryKey\x18\x01 \x01(\tR\bqueryKey\"/\n" +
+ "-SubscribeToConnectInvalidationRequestsRequest*;\n" +
+ "\x14MessageBusEntityType\x12\t\n" +
+ "\x05OTHER\x10\x00\x12\x18\n" +
+ "\x14INVALIDATION_REQUEST\x10\x012\x9f\x01\n" +
+ "\x11MessageBusService\x12\x89\x01\n" +
+ "&SubscribeToConnectInvalidationRequests\x12<.messagebus.v1.SubscribeToConnectInvalidationRequestsRequest\x1a\x1f.messagebus.v1.MessageBusEntity0\x01B\xb4\x01\n" +
+ "\x11com.messagebus.v1B\x0fMessagebusProtoP\x01Z9git.kocoder.xyz/kocoded/vt/gen/messagebus/v1;messagebusv1\xa2\x02\x03MXX\xaa\x02\rMessagebus.V1\xca\x02\rMessagebus\\V1\xe2\x02\x19Messagebus\\V1\\GPBMetadata\xea\x02\x0eMessagebus::V1b\x06proto3"
+
+var (
+ file_messagebus_v1_messagebus_proto_rawDescOnce sync.Once
+ file_messagebus_v1_messagebus_proto_rawDescData []byte
+)
+
+func file_messagebus_v1_messagebus_proto_rawDescGZIP() []byte {
+ file_messagebus_v1_messagebus_proto_rawDescOnce.Do(func() {
+ file_messagebus_v1_messagebus_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_messagebus_v1_messagebus_proto_rawDesc), len(file_messagebus_v1_messagebus_proto_rawDesc)))
+ })
+ return file_messagebus_v1_messagebus_proto_rawDescData
+}
+
+var file_messagebus_v1_messagebus_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_messagebus_v1_messagebus_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_messagebus_v1_messagebus_proto_goTypes = []any{
+ (MessageBusEntityType)(0), // 0: messagebus.v1.MessageBusEntityType
+ (*MessageBusEntity)(nil), // 1: messagebus.v1.MessageBusEntity
+ (*SubscribeToConnectInvalidationRequestsRequest)(nil), // 2: messagebus.v1.SubscribeToConnectInvalidationRequestsRequest
+}
+var file_messagebus_v1_messagebus_proto_depIdxs = []int32{
+ 2, // 0: messagebus.v1.MessageBusService.SubscribeToConnectInvalidationRequests:input_type -> messagebus.v1.SubscribeToConnectInvalidationRequestsRequest
+ 1, // 1: messagebus.v1.MessageBusService.SubscribeToConnectInvalidationRequests:output_type -> messagebus.v1.MessageBusEntity
+ 1, // [1:2] is the sub-list for method output_type
+ 0, // [0:1] is the sub-list for method input_type
+ 0, // [0:0] is the sub-list for extension type_name
+ 0, // [0:0] is the sub-list for extension extendee
+ 0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_messagebus_v1_messagebus_proto_init() }
+func file_messagebus_v1_messagebus_proto_init() {
+ if File_messagebus_v1_messagebus_proto != nil {
+ return
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: unsafe.Slice(unsafe.StringData(file_messagebus_v1_messagebus_proto_rawDesc), len(file_messagebus_v1_messagebus_proto_rawDesc)),
+ NumEnums: 1,
+ NumMessages: 2,
+ NumExtensions: 0,
+ NumServices: 1,
+ },
+ GoTypes: file_messagebus_v1_messagebus_proto_goTypes,
+ DependencyIndexes: file_messagebus_v1_messagebus_proto_depIdxs,
+ EnumInfos: file_messagebus_v1_messagebus_proto_enumTypes,
+ MessageInfos: file_messagebus_v1_messagebus_proto_msgTypes,
+ }.Build()
+ File_messagebus_v1_messagebus_proto = out.File
+ file_messagebus_v1_messagebus_proto_goTypes = nil
+ file_messagebus_v1_messagebus_proto_depIdxs = nil
+}
diff --git a/gen/messagebus/v1/messagebusv1connect/messagebus.connect.go b/gen/messagebus/v1/messagebusv1connect/messagebus.connect.go
new file mode 100644
index 0000000..e57fce0
--- /dev/null
+++ b/gen/messagebus/v1/messagebusv1connect/messagebus.connect.go
@@ -0,0 +1,110 @@
+// Code generated by protoc-gen-connect-go. DO NOT EDIT.
+//
+// Source: messagebus/v1/messagebus.proto
+
+package messagebusv1connect
+
+import (
+ connect "connectrpc.com/connect"
+ context "context"
+ errors "errors"
+ v1 "git.kocoder.xyz/kocoded/vt/gen/messagebus/v1"
+ http "net/http"
+ strings "strings"
+)
+
+// This is a compile-time assertion to ensure that this generated file and the connect package are
+// compatible. If you get a compiler error that this constant is not defined, this code was
+// generated with a version of connect newer than the one compiled into your binary. You can fix the
+// problem by either regenerating this code with an older version of connect or updating the connect
+// version compiled into your binary.
+const _ = connect.IsAtLeastVersion1_13_0
+
+const (
+ // MessageBusServiceName is the fully-qualified name of the MessageBusService service.
+ MessageBusServiceName = "messagebus.v1.MessageBusService"
+)
+
+// These constants are the fully-qualified names of the RPCs defined in this package. They're
+// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
+//
+// Note that these are different from the fully-qualified method names used by
+// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
+// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
+// period.
+const (
+ // MessageBusServiceSubscribeToConnectInvalidationRequestsProcedure is the fully-qualified name of
+ // the MessageBusService's SubscribeToConnectInvalidationRequests RPC.
+ MessageBusServiceSubscribeToConnectInvalidationRequestsProcedure = "/messagebus.v1.MessageBusService/SubscribeToConnectInvalidationRequests"
+)
+
+// MessageBusServiceClient is a client for the messagebus.v1.MessageBusService service.
+type MessageBusServiceClient interface {
+ SubscribeToConnectInvalidationRequests(context.Context, *v1.SubscribeToConnectInvalidationRequestsRequest) (*connect.ServerStreamForClient[v1.MessageBusEntity], error)
+}
+
+// NewMessageBusServiceClient constructs a client for the messagebus.v1.MessageBusService service.
+// By default, it uses the Connect protocol with the binary Protobuf Codec, asks for gzipped
+// responses, and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the
+// connect.WithGRPC() or connect.WithGRPCWeb() options.
+//
+// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
+// http://api.acme.com or https://acme.com/grpc).
+func NewMessageBusServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) MessageBusServiceClient {
+ baseURL = strings.TrimRight(baseURL, "/")
+ messageBusServiceMethods := v1.File_messagebus_v1_messagebus_proto.Services().ByName("MessageBusService").Methods()
+ return &messageBusServiceClient{
+ subscribeToConnectInvalidationRequests: connect.NewClient[v1.SubscribeToConnectInvalidationRequestsRequest, v1.MessageBusEntity](
+ httpClient,
+ baseURL+MessageBusServiceSubscribeToConnectInvalidationRequestsProcedure,
+ connect.WithSchema(messageBusServiceMethods.ByName("SubscribeToConnectInvalidationRequests")),
+ connect.WithClientOptions(opts...),
+ ),
+ }
+}
+
+// messageBusServiceClient implements MessageBusServiceClient.
+type messageBusServiceClient struct {
+ subscribeToConnectInvalidationRequests *connect.Client[v1.SubscribeToConnectInvalidationRequestsRequest, v1.MessageBusEntity]
+}
+
+// SubscribeToConnectInvalidationRequests calls
+// messagebus.v1.MessageBusService.SubscribeToConnectInvalidationRequests.
+func (c *messageBusServiceClient) SubscribeToConnectInvalidationRequests(ctx context.Context, req *v1.SubscribeToConnectInvalidationRequestsRequest) (*connect.ServerStreamForClient[v1.MessageBusEntity], error) {
+ return c.subscribeToConnectInvalidationRequests.CallServerStream(ctx, connect.NewRequest(req))
+}
+
+// MessageBusServiceHandler is an implementation of the messagebus.v1.MessageBusService service.
+type MessageBusServiceHandler interface {
+ SubscribeToConnectInvalidationRequests(context.Context, *v1.SubscribeToConnectInvalidationRequestsRequest, *connect.ServerStream[v1.MessageBusEntity]) error
+}
+
+// NewMessageBusServiceHandler builds an HTTP handler from the service implementation. It returns
+// the path on which to mount the handler and the handler itself.
+//
+// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
+// and JSON codecs. They also support gzip compression.
+func NewMessageBusServiceHandler(svc MessageBusServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
+ messageBusServiceMethods := v1.File_messagebus_v1_messagebus_proto.Services().ByName("MessageBusService").Methods()
+ messageBusServiceSubscribeToConnectInvalidationRequestsHandler := connect.NewServerStreamHandlerSimple(
+ MessageBusServiceSubscribeToConnectInvalidationRequestsProcedure,
+ svc.SubscribeToConnectInvalidationRequests,
+ connect.WithSchema(messageBusServiceMethods.ByName("SubscribeToConnectInvalidationRequests")),
+ connect.WithHandlerOptions(opts...),
+ )
+ return "/messagebus.v1.MessageBusService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ switch r.URL.Path {
+ case MessageBusServiceSubscribeToConnectInvalidationRequestsProcedure:
+ messageBusServiceSubscribeToConnectInvalidationRequestsHandler.ServeHTTP(w, r)
+ default:
+ http.NotFound(w, r)
+ }
+ })
+}
+
+// UnimplementedMessageBusServiceHandler returns CodeUnimplemented from all methods.
+type UnimplementedMessageBusServiceHandler struct{}
+
+func (UnimplementedMessageBusServiceHandler) SubscribeToConnectInvalidationRequests(context.Context, *v1.SubscribeToConnectInvalidationRequestsRequest, *connect.ServerStream[v1.MessageBusEntity]) error {
+ return connect.NewError(connect.CodeUnimplemented, errors.New("messagebus.v1.MessageBusService.SubscribeToConnectInvalidationRequests is not implemented"))
+}
diff --git a/gen/project/v1/project.pb.go b/gen/project/v1/project.pb.go
new file mode 100644
index 0000000..09d3b83
--- /dev/null
+++ b/gen/project/v1/project.pb.go
@@ -0,0 +1,433 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.36.10
+// protoc (unknown)
+// source: project/v1/project.proto
+
+package projectv1
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+ unsafe "unsafe"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type GetProjectRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *GetProjectRequest) Reset() {
+ *x = GetProjectRequest{}
+ mi := &file_project_v1_project_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *GetProjectRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetProjectRequest) ProtoMessage() {}
+
+func (x *GetProjectRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_project_v1_project_proto_msgTypes[0]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetProjectRequest.ProtoReflect.Descriptor instead.
+func (*GetProjectRequest) Descriptor() ([]byte, []int) {
+ return file_project_v1_project_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *GetProjectRequest) GetId() int32 {
+ if x != nil {
+ return x.Id
+ }
+ return 0
+}
+
+type GetProjectResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
+ Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
+ Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"`
+ IsMaterialized *bool `protobuf:"varint,5,opt,name=is_materialized,json=isMaterialized,proto3,oneof" json:"is_materialized,omitempty"`
+ IsPersonalized *bool `protobuf:"varint,6,opt,name=is_personalized,json=isPersonalized,proto3,oneof" json:"is_personalized,omitempty"`
+ IsConfirmed *bool `protobuf:"varint,7,opt,name=is_confirmed,json=isConfirmed,proto3,oneof" json:"is_confirmed,omitempty"`
+ IsPaid *bool `protobuf:"varint,8,opt,name=is_paid,json=isPaid,proto3,oneof" json:"is_paid,omitempty"`
+ IsDone *bool `protobuf:"varint,9,opt,name=is_done,json=isDone,proto3,oneof" json:"is_done,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *GetProjectResponse) Reset() {
+ *x = GetProjectResponse{}
+ mi := &file_project_v1_project_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *GetProjectResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetProjectResponse) ProtoMessage() {}
+
+func (x *GetProjectResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_project_v1_project_proto_msgTypes[1]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetProjectResponse.ProtoReflect.Descriptor instead.
+func (*GetProjectResponse) Descriptor() ([]byte, []int) {
+ return file_project_v1_project_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *GetProjectResponse) GetId() int64 {
+ if x != nil {
+ return x.Id
+ }
+ return 0
+}
+
+func (x *GetProjectResponse) GetName() string {
+ if x != nil {
+ return x.Name
+ }
+ return ""
+}
+
+func (x *GetProjectResponse) GetDescription() string {
+ if x != nil {
+ return x.Description
+ }
+ return ""
+}
+
+func (x *GetProjectResponse) GetIsMaterialized() bool {
+ if x != nil && x.IsMaterialized != nil {
+ return *x.IsMaterialized
+ }
+ return false
+}
+
+func (x *GetProjectResponse) GetIsPersonalized() bool {
+ if x != nil && x.IsPersonalized != nil {
+ return *x.IsPersonalized
+ }
+ return false
+}
+
+func (x *GetProjectResponse) GetIsConfirmed() bool {
+ if x != nil && x.IsConfirmed != nil {
+ return *x.IsConfirmed
+ }
+ return false
+}
+
+func (x *GetProjectResponse) GetIsPaid() bool {
+ if x != nil && x.IsPaid != nil {
+ return *x.IsPaid
+ }
+ return false
+}
+
+func (x *GetProjectResponse) GetIsDone() bool {
+ if x != nil && x.IsDone != nil {
+ return *x.IsDone
+ }
+ return false
+}
+
+type ListProjectsRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Page int32 `protobuf:"varint,1,opt,name=page,proto3" json:"page,omitempty"`
+ PerPage int32 `protobuf:"varint,2,opt,name=per_page,json=perPage,proto3" json:"per_page,omitempty"`
+ OrberBy string `protobuf:"bytes,3,opt,name=orber_by,json=orberBy,proto3" json:"orber_by,omitempty"`
+ Asc bool `protobuf:"varint,4,opt,name=asc,proto3" json:"asc,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ListProjectsRequest) Reset() {
+ *x = ListProjectsRequest{}
+ mi := &file_project_v1_project_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ListProjectsRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ListProjectsRequest) ProtoMessage() {}
+
+func (x *ListProjectsRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_project_v1_project_proto_msgTypes[2]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ListProjectsRequest.ProtoReflect.Descriptor instead.
+func (*ListProjectsRequest) Descriptor() ([]byte, []int) {
+ return file_project_v1_project_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *ListProjectsRequest) GetPage() int32 {
+ if x != nil {
+ return x.Page
+ }
+ return 0
+}
+
+func (x *ListProjectsRequest) GetPerPage() int32 {
+ if x != nil {
+ return x.PerPage
+ }
+ return 0
+}
+
+func (x *ListProjectsRequest) GetOrberBy() string {
+ if x != nil {
+ return x.OrberBy
+ }
+ return ""
+}
+
+func (x *ListProjectsRequest) GetAsc() bool {
+ if x != nil {
+ return x.Asc
+ }
+ return false
+}
+
+type Metadata struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ TotalCount int32 `protobuf:"varint,1,opt,name=totalCount,proto3" json:"totalCount,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Metadata) Reset() {
+ *x = Metadata{}
+ mi := &file_project_v1_project_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Metadata) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Metadata) ProtoMessage() {}
+
+func (x *Metadata) ProtoReflect() protoreflect.Message {
+ mi := &file_project_v1_project_proto_msgTypes[3]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Metadata.ProtoReflect.Descriptor instead.
+func (*Metadata) Descriptor() ([]byte, []int) {
+ return file_project_v1_project_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *Metadata) GetTotalCount() int32 {
+ if x != nil {
+ return x.TotalCount
+ }
+ return 0
+}
+
+type ListProjectsResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Data []*GetProjectResponse `protobuf:"bytes,1,rep,name=data,proto3" json:"data,omitempty"`
+ Meta *Metadata `protobuf:"bytes,2,opt,name=meta,proto3" json:"meta,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ListProjectsResponse) Reset() {
+ *x = ListProjectsResponse{}
+ mi := &file_project_v1_project_proto_msgTypes[4]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ListProjectsResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ListProjectsResponse) ProtoMessage() {}
+
+func (x *ListProjectsResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_project_v1_project_proto_msgTypes[4]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ListProjectsResponse.ProtoReflect.Descriptor instead.
+func (*ListProjectsResponse) Descriptor() ([]byte, []int) {
+ return file_project_v1_project_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *ListProjectsResponse) GetData() []*GetProjectResponse {
+ if x != nil {
+ return x.Data
+ }
+ return nil
+}
+
+func (x *ListProjectsResponse) GetMeta() *Metadata {
+ if x != nil {
+ return x.Meta
+ }
+ return nil
+}
+
+var File_project_v1_project_proto protoreflect.FileDescriptor
+
+const file_project_v1_project_proto_rawDesc = "" +
+ "\n" +
+ "\x18project/v1/project.proto\x12\n" +
+ "project.v1\"#\n" +
+ "\x11GetProjectRequest\x12\x0e\n" +
+ "\x02id\x18\x01 \x01(\x05R\x02id\"\xeb\x02\n" +
+ "\x12GetProjectResponse\x12\x0e\n" +
+ "\x02id\x18\x01 \x01(\x03R\x02id\x12\x12\n" +
+ "\x04name\x18\x02 \x01(\tR\x04name\x12 \n" +
+ "\vdescription\x18\x03 \x01(\tR\vdescription\x12,\n" +
+ "\x0fis_materialized\x18\x05 \x01(\bH\x00R\x0eisMaterialized\x88\x01\x01\x12,\n" +
+ "\x0fis_personalized\x18\x06 \x01(\bH\x01R\x0eisPersonalized\x88\x01\x01\x12&\n" +
+ "\fis_confirmed\x18\a \x01(\bH\x02R\visConfirmed\x88\x01\x01\x12\x1c\n" +
+ "\ais_paid\x18\b \x01(\bH\x03R\x06isPaid\x88\x01\x01\x12\x1c\n" +
+ "\ais_done\x18\t \x01(\bH\x04R\x06isDone\x88\x01\x01B\x12\n" +
+ "\x10_is_materializedB\x12\n" +
+ "\x10_is_personalizedB\x0f\n" +
+ "\r_is_confirmedB\n" +
+ "\n" +
+ "\b_is_paidB\n" +
+ "\n" +
+ "\b_is_done\"q\n" +
+ "\x13ListProjectsRequest\x12\x12\n" +
+ "\x04page\x18\x01 \x01(\x05R\x04page\x12\x19\n" +
+ "\bper_page\x18\x02 \x01(\x05R\aperPage\x12\x19\n" +
+ "\borber_by\x18\x03 \x01(\tR\aorberBy\x12\x10\n" +
+ "\x03asc\x18\x04 \x01(\bR\x03asc\"*\n" +
+ "\bMetadata\x12\x1e\n" +
+ "\n" +
+ "totalCount\x18\x01 \x01(\x05R\n" +
+ "totalCount\"t\n" +
+ "\x14ListProjectsResponse\x122\n" +
+ "\x04data\x18\x01 \x03(\v2\x1e.project.v1.GetProjectResponseR\x04data\x12(\n" +
+ "\x04meta\x18\x02 \x01(\v2\x14.project.v1.MetadataR\x04meta2\xb0\x01\n" +
+ "\x0eProjectService\x12K\n" +
+ "\n" +
+ "GetProject\x12\x1d.project.v1.GetProjectRequest\x1a\x1e.project.v1.GetProjectResponse\x12Q\n" +
+ "\fListProjects\x12\x1f.project.v1.ListProjectsRequest\x1a .project.v1.ListProjectsResponseB\x9c\x01\n" +
+ "\x0ecom.project.v1B\fProjectProtoP\x01Z3git.kocoder.xyz/kocoded/vt/gen/project/v1;projectv1\xa2\x02\x03PXX\xaa\x02\n" +
+ "Project.V1\xca\x02\n" +
+ "Project\\V1\xe2\x02\x16Project\\V1\\GPBMetadata\xea\x02\vProject::V1b\x06proto3"
+
+var (
+ file_project_v1_project_proto_rawDescOnce sync.Once
+ file_project_v1_project_proto_rawDescData []byte
+)
+
+func file_project_v1_project_proto_rawDescGZIP() []byte {
+ file_project_v1_project_proto_rawDescOnce.Do(func() {
+ file_project_v1_project_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_project_v1_project_proto_rawDesc), len(file_project_v1_project_proto_rawDesc)))
+ })
+ return file_project_v1_project_proto_rawDescData
+}
+
+var file_project_v1_project_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
+var file_project_v1_project_proto_goTypes = []any{
+ (*GetProjectRequest)(nil), // 0: project.v1.GetProjectRequest
+ (*GetProjectResponse)(nil), // 1: project.v1.GetProjectResponse
+ (*ListProjectsRequest)(nil), // 2: project.v1.ListProjectsRequest
+ (*Metadata)(nil), // 3: project.v1.Metadata
+ (*ListProjectsResponse)(nil), // 4: project.v1.ListProjectsResponse
+}
+var file_project_v1_project_proto_depIdxs = []int32{
+ 1, // 0: project.v1.ListProjectsResponse.data:type_name -> project.v1.GetProjectResponse
+ 3, // 1: project.v1.ListProjectsResponse.meta:type_name -> project.v1.Metadata
+ 0, // 2: project.v1.ProjectService.GetProject:input_type -> project.v1.GetProjectRequest
+ 2, // 3: project.v1.ProjectService.ListProjects:input_type -> project.v1.ListProjectsRequest
+ 1, // 4: project.v1.ProjectService.GetProject:output_type -> project.v1.GetProjectResponse
+ 4, // 5: project.v1.ProjectService.ListProjects:output_type -> project.v1.ListProjectsResponse
+ 4, // [4:6] is the sub-list for method output_type
+ 2, // [2:4] is the sub-list for method input_type
+ 2, // [2:2] is the sub-list for extension type_name
+ 2, // [2:2] is the sub-list for extension extendee
+ 0, // [0:2] is the sub-list for field type_name
+}
+
+func init() { file_project_v1_project_proto_init() }
+func file_project_v1_project_proto_init() {
+ if File_project_v1_project_proto != nil {
+ return
+ }
+ file_project_v1_project_proto_msgTypes[1].OneofWrappers = []any{}
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: unsafe.Slice(unsafe.StringData(file_project_v1_project_proto_rawDesc), len(file_project_v1_project_proto_rawDesc)),
+ NumEnums: 0,
+ NumMessages: 5,
+ NumExtensions: 0,
+ NumServices: 1,
+ },
+ GoTypes: file_project_v1_project_proto_goTypes,
+ DependencyIndexes: file_project_v1_project_proto_depIdxs,
+ MessageInfos: file_project_v1_project_proto_msgTypes,
+ }.Build()
+ File_project_v1_project_proto = out.File
+ file_project_v1_project_proto_goTypes = nil
+ file_project_v1_project_proto_depIdxs = nil
+}
diff --git a/gen/project/v1/projectv1connect/project.connect.go b/gen/project/v1/projectv1connect/project.connect.go
new file mode 100644
index 0000000..5033145
--- /dev/null
+++ b/gen/project/v1/projectv1connect/project.connect.go
@@ -0,0 +1,146 @@
+// Code generated by protoc-gen-connect-go. DO NOT EDIT.
+//
+// Source: project/v1/project.proto
+
+package projectv1connect
+
+import (
+ connect "connectrpc.com/connect"
+ context "context"
+ errors "errors"
+ v1 "git.kocoder.xyz/kocoded/vt/gen/project/v1"
+ http "net/http"
+ strings "strings"
+)
+
+// This is a compile-time assertion to ensure that this generated file and the connect package are
+// compatible. If you get a compiler error that this constant is not defined, this code was
+// generated with a version of connect newer than the one compiled into your binary. You can fix the
+// problem by either regenerating this code with an older version of connect or updating the connect
+// version compiled into your binary.
+const _ = connect.IsAtLeastVersion1_13_0
+
+const (
+ // ProjectServiceName is the fully-qualified name of the ProjectService service.
+ ProjectServiceName = "project.v1.ProjectService"
+)
+
+// These constants are the fully-qualified names of the RPCs defined in this package. They're
+// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
+//
+// Note that these are different from the fully-qualified method names used by
+// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
+// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
+// period.
+const (
+ // ProjectServiceGetProjectProcedure is the fully-qualified name of the ProjectService's GetProject
+ // RPC.
+ ProjectServiceGetProjectProcedure = "/project.v1.ProjectService/GetProject"
+ // ProjectServiceListProjectsProcedure is the fully-qualified name of the ProjectService's
+ // ListProjects RPC.
+ ProjectServiceListProjectsProcedure = "/project.v1.ProjectService/ListProjects"
+)
+
+// ProjectServiceClient is a client for the project.v1.ProjectService service.
+type ProjectServiceClient interface {
+ GetProject(context.Context, *v1.GetProjectRequest) (*v1.GetProjectResponse, error)
+ ListProjects(context.Context, *v1.ListProjectsRequest) (*v1.ListProjectsResponse, error)
+}
+
+// NewProjectServiceClient constructs a client for the project.v1.ProjectService service. By
+// default, it uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses,
+// and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the
+// connect.WithGRPC() or connect.WithGRPCWeb() options.
+//
+// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
+// http://api.acme.com or https://acme.com/grpc).
+func NewProjectServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) ProjectServiceClient {
+ baseURL = strings.TrimRight(baseURL, "/")
+ projectServiceMethods := v1.File_project_v1_project_proto.Services().ByName("ProjectService").Methods()
+ return &projectServiceClient{
+ getProject: connect.NewClient[v1.GetProjectRequest, v1.GetProjectResponse](
+ httpClient,
+ baseURL+ProjectServiceGetProjectProcedure,
+ connect.WithSchema(projectServiceMethods.ByName("GetProject")),
+ connect.WithClientOptions(opts...),
+ ),
+ listProjects: connect.NewClient[v1.ListProjectsRequest, v1.ListProjectsResponse](
+ httpClient,
+ baseURL+ProjectServiceListProjectsProcedure,
+ connect.WithSchema(projectServiceMethods.ByName("ListProjects")),
+ connect.WithClientOptions(opts...),
+ ),
+ }
+}
+
+// projectServiceClient implements ProjectServiceClient.
+type projectServiceClient struct {
+ getProject *connect.Client[v1.GetProjectRequest, v1.GetProjectResponse]
+ listProjects *connect.Client[v1.ListProjectsRequest, v1.ListProjectsResponse]
+}
+
+// GetProject calls project.v1.ProjectService.GetProject.
+func (c *projectServiceClient) GetProject(ctx context.Context, req *v1.GetProjectRequest) (*v1.GetProjectResponse, error) {
+ response, err := c.getProject.CallUnary(ctx, connect.NewRequest(req))
+ if response != nil {
+ return response.Msg, err
+ }
+ return nil, err
+}
+
+// ListProjects calls project.v1.ProjectService.ListProjects.
+func (c *projectServiceClient) ListProjects(ctx context.Context, req *v1.ListProjectsRequest) (*v1.ListProjectsResponse, error) {
+ response, err := c.listProjects.CallUnary(ctx, connect.NewRequest(req))
+ if response != nil {
+ return response.Msg, err
+ }
+ return nil, err
+}
+
+// ProjectServiceHandler is an implementation of the project.v1.ProjectService service.
+type ProjectServiceHandler interface {
+ GetProject(context.Context, *v1.GetProjectRequest) (*v1.GetProjectResponse, error)
+ ListProjects(context.Context, *v1.ListProjectsRequest) (*v1.ListProjectsResponse, error)
+}
+
+// NewProjectServiceHandler builds an HTTP handler from the service implementation. It returns the
+// path on which to mount the handler and the handler itself.
+//
+// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
+// and JSON codecs. They also support gzip compression.
+func NewProjectServiceHandler(svc ProjectServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
+ projectServiceMethods := v1.File_project_v1_project_proto.Services().ByName("ProjectService").Methods()
+ projectServiceGetProjectHandler := connect.NewUnaryHandlerSimple(
+ ProjectServiceGetProjectProcedure,
+ svc.GetProject,
+ connect.WithSchema(projectServiceMethods.ByName("GetProject")),
+ connect.WithHandlerOptions(opts...),
+ )
+ projectServiceListProjectsHandler := connect.NewUnaryHandlerSimple(
+ ProjectServiceListProjectsProcedure,
+ svc.ListProjects,
+ connect.WithSchema(projectServiceMethods.ByName("ListProjects")),
+ connect.WithHandlerOptions(opts...),
+ )
+ return "/project.v1.ProjectService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ switch r.URL.Path {
+ case ProjectServiceGetProjectProcedure:
+ projectServiceGetProjectHandler.ServeHTTP(w, r)
+ case ProjectServiceListProjectsProcedure:
+ projectServiceListProjectsHandler.ServeHTTP(w, r)
+ default:
+ http.NotFound(w, r)
+ }
+ })
+}
+
+// UnimplementedProjectServiceHandler returns CodeUnimplemented from all methods.
+type UnimplementedProjectServiceHandler struct{}
+
+func (UnimplementedProjectServiceHandler) GetProject(context.Context, *v1.GetProjectRequest) (*v1.GetProjectResponse, error) {
+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("project.v1.ProjectService.GetProject is not implemented"))
+}
+
+func (UnimplementedProjectServiceHandler) ListProjects(context.Context, *v1.ListProjectsRequest) (*v1.ListProjectsResponse, error) {
+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("project.v1.ProjectService.ListProjects is not implemented"))
+}
diff --git a/gen/todo/v1/todo.pb.go b/gen/todo/v1/todo.pb.go
new file mode 100644
index 0000000..b1ddbfa
--- /dev/null
+++ b/gen/todo/v1/todo.pb.go
@@ -0,0 +1,645 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.36.10
+// protoc (unknown)
+// source: todo/v1/todo.proto
+
+package todov1
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+ unsafe "unsafe"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type Status int32
+
+const (
+ Status_Todo Status = 0
+ Status_NeedsMoreInfo Status = 1
+ Status_Doing Status = 2
+ Status_Done Status = 3
+)
+
+// Enum value maps for Status.
+var (
+ Status_name = map[int32]string{
+ 0: "Todo",
+ 1: "NeedsMoreInfo",
+ 2: "Doing",
+ 3: "Done",
+ }
+ Status_value = map[string]int32{
+ "Todo": 0,
+ "NeedsMoreInfo": 1,
+ "Doing": 2,
+ "Done": 3,
+ }
+)
+
+func (x Status) Enum() *Status {
+ p := new(Status)
+ *p = x
+ return p
+}
+
+func (x Status) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (Status) Descriptor() protoreflect.EnumDescriptor {
+ return file_todo_v1_todo_proto_enumTypes[0].Descriptor()
+}
+
+func (Status) Type() protoreflect.EnumType {
+ return &file_todo_v1_todo_proto_enumTypes[0]
+}
+
+func (x Status) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use Status.Descriptor instead.
+func (Status) EnumDescriptor() ([]byte, []int) {
+ return file_todo_v1_todo_proto_rawDescGZIP(), []int{0}
+}
+
+type Field int32
+
+const (
+ Field_FieldId Field = 0
+ Field_FieldTitle Field = 1
+ Field_FieldDescription Field = 2
+ Field_FieldStatus Field = 3
+)
+
+// Enum value maps for Field.
+var (
+ Field_name = map[int32]string{
+ 0: "FieldId",
+ 1: "FieldTitle",
+ 2: "FieldDescription",
+ 3: "FieldStatus",
+ }
+ Field_value = map[string]int32{
+ "FieldId": 0,
+ "FieldTitle": 1,
+ "FieldDescription": 2,
+ "FieldStatus": 3,
+ }
+)
+
+func (x Field) Enum() *Field {
+ p := new(Field)
+ *p = x
+ return p
+}
+
+func (x Field) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (Field) Descriptor() protoreflect.EnumDescriptor {
+ return file_todo_v1_todo_proto_enumTypes[1].Descriptor()
+}
+
+func (Field) Type() protoreflect.EnumType {
+ return &file_todo_v1_todo_proto_enumTypes[1]
+}
+
+func (x Field) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use Field.Descriptor instead.
+func (Field) EnumDescriptor() ([]byte, []int) {
+ return file_todo_v1_todo_proto_rawDescGZIP(), []int{1}
+}
+
+type Operation int32
+
+const (
+ Operation_Equals Operation = 0
+ Operation_NotEquals Operation = 1
+ Operation_GreaterThan Operation = 2
+ Operation_LessThan Operation = 3
+ Operation_Like Operation = 4
+)
+
+// Enum value maps for Operation.
+var (
+ Operation_name = map[int32]string{
+ 0: "Equals",
+ 1: "NotEquals",
+ 2: "GreaterThan",
+ 3: "LessThan",
+ 4: "Like",
+ }
+ Operation_value = map[string]int32{
+ "Equals": 0,
+ "NotEquals": 1,
+ "GreaterThan": 2,
+ "LessThan": 3,
+ "Like": 4,
+ }
+)
+
+func (x Operation) Enum() *Operation {
+ p := new(Operation)
+ *p = x
+ return p
+}
+
+func (x Operation) String() string {
+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (Operation) Descriptor() protoreflect.EnumDescriptor {
+ return file_todo_v1_todo_proto_enumTypes[2].Descriptor()
+}
+
+func (Operation) Type() protoreflect.EnumType {
+ return &file_todo_v1_todo_proto_enumTypes[2]
+}
+
+func (x Operation) Number() protoreflect.EnumNumber {
+ return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use Operation.Descriptor instead.
+func (Operation) EnumDescriptor() ([]byte, []int) {
+ return file_todo_v1_todo_proto_rawDescGZIP(), []int{2}
+}
+
+type GetTodosRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Id int32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *GetTodosRequest) Reset() {
+ *x = GetTodosRequest{}
+ mi := &file_todo_v1_todo_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *GetTodosRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetTodosRequest) ProtoMessage() {}
+
+func (x *GetTodosRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_todo_v1_todo_proto_msgTypes[0]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetTodosRequest.ProtoReflect.Descriptor instead.
+func (*GetTodosRequest) Descriptor() ([]byte, []int) {
+ return file_todo_v1_todo_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *GetTodosRequest) GetId() int32 {
+ if x != nil {
+ return x.Id
+ }
+ return 0
+}
+
+type GetTodosResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
+ Title string `protobuf:"bytes,2,opt,name=title,proto3" json:"title,omitempty"`
+ Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"`
+ Status Status `protobuf:"varint,4,opt,name=status,proto3,enum=todo.v1.Status" json:"status,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *GetTodosResponse) Reset() {
+ *x = GetTodosResponse{}
+ mi := &file_todo_v1_todo_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *GetTodosResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GetTodosResponse) ProtoMessage() {}
+
+func (x *GetTodosResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_todo_v1_todo_proto_msgTypes[1]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use GetTodosResponse.ProtoReflect.Descriptor instead.
+func (*GetTodosResponse) Descriptor() ([]byte, []int) {
+ return file_todo_v1_todo_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *GetTodosResponse) GetId() int64 {
+ if x != nil {
+ return x.Id
+ }
+ return 0
+}
+
+func (x *GetTodosResponse) GetTitle() string {
+ if x != nil {
+ return x.Title
+ }
+ return ""
+}
+
+func (x *GetTodosResponse) GetDescription() string {
+ if x != nil {
+ return x.Description
+ }
+ return ""
+}
+
+func (x *GetTodosResponse) GetStatus() Status {
+ if x != nil {
+ return x.Status
+ }
+ return Status_Todo
+}
+
+type ListTodosRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Page int32 `protobuf:"varint,1,opt,name=page,proto3" json:"page,omitempty"`
+ PerPage int32 `protobuf:"varint,2,opt,name=per_page,json=perPage,proto3" json:"per_page,omitempty"`
+ OrberBy string `protobuf:"bytes,3,opt,name=orber_by,json=orberBy,proto3" json:"orber_by,omitempty"`
+ Asc bool `protobuf:"varint,4,opt,name=asc,proto3" json:"asc,omitempty"`
+ Filters []*Filter `protobuf:"bytes,5,rep,name=filters,proto3" json:"filters,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ListTodosRequest) Reset() {
+ *x = ListTodosRequest{}
+ mi := &file_todo_v1_todo_proto_msgTypes[2]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ListTodosRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ListTodosRequest) ProtoMessage() {}
+
+func (x *ListTodosRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_todo_v1_todo_proto_msgTypes[2]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ListTodosRequest.ProtoReflect.Descriptor instead.
+func (*ListTodosRequest) Descriptor() ([]byte, []int) {
+ return file_todo_v1_todo_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *ListTodosRequest) GetPage() int32 {
+ if x != nil {
+ return x.Page
+ }
+ return 0
+}
+
+func (x *ListTodosRequest) GetPerPage() int32 {
+ if x != nil {
+ return x.PerPage
+ }
+ return 0
+}
+
+func (x *ListTodosRequest) GetOrberBy() string {
+ if x != nil {
+ return x.OrberBy
+ }
+ return ""
+}
+
+func (x *ListTodosRequest) GetAsc() bool {
+ if x != nil {
+ return x.Asc
+ }
+ return false
+}
+
+func (x *ListTodosRequest) GetFilters() []*Filter {
+ if x != nil {
+ return x.Filters
+ }
+ return nil
+}
+
+type Filter struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Field Field `protobuf:"varint,1,opt,name=field,proto3,enum=todo.v1.Field" json:"field,omitempty"`
+ Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
+ Operation Operation `protobuf:"varint,3,opt,name=operation,proto3,enum=todo.v1.Operation" json:"operation,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Filter) Reset() {
+ *x = Filter{}
+ mi := &file_todo_v1_todo_proto_msgTypes[3]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Filter) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Filter) ProtoMessage() {}
+
+func (x *Filter) ProtoReflect() protoreflect.Message {
+ mi := &file_todo_v1_todo_proto_msgTypes[3]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Filter.ProtoReflect.Descriptor instead.
+func (*Filter) Descriptor() ([]byte, []int) {
+ return file_todo_v1_todo_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *Filter) GetField() Field {
+ if x != nil {
+ return x.Field
+ }
+ return Field_FieldId
+}
+
+func (x *Filter) GetValue() string {
+ if x != nil {
+ return x.Value
+ }
+ return ""
+}
+
+func (x *Filter) GetOperation() Operation {
+ if x != nil {
+ return x.Operation
+ }
+ return Operation_Equals
+}
+
+type Metadata struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ TotalCount int32 `protobuf:"varint,1,opt,name=totalCount,proto3" json:"totalCount,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *Metadata) Reset() {
+ *x = Metadata{}
+ mi := &file_todo_v1_todo_proto_msgTypes[4]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *Metadata) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Metadata) ProtoMessage() {}
+
+func (x *Metadata) ProtoReflect() protoreflect.Message {
+ mi := &file_todo_v1_todo_proto_msgTypes[4]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Metadata.ProtoReflect.Descriptor instead.
+func (*Metadata) Descriptor() ([]byte, []int) {
+ return file_todo_v1_todo_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *Metadata) GetTotalCount() int32 {
+ if x != nil {
+ return x.TotalCount
+ }
+ return 0
+}
+
+type ListTodosResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Data []*GetTodosResponse `protobuf:"bytes,1,rep,name=data,proto3" json:"data,omitempty"`
+ Meta *Metadata `protobuf:"bytes,2,opt,name=meta,proto3" json:"meta,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ListTodosResponse) Reset() {
+ *x = ListTodosResponse{}
+ mi := &file_todo_v1_todo_proto_msgTypes[5]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ListTodosResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ListTodosResponse) ProtoMessage() {}
+
+func (x *ListTodosResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_todo_v1_todo_proto_msgTypes[5]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ListTodosResponse.ProtoReflect.Descriptor instead.
+func (*ListTodosResponse) Descriptor() ([]byte, []int) {
+ return file_todo_v1_todo_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *ListTodosResponse) GetData() []*GetTodosResponse {
+ if x != nil {
+ return x.Data
+ }
+ return nil
+}
+
+func (x *ListTodosResponse) GetMeta() *Metadata {
+ if x != nil {
+ return x.Meta
+ }
+ return nil
+}
+
+var File_todo_v1_todo_proto protoreflect.FileDescriptor
+
+const file_todo_v1_todo_proto_rawDesc = "" +
+ "\n" +
+ "\x12todo/v1/todo.proto\x12\atodo.v1\"!\n" +
+ "\x0fGetTodosRequest\x12\x0e\n" +
+ "\x02id\x18\x01 \x01(\x05R\x02id\"\x83\x01\n" +
+ "\x10GetTodosResponse\x12\x0e\n" +
+ "\x02id\x18\x01 \x01(\x03R\x02id\x12\x14\n" +
+ "\x05title\x18\x02 \x01(\tR\x05title\x12 \n" +
+ "\vdescription\x18\x03 \x01(\tR\vdescription\x12'\n" +
+ "\x06status\x18\x04 \x01(\x0e2\x0f.todo.v1.StatusR\x06status\"\x99\x01\n" +
+ "\x10ListTodosRequest\x12\x12\n" +
+ "\x04page\x18\x01 \x01(\x05R\x04page\x12\x19\n" +
+ "\bper_page\x18\x02 \x01(\x05R\aperPage\x12\x19\n" +
+ "\borber_by\x18\x03 \x01(\tR\aorberBy\x12\x10\n" +
+ "\x03asc\x18\x04 \x01(\bR\x03asc\x12)\n" +
+ "\afilters\x18\x05 \x03(\v2\x0f.todo.v1.FilterR\afilters\"v\n" +
+ "\x06Filter\x12$\n" +
+ "\x05field\x18\x01 \x01(\x0e2\x0e.todo.v1.FieldR\x05field\x12\x14\n" +
+ "\x05value\x18\x02 \x01(\tR\x05value\x120\n" +
+ "\toperation\x18\x03 \x01(\x0e2\x12.todo.v1.OperationR\toperation\"*\n" +
+ "\bMetadata\x12\x1e\n" +
+ "\n" +
+ "totalCount\x18\x01 \x01(\x05R\n" +
+ "totalCount\"i\n" +
+ "\x11ListTodosResponse\x12-\n" +
+ "\x04data\x18\x01 \x03(\v2\x19.todo.v1.GetTodosResponseR\x04data\x12%\n" +
+ "\x04meta\x18\x02 \x01(\v2\x11.todo.v1.MetadataR\x04meta*:\n" +
+ "\x06Status\x12\b\n" +
+ "\x04Todo\x10\x00\x12\x11\n" +
+ "\rNeedsMoreInfo\x10\x01\x12\t\n" +
+ "\x05Doing\x10\x02\x12\b\n" +
+ "\x04Done\x10\x03*K\n" +
+ "\x05Field\x12\v\n" +
+ "\aFieldId\x10\x00\x12\x0e\n" +
+ "\n" +
+ "FieldTitle\x10\x01\x12\x14\n" +
+ "\x10FieldDescription\x10\x02\x12\x0f\n" +
+ "\vFieldStatus\x10\x03*O\n" +
+ "\tOperation\x12\n" +
+ "\n" +
+ "\x06Equals\x10\x00\x12\r\n" +
+ "\tNotEquals\x10\x01\x12\x0f\n" +
+ "\vGreaterThan\x10\x02\x12\f\n" +
+ "\bLessThan\x10\x03\x12\b\n" +
+ "\x04Like\x10\x042\x91\x01\n" +
+ "\vTodoService\x12>\n" +
+ "\aGetTodo\x12\x18.todo.v1.GetTodosRequest\x1a\x19.todo.v1.GetTodosResponse\x12B\n" +
+ "\tListTodos\x12\x19.todo.v1.ListTodosRequest\x1a\x1a.todo.v1.ListTodosResponseB\x84\x01\n" +
+ "\vcom.todo.v1B\tTodoProtoP\x01Z-git.kocoder.xyz/kocoded/vt/gen/todo/v1;todov1\xa2\x02\x03TXX\xaa\x02\aTodo.V1\xca\x02\aTodo\\V1\xe2\x02\x13Todo\\V1\\GPBMetadata\xea\x02\bTodo::V1b\x06proto3"
+
+var (
+ file_todo_v1_todo_proto_rawDescOnce sync.Once
+ file_todo_v1_todo_proto_rawDescData []byte
+)
+
+func file_todo_v1_todo_proto_rawDescGZIP() []byte {
+ file_todo_v1_todo_proto_rawDescOnce.Do(func() {
+ file_todo_v1_todo_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_todo_v1_todo_proto_rawDesc), len(file_todo_v1_todo_proto_rawDesc)))
+ })
+ return file_todo_v1_todo_proto_rawDescData
+}
+
+var file_todo_v1_todo_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
+var file_todo_v1_todo_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
+var file_todo_v1_todo_proto_goTypes = []any{
+ (Status)(0), // 0: todo.v1.Status
+ (Field)(0), // 1: todo.v1.Field
+ (Operation)(0), // 2: todo.v1.Operation
+ (*GetTodosRequest)(nil), // 3: todo.v1.GetTodosRequest
+ (*GetTodosResponse)(nil), // 4: todo.v1.GetTodosResponse
+ (*ListTodosRequest)(nil), // 5: todo.v1.ListTodosRequest
+ (*Filter)(nil), // 6: todo.v1.Filter
+ (*Metadata)(nil), // 7: todo.v1.Metadata
+ (*ListTodosResponse)(nil), // 8: todo.v1.ListTodosResponse
+}
+var file_todo_v1_todo_proto_depIdxs = []int32{
+ 0, // 0: todo.v1.GetTodosResponse.status:type_name -> todo.v1.Status
+ 6, // 1: todo.v1.ListTodosRequest.filters:type_name -> todo.v1.Filter
+ 1, // 2: todo.v1.Filter.field:type_name -> todo.v1.Field
+ 2, // 3: todo.v1.Filter.operation:type_name -> todo.v1.Operation
+ 4, // 4: todo.v1.ListTodosResponse.data:type_name -> todo.v1.GetTodosResponse
+ 7, // 5: todo.v1.ListTodosResponse.meta:type_name -> todo.v1.Metadata
+ 3, // 6: todo.v1.TodoService.GetTodo:input_type -> todo.v1.GetTodosRequest
+ 5, // 7: todo.v1.TodoService.ListTodos:input_type -> todo.v1.ListTodosRequest
+ 4, // 8: todo.v1.TodoService.GetTodo:output_type -> todo.v1.GetTodosResponse
+ 8, // 9: todo.v1.TodoService.ListTodos:output_type -> todo.v1.ListTodosResponse
+ 8, // [8:10] is the sub-list for method output_type
+ 6, // [6:8] is the sub-list for method input_type
+ 6, // [6:6] is the sub-list for extension type_name
+ 6, // [6:6] is the sub-list for extension extendee
+ 0, // [0:6] is the sub-list for field type_name
+}
+
+func init() { file_todo_v1_todo_proto_init() }
+func file_todo_v1_todo_proto_init() {
+ if File_todo_v1_todo_proto != nil {
+ return
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: unsafe.Slice(unsafe.StringData(file_todo_v1_todo_proto_rawDesc), len(file_todo_v1_todo_proto_rawDesc)),
+ NumEnums: 3,
+ NumMessages: 6,
+ NumExtensions: 0,
+ NumServices: 1,
+ },
+ GoTypes: file_todo_v1_todo_proto_goTypes,
+ DependencyIndexes: file_todo_v1_todo_proto_depIdxs,
+ EnumInfos: file_todo_v1_todo_proto_enumTypes,
+ MessageInfos: file_todo_v1_todo_proto_msgTypes,
+ }.Build()
+ File_todo_v1_todo_proto = out.File
+ file_todo_v1_todo_proto_goTypes = nil
+ file_todo_v1_todo_proto_depIdxs = nil
+}
diff --git a/gen/todo/v1/todov1connect/todo.connect.go b/gen/todo/v1/todov1connect/todo.connect.go
new file mode 100644
index 0000000..5239d53
--- /dev/null
+++ b/gen/todo/v1/todov1connect/todo.connect.go
@@ -0,0 +1,144 @@
+// Code generated by protoc-gen-connect-go. DO NOT EDIT.
+//
+// Source: todo/v1/todo.proto
+
+package todov1connect
+
+import (
+ connect "connectrpc.com/connect"
+ context "context"
+ errors "errors"
+ v1 "git.kocoder.xyz/kocoded/vt/gen/todo/v1"
+ http "net/http"
+ strings "strings"
+)
+
+// This is a compile-time assertion to ensure that this generated file and the connect package are
+// compatible. If you get a compiler error that this constant is not defined, this code was
+// generated with a version of connect newer than the one compiled into your binary. You can fix the
+// problem by either regenerating this code with an older version of connect or updating the connect
+// version compiled into your binary.
+const _ = connect.IsAtLeastVersion1_13_0
+
+const (
+ // TodoServiceName is the fully-qualified name of the TodoService service.
+ TodoServiceName = "todo.v1.TodoService"
+)
+
+// These constants are the fully-qualified names of the RPCs defined in this package. They're
+// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route.
+//
+// Note that these are different from the fully-qualified method names used by
+// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
+// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
+// period.
+const (
+ // TodoServiceGetTodoProcedure is the fully-qualified name of the TodoService's GetTodo RPC.
+ TodoServiceGetTodoProcedure = "/todo.v1.TodoService/GetTodo"
+ // TodoServiceListTodosProcedure is the fully-qualified name of the TodoService's ListTodos RPC.
+ TodoServiceListTodosProcedure = "/todo.v1.TodoService/ListTodos"
+)
+
+// TodoServiceClient is a client for the todo.v1.TodoService service.
+type TodoServiceClient interface {
+ GetTodo(context.Context, *v1.GetTodosRequest) (*v1.GetTodosResponse, error)
+ ListTodos(context.Context, *v1.ListTodosRequest) (*v1.ListTodosResponse, error)
+}
+
+// NewTodoServiceClient constructs a client for the todo.v1.TodoService service. By default, it uses
+// the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, and sends
+// uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or
+// connect.WithGRPCWeb() options.
+//
+// The URL supplied here should be the base URL for the Connect or gRPC server (for example,
+// http://api.acme.com or https://acme.com/grpc).
+func NewTodoServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) TodoServiceClient {
+ baseURL = strings.TrimRight(baseURL, "/")
+ todoServiceMethods := v1.File_todo_v1_todo_proto.Services().ByName("TodoService").Methods()
+ return &todoServiceClient{
+ getTodo: connect.NewClient[v1.GetTodosRequest, v1.GetTodosResponse](
+ httpClient,
+ baseURL+TodoServiceGetTodoProcedure,
+ connect.WithSchema(todoServiceMethods.ByName("GetTodo")),
+ connect.WithClientOptions(opts...),
+ ),
+ listTodos: connect.NewClient[v1.ListTodosRequest, v1.ListTodosResponse](
+ httpClient,
+ baseURL+TodoServiceListTodosProcedure,
+ connect.WithSchema(todoServiceMethods.ByName("ListTodos")),
+ connect.WithClientOptions(opts...),
+ ),
+ }
+}
+
+// todoServiceClient implements TodoServiceClient.
+type todoServiceClient struct {
+ getTodo *connect.Client[v1.GetTodosRequest, v1.GetTodosResponse]
+ listTodos *connect.Client[v1.ListTodosRequest, v1.ListTodosResponse]
+}
+
+// GetTodo calls todo.v1.TodoService.GetTodo.
+func (c *todoServiceClient) GetTodo(ctx context.Context, req *v1.GetTodosRequest) (*v1.GetTodosResponse, error) {
+ response, err := c.getTodo.CallUnary(ctx, connect.NewRequest(req))
+ if response != nil {
+ return response.Msg, err
+ }
+ return nil, err
+}
+
+// ListTodos calls todo.v1.TodoService.ListTodos.
+func (c *todoServiceClient) ListTodos(ctx context.Context, req *v1.ListTodosRequest) (*v1.ListTodosResponse, error) {
+ response, err := c.listTodos.CallUnary(ctx, connect.NewRequest(req))
+ if response != nil {
+ return response.Msg, err
+ }
+ return nil, err
+}
+
+// TodoServiceHandler is an implementation of the todo.v1.TodoService service.
+type TodoServiceHandler interface {
+ GetTodo(context.Context, *v1.GetTodosRequest) (*v1.GetTodosResponse, error)
+ ListTodos(context.Context, *v1.ListTodosRequest) (*v1.ListTodosResponse, error)
+}
+
+// NewTodoServiceHandler builds an HTTP handler from the service implementation. It returns the path
+// on which to mount the handler and the handler itself.
+//
+// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf
+// and JSON codecs. They also support gzip compression.
+func NewTodoServiceHandler(svc TodoServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) {
+ todoServiceMethods := v1.File_todo_v1_todo_proto.Services().ByName("TodoService").Methods()
+ todoServiceGetTodoHandler := connect.NewUnaryHandlerSimple(
+ TodoServiceGetTodoProcedure,
+ svc.GetTodo,
+ connect.WithSchema(todoServiceMethods.ByName("GetTodo")),
+ connect.WithHandlerOptions(opts...),
+ )
+ todoServiceListTodosHandler := connect.NewUnaryHandlerSimple(
+ TodoServiceListTodosProcedure,
+ svc.ListTodos,
+ connect.WithSchema(todoServiceMethods.ByName("ListTodos")),
+ connect.WithHandlerOptions(opts...),
+ )
+ return "/todo.v1.TodoService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ switch r.URL.Path {
+ case TodoServiceGetTodoProcedure:
+ todoServiceGetTodoHandler.ServeHTTP(w, r)
+ case TodoServiceListTodosProcedure:
+ todoServiceListTodosHandler.ServeHTTP(w, r)
+ default:
+ http.NotFound(w, r)
+ }
+ })
+}
+
+// UnimplementedTodoServiceHandler returns CodeUnimplemented from all methods.
+type UnimplementedTodoServiceHandler struct{}
+
+func (UnimplementedTodoServiceHandler) GetTodo(context.Context, *v1.GetTodosRequest) (*v1.GetTodosResponse, error) {
+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("todo.v1.TodoService.GetTodo is not implemented"))
+}
+
+func (UnimplementedTodoServiceHandler) ListTodos(context.Context, *v1.ListTodosRequest) (*v1.ListTodosResponse, error) {
+ return nil, connect.NewError(connect.CodeUnimplemented, errors.New("todo.v1.TodoService.ListTodos is not implemented"))
+}
diff --git a/go.mod b/go.mod
index 7eaa09d..13ae7ad 100644
--- a/go.mod
+++ b/go.mod
@@ -1,12 +1,18 @@
module git.kocoder.xyz/kocoded/vt
-go 1.24.4
+go 1.25
require (
github.com/emersion/go-imap/v2 v2.0.0-beta.6
github.com/emersion/go-message v0.18.1
github.com/gofiber/fiber/v2 v2.52.9
- golang.org/x/oauth2 v0.28.0
+ github.com/valkey-io/valkey-glide/go/v2 v2.1.1
+ go.opentelemetry.io/otel v1.39.0
+ go.opentelemetry.io/otel/log v0.15.0
+ go.opentelemetry.io/otel/sdk v1.38.0
+ go.opentelemetry.io/otel/sdk/log v0.14.0
+ go.opentelemetry.io/otel/sdk/metric v1.38.0
+ google.golang.org/protobuf v1.36.9
gorm.io/driver/postgres v1.6.0
gorm.io/gen v0.3.27
gorm.io/gorm v1.30.1
@@ -14,26 +20,69 @@ require (
)
require (
+ github.com/ClickHouse/ch-go v0.61.5 // indirect
+ github.com/ClickHouse/clickhouse-go/v2 v2.30.0 // indirect
+ github.com/StirlingMarketingGroup/go-retry v0.0.0-20190512160921-94a8eb23e893 // indirect
+ github.com/cenkalti/backoff/v5 v5.0.3 // indirect
+ github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/dustin/go-humanize v1.0.1 // indirect
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 // indirect
github.com/fasthttp/websocket v1.5.8 // indirect
- github.com/go-jose/go-jose/v4 v4.0.5 // indirect
+ github.com/go-faster/city v1.0.1 // indirect
+ github.com/go-faster/errors v0.7.1 // indirect
+ github.com/go-jose/go-jose/v4 v4.1.3 // indirect
+ github.com/go-logr/logr v1.4.3 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect
+ github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
+ github.com/hashicorp/go-version v1.6.0 // indirect
+ github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 // indirect
+ github.com/jhillyerd/enmime v1.3.0 // indirect
+ github.com/olekukonko/tablewriter v0.0.5 // indirect
+ github.com/paulmach/orb v0.11.1 // indirect
+ github.com/pierrec/lz4/v4 v4.1.21 // indirect
+ github.com/pkg/errors v0.9.1 // indirect
+ github.com/rs/xid v1.6.0 // indirect
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 // indirect
- github.com/valkey-io/valkey-glide/go/v2 v2.1.1 // indirect
- golang.org/x/net v0.43.0 // indirect
- google.golang.org/protobuf v1.33.0 // indirect
+ github.com/segmentio/asm v1.2.0 // indirect
+ github.com/shopspring/decimal v1.4.0 // indirect
+ github.com/sqs/go-xoauth2 v0.0.0-20120917012134-0911dad68e56 // indirect
+ github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
+ go.opentelemetry.io/auto/sdk v1.2.1 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
+ go.opentelemetry.io/otel/metric v1.39.0 // indirect
+ go.opentelemetry.io/otel/trace v1.39.0 // indirect
+ go.opentelemetry.io/proto/otlp v1.7.1 // indirect
+ go.uber.org/dig v1.19.0 // indirect
+ go.uber.org/multierr v1.11.0 // indirect
+ go.uber.org/zap v1.27.0 // indirect
+ golang.org/x/net v0.48.0 // indirect
+ golang.org/x/oauth2 v0.30.0 // indirect
+ google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect
+ google.golang.org/grpc v1.75.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+ gorm.io/driver/clickhouse v0.7.0 // indirect
)
require (
+ connectrpc.com/connect v1.19.1
+ connectrpc.com/cors v0.1.0
+ connectrpc.com/grpchealth v1.4.0
+ connectrpc.com/grpcreflect v1.3.0
+ connectrpc.com/otelconnect v0.8.0
filippo.io/edwards25519 v1.1.0 // indirect
+ github.com/BrianLeishman/go-imap v0.1.20
github.com/andybalholm/brotli v1.2.0 // indirect
- github.com/coreos/go-oidc/v3 v3.15.0
- github.com/fatih/structs v1.1.0
+ github.com/coreos/go-oidc/v3 v3.17.0
github.com/go-sql-driver/mysql v1.9.3 // indirect
github.com/gofiber/contrib/websocket v1.3.4
github.com/google/uuid v1.6.0 // 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.5 // indirect
+ github.com/jackc/pgx/v5 v5.8.0 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
@@ -44,16 +93,23 @@ require (
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/philhofer/fwd v1.2.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
+ github.com/rs/cors v1.11.1
github.com/tinylib/msgp v1.3.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.64.0 // indirect
- golang.org/x/crypto v0.41.0 // indirect
- golang.org/x/mod v0.27.0 // indirect
- golang.org/x/sync v0.16.0 // indirect
- golang.org/x/sys v0.35.0 // indirect
- golang.org/x/text v0.28.0 // indirect
- golang.org/x/tools v0.36.0 // indirect
+ go.opentelemetry.io/contrib/bridges/otelslog v0.14.0
+ go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0
+ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0
+ go.uber.org/fx v1.24.0
+ golang.org/x/crypto v0.46.0 // indirect
+ golang.org/x/mod v0.30.0 // indirect
+ golang.org/x/sync v0.19.0 // indirect
+ golang.org/x/sys v0.39.0 // indirect
+ golang.org/x/text v0.32.0 // indirect
+ golang.org/x/tools v0.39.0 // indirect
gorm.io/datatypes v1.2.6 // indirect
gorm.io/driver/mysql v1.6.0 // indirect
gorm.io/hints v1.1.2 // indirect
+ gorm.io/plugin/opentelemetry v0.1.16
)
diff --git a/go.sum b/go.sum
index 3046425..1202305 100644
--- a/go.sum
+++ b/go.sum
@@ -1,12 +1,38 @@
+connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14=
+connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w=
+connectrpc.com/cors v0.1.0 h1:f3gTXJyDZPrDIZCQ567jxfD9PAIpopHiRDnJRt3QuOQ=
+connectrpc.com/cors v0.1.0/go.mod h1:v8SJZCPfHtGH1zsm+Ttajpozd4cYIUryl4dFB6QEpfg=
+connectrpc.com/grpchealth v1.4.0 h1:MJC96JLelARPgZTiRF9KRfY/2N9OcoQvF2EWX07v2IE=
+connectrpc.com/grpchealth v1.4.0/go.mod h1:WhW6m1EzTmq3Ky1FE8EfkIpSDc6TfUx2M2KqZO3ts/Q=
+connectrpc.com/grpcreflect v1.3.0 h1:Y4V+ACf8/vOb1XOc251Qun7jMB75gCUNw6llvB9csXc=
+connectrpc.com/grpcreflect v1.3.0/go.mod h1:nfloOtCS8VUQOQ1+GTdFzVg2CJo4ZGaat8JIovCtDYs=
+connectrpc.com/otelconnect v0.8.0 h1:a4qrN4H8aEE2jAoCxheZYYfEjXMgVPyL9OzPQLBEFXU=
+connectrpc.com/otelconnect v0.8.0/go.mod h1:AEkVLjCPXra+ObGFCOClcJkNjS7zPaQSqvO0lCyjfZc=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
+github.com/BrianLeishman/go-imap v0.1.20 h1:jvkir/v5A7Ml0zAAZyTSq60+WDN54RzRhhPwC9Bw9Bg=
+github.com/BrianLeishman/go-imap v0.1.20/go.mod h1:MiXltWbhjxLKrYHGivAieizDwQ+oOULASiV9kVMyBLc=
+github.com/ClickHouse/ch-go v0.61.5 h1:zwR8QbYI0tsMiEcze/uIMK+Tz1D3XZXLdNrlaOpeEI4=
+github.com/ClickHouse/ch-go v0.61.5/go.mod h1:s1LJW/F/LcFs5HJnuogFMta50kKDO0lf9zzfrbl0RQg=
+github.com/ClickHouse/clickhouse-go/v2 v2.30.0 h1:AG4D/hW39qa58+JHQIFOSnxyL46H6h2lrmGGk17dhFo=
+github.com/ClickHouse/clickhouse-go/v2 v2.30.0/go.mod h1:i9ZQAojcayW3RsdCb3YR+n+wC2h65eJsZCscZ1Z1wyo=
+github.com/StirlingMarketingGroup/go-retry v0.0.0-20190512160921-94a8eb23e893 h1:y1OlgL2twHNQGJ4OTHhvVLebgDCwP4pttmZc2w4UAz8=
+github.com/StirlingMarketingGroup/go-retry v0.0.0-20190512160921-94a8eb23e893/go.mod h1:RHK0VFlYDZQeNFg4C2dp7cPE6urfbpgyEZIGxa9f5zw=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
-github.com/coreos/go-oidc/v3 v3.15.0 h1:R6Oz8Z4bqWR7VFQ+sPSvZPQv4x8M+sJkDO5ojgwlyAg=
-github.com/coreos/go-oidc/v3 v3.15.0/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
+github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
+github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
+github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a h1:MISbI8sU/PSK/ztvmWKFcI7UGb5/HQT7B+i3a2myKgI=
+github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a/go.mod h1:2GxOXOlEPAMFPfp014mK1SWq8G8BN8o7/dfYqJrVGn8=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=
+github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
+github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/emersion/go-imap/v2 v2.0.0-beta.6 h1:3w7QGUcDEoZXr+okRZR75VBjX0yvvJqkfy3gibbH2yY=
github.com/emersion/go-imap/v2 v2.0.0-beta.6/go.mod h1:BZTFHsS1hmgBkFlHqbxGLXk2hnRqTItUgwjSSCsYNAk=
github.com/emersion/go-message v0.18.1 h1:tfTxIoXFSFRwWaZsgnqS1DSZuGpYGzSmCZD8SK3QA2E=
@@ -15,44 +41,81 @@ github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 h1:hH4PQfOndHDlpz
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/fasthttp/websocket v1.5.8 h1:k5DpirKkftIF/w1R8ZzjSgARJrs54Je9YJK37DL/Ah8=
github.com/fasthttp/websocket v1.5.8/go.mod h1:d08g8WaT6nnyvg9uMm8K9zMYyDjfKyj3170AtPRuVU0=
-github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
-github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
-github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
-github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
+github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw=
+github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw=
+github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg=
+github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo=
+github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
+github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
+github.com/go-logr/logr v1.4.3/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-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/gofiber/contrib/websocket v1.3.4 h1:tWeBdbJ8q0WFQXariLN4dBIbGH9KBU75s0s7YXplOSg=
github.com/gofiber/contrib/websocket v1.3.4/go.mod h1:kTFBPC6YENCnKfKx0BoOFjgXxdz7E85/STdkmZPEmPs=
github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw=
github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs=
+github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
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/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
-github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+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.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
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/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
+github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
+github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
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.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
+github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo=
+github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw=
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/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 h1:iCHtR9CQyktQ5+f3dMVZfwD2KWJUgm7M0gdL9NGr8KA=
+github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
+github.com/jhillyerd/enmime v1.3.0 h1:LV5kzfLidiOr8qRGIpYYmUZCnhrPbcFAnAFUnWn99rw=
+github.com/jhillyerd/enmime v1.3.0/go.mod h1:6c6jg5HdRRV2FtvVL69LjiX1M8oE0xDX9VEhV3oy4gs=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
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.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+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=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
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/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
@@ -60,20 +123,46 @@ github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwp
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA=
github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=
+github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
+github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
+github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
+github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
+github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
+github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
+github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
+github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
+github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
+github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
+github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
+github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
+github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 h1:KanIMPX0QdEdB4R3CiimCAbxFrhB3j7h0/OvpYGVQa8=
github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
+github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
+github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
+github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
+github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
+github.com/sqs/go-xoauth2 v0.0.0-20120917012134-0911dad68e56 h1:KCgKdj+ha4CgnVHIiJYGKzgZk3HfCc6XssESfOa6atM=
+github.com/sqs/go-xoauth2 v0.0.0-20120917012134-0911dad68e56/go.mod h1:ghDEBrT4oFcM4rv18bzcZaAWXbHPGpDa4e2hh9oXL8A=
+github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
+github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww=
github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
github.com/valkey-io/valkey-glide/go/v2 v2.1.1 h1:78eoWXIYLbse0ZpspKRMwbREj0+Tkoc/qkSR8H9iRsc=
@@ -82,32 +171,111 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.64.0 h1:QBygLLQmiAyiXuRhthf0tuRkqAFcrC42dckN2S+N3og=
github.com/valyala/fasthttp v1.64.0/go.mod h1:dGmFxwkWXSK0NbOSJuF7AMVzU+lkHz0wQVvVITv2UQA=
+github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
+github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
+github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
+github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
+go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
+go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
+go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
+go.opentelemetry.io/contrib/bridges/otelslog v0.14.0 h1:eypSOd+0txRKCXPNyqLPsbSfA0jULgJcGmSAdFAnrCM=
+go.opentelemetry.io/contrib/bridges/otelslog v0.14.0/go.mod h1:CRGvIBL/aAxpQU34ZxyQVFlovVcp67s4cAmQu8Jh9mc=
+go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
+go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
+go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
+go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc=
+go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
+go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM=
+go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno=
+go.opentelemetry.io/otel/log v0.15.0 h1:0VqVnc3MgyYd7QqNVIldC3dsLFKgazR6P3P3+ypkyDY=
+go.opentelemetry.io/otel/log v0.15.0/go.mod h1:9c/G1zbyZfgu1HmQD7Qj84QMmwTp2QCQsZH1aeoWDE4=
+go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
+go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
+go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
+go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
+go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
+go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
+go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg=
+go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM=
+go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM=
+go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA=
+go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
+go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
+go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
+go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
+go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
+go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
+go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
+go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
+go.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4=
+go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
+go.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg=
+go.uber.org/fx v1.24.0/go.mod h1:AmDeGyS+ZARGKM4tlH4FY2Jr63VjbEDJHtqXTGP5hbo=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
+golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
+golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
+golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
-golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
-golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
+golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
+golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
+golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
+golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
+golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -115,31 +283,58 @@ 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.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
+golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
+golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
+golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
+golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
+golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
-google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
+gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
+google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY=
+google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc=
+google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
+google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
+google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
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=
+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=
gorm.io/datatypes v1.2.6 h1:KafLdXvFUhzNeL2ncm03Gl3eTLONQfNKZ+wJ+9Y4Nck=
gorm.io/datatypes v1.2.6/go.mod h1:M2iO+6S3hhi4nAyYe444Pcb0dcIiOMJ7QHaUXxyiNZY=
+gorm.io/driver/clickhouse v0.7.0 h1:BCrqvgONayvZRgtuA6hdya+eAW5P2QVagV3OlEp1vtA=
+gorm.io/driver/clickhouse v0.7.0/go.mod h1:TmNo0wcVTsD4BBObiRnCahUgHJHjBIwuRejHwYt3JRs=
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
@@ -158,3 +353,5 @@ gorm.io/hints v1.1.2 h1:b5j0kwk5p4+3BtDtYqqfY+ATSxjj+6ptPgVveuynn9o=
gorm.io/hints v1.1.2/go.mod h1:/ARdpUHAtyEMCh5NNi3tI7FsGh+Cj/MIUlvNxCNCFWg=
gorm.io/plugin/dbresolver v1.6.2 h1:F4b85TenghUeITqe3+epPSUtHH7RIk3fXr5l83DF8Pc=
gorm.io/plugin/dbresolver v1.6.2/go.mod h1:tctw63jdrOezFR9HmrKnPkmig3m5Edem9fdxk9bQSzM=
+gorm.io/plugin/opentelemetry v0.1.16 h1:Kypj2YYAliJqkIczDZDde6P6sFMhKSlG5IpngMFQGpc=
+gorm.io/plugin/opentelemetry v0.1.16/go.mod h1:P3RmTeZXT+9n0F1ccUqR5uuTvEXDxF8k2UpO7mTIB2Y=
diff --git a/integration/integration_handler.go b/integration/integration_handler.go
new file mode 100644
index 0000000..9cf5b99
--- /dev/null
+++ b/integration/integration_handler.go
@@ -0,0 +1,17 @@
+package integration
+
+import (
+ "git.kocoder.xyz/kocoded/vt/fx/interfaces"
+ "go.uber.org/fx"
+)
+
+type IntegrationHandler struct {
+}
+
+func NewIntegrationHandler(integrations []interfaces.BasicIntegration, lc fx.Lifecycle) *IntegrationHandler {
+ for _, i := range integrations {
+ lc.Append(fx.StartStopHook(i.OnStart, i.OnStop))
+ }
+
+ return &IntegrationHandler{}
+}
diff --git a/integration/mailing/imap_integration.go b/integration/mailing/imap_integration.go
new file mode 100644
index 0000000..8a85538
--- /dev/null
+++ b/integration/mailing/imap_integration.go
@@ -0,0 +1,84 @@
+package mailing
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "git.kocoder.xyz/kocoded/vt/fx/interfaces"
+
+ imap "github.com/BrianLeishman/go-imap"
+)
+
+type imapIntegration struct {
+ id *imap.Dialer
+}
+
+// Invoke implements interfaces.BasicIntegration.
+func (i *imapIntegration) Invoke() {
+ fmt.Println("Invoked!")
+}
+
+// OnStart implements interfaces.BasicIntegration.
+func (i *imapIntegration) OnStart(ctx context.Context) error {
+ fmt.Println("Integration IMAP started")
+ return nil
+}
+
+// OnStop implements interfaces.BasicIntegration.
+func (i *imapIntegration) OnStop(ctx context.Context) error {
+ fmt.Println("Integration IMAP stopped")
+ return nil
+}
+
+func NewImapIntegration() interfaces.BasicIntegration {
+ fmt.Println("New Imap Integration")
+
+ imap.Verbose = true
+ imap.RetryCount = 1
+ imap.DialTimeout = 10 * time.Second
+ imap.CommandTimeout = 30 * time.Second
+
+ m, err := imap.New("me@kocoder.xyz", "&,25,upMeddeEnTYPTifaccEptIonaAlErGiE", "mx.kocoder.xyz", 993)
+
+ if err != nil {
+ panic(err)
+ }
+
+ if err := m.SelectFolder("INBOX"); err != nil {
+ panic("Failed to select inbox")
+ }
+
+ uids, err := m.GetUIDs("UNSEEN")
+ if err != nil {
+ panic("Search")
+ }
+
+ emails, err := m.GetEmails(uids[:5]...)
+ if err != nil {
+ panic("Getting EMails")
+ }
+
+ for uid, email := range emails {
+ fmt.Printf("\n--- Email UID %d ---\n", uid)
+ fmt.Printf("From: %s\n", email.From)
+ fmt.Printf("Subject: %s\n", email.Subject)
+ fmt.Printf("Date: %s\n", email.Sent.Format("Jan 2, 2006 3:04 PM"))
+ fmt.Printf("Size: %.1f KB\n", float64(email.Size)/1024)
+
+ if len(email.Text) > 100 {
+ fmt.Printf("Preview: %.100s...\n", email.Text)
+ } else if len(email.Text) > 0 {
+ fmt.Printf("Preview: %s\n", email.Text)
+ }
+
+ if len(email.Attachments) > 0 {
+ fmt.Printf("Attachments: %d\n", len(email.Attachments))
+ for _, att := range email.Attachments {
+ fmt.Printf(" - %s (%.1f KB)\n", att.Name, float64(len(att.Content))/1024)
+ }
+ }
+ }
+
+ return &imapIntegration{id: m}
+}
diff --git a/interceptors/auth.go b/interceptors/auth.go
new file mode 100644
index 0000000..381618b
--- /dev/null
+++ b/interceptors/auth.go
@@ -0,0 +1,116 @@
+package interceptors
+
+import (
+ "context"
+ "errors"
+ "log/slog"
+ "strings"
+
+ "connectrpc.com/connect"
+ "git.kocoder.xyz/kocoded/vt/cmd/mailingest/query"
+ "git.kocoder.xyz/kocoded/vt/fx/interfaces/stores"
+ "git.kocoder.xyz/kocoded/vt/model"
+ "github.com/coreos/go-oidc/v3/oidc"
+ "gorm.io/gen/field"
+ "gorm.io/gorm"
+)
+
+const tokenHeader = "authentication"
+const alternativeTokenHeader = "Authorization"
+
+type AuthenticationInterceptor struct {
+ verifier *oidc.IDTokenVerifier
+ sr stores.SessionStore
+ logger *slog.Logger
+ db *gorm.DB
+}
+
+// WrapStreamingClient implements connect.Interceptor.
+func (a *AuthenticationInterceptor) WrapStreamingClient(connect.StreamingClientFunc) connect.StreamingClientFunc {
+ panic("unimplemented")
+}
+
+// WrapStreamingHandler implements connect.Interceptor.
+func (a *AuthenticationInterceptor) WrapStreamingHandler(connect.StreamingHandlerFunc) connect.StreamingHandlerFunc {
+ panic("unimplemented")
+}
+
+// WrapUnary implements connect.Interceptor.
+func (a *AuthenticationInterceptor) WrapUnary(next connect.UnaryFunc) connect.UnaryFunc {
+ return func(
+ ctx context.Context,
+ req connect.AnyRequest,
+ ) (connect.AnyResponse, error) {
+ if req.Spec().IsClient {
+ panic(errors.New(""))
+ } else {
+ authToken := req.Header().Get(tokenHeader)
+ if authToken == "" {
+ authToken = req.Header().Get(alternativeTokenHeader)
+ }
+
+ if ok := strings.HasPrefix(authToken, "Bearer "); !ok {
+ a.logger.Info("No Authtoken with Bearer Prefix provided")
+ return nil, connect.NewError(
+ connect.CodeUnauthenticated,
+ errors.New("No valid token provided"),
+ )
+ }
+ authToken = strings.TrimPrefix(authToken, "Bearer ")
+
+ IDToken, err := a.verifier.Verify(context.TODO(), authToken)
+ if err != nil {
+ a.logger.Info("No valid/verifyable Authtoken provided", "err", err)
+ return nil, err
+ }
+
+ session, err := a.sr.GetSessionFromToken(IDToken.Subject)
+
+ if err != nil {
+
+ q := query.Use(a.db)
+ u := q.User
+
+ claims := model.User{}
+
+ if err := IDToken.Claims(&claims); err != nil {
+ a.logger.Error("Failed to parse claims from IDToken", "err", err)
+ panic(err)
+ }
+
+ a.logger.Info("Parsed Claims", "claims", claims)
+
+ us, err := u.Where(u.Sub.Eq(IDToken.Subject)).Assign(field.Attrs(&claims)).FirstOrCreate()
+ if err != nil {
+ a.logger.Error("Failed to get or create user from DB", "err", err, "sub", IDToken.Subject)
+ return nil, connect.NewError(connect.CodeInternal, errors.New("Failed to create user in DB"))
+ }
+
+ session = &stores.Session{Token: IDToken.Subject, UserID: us.ID, MandantId: 1}
+ a.sr.AddSession(session)
+ }
+
+ ctx = NewSessionContext(ctx, session)
+ }
+
+ return next(ctx, req)
+ }
+}
+
+func NewOIDCInterceptor(verifier *oidc.IDTokenVerifier, sr stores.SessionStore, logger *slog.Logger, db *gorm.DB) *AuthenticationInterceptor {
+ return &AuthenticationInterceptor{verifier: verifier, sr: sr, logger: logger, db: db}
+}
+
+type key int
+
+var sessionKey key
+
+func NewSessionContext(ctx context.Context, u *stores.Session) context.Context {
+ return context.WithValue(ctx, sessionKey, u)
+}
+
+// FromContext returns the User value stored in ctx, if any.
+func SessionFromContext(ctx context.Context) (*stores.Session, bool) {
+ u, ok := ctx.Value(sessionKey).(*stores.Session)
+ return u, ok
+}
diff --git a/interceptors/db.go b/interceptors/db.go
new file mode 100644
index 0000000..954209d
--- /dev/null
+++ b/interceptors/db.go
@@ -0,0 +1,29 @@
+package interceptors
+
+import (
+ "context"
+ "net/http"
+
+ "gorm.io/gorm"
+)
+
+var dbKey key
+
+func NewDBContext(ctx context.Context, db *gorm.DB) context.Context {
+ return context.WithValue(ctx, dbKey, db)
+}
+
+// FromContext returns the User value stored in ctx, if any.
+func DBFromContex(ctx context.Context) (*gorm.DB, bool) {
+ u, ok := ctx.Value(dbKey).(*gorm.DB)
+ return u, ok
+}
+
+func AddDBToContext(next http.Handler, db *gorm.DB) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ ctx := r.Context()
+ ctx = NewDBContext(ctx, db)
+ r = r.WithContext(ctx)
+ next.ServeHTTP(w, r)
+ })
+}
diff --git a/model/projekt.go b/model/projekt.go
index 314883b..bfcd200 100644
--- a/model/projekt.go
+++ b/model/projekt.go
@@ -1,6 +1,8 @@
package model
-import "gorm.io/gorm"
+import (
+ "gorm.io/gorm"
+)
type Projekt struct {
gorm.Model
diff --git a/model/task.go b/model/task.go
new file mode 100644
index 0000000..7147c54
--- /dev/null
+++ b/model/task.go
@@ -0,0 +1,27 @@
+package model
+
+import "gorm.io/gorm"
+
+type Status int
+
+const (
+ Todo Status = iota
+ NeedsMoreInfo
+ Started
+ Done
+)
+
+func (s Status) IsDone() bool {
+ return s == Done
+}
+
+type Task struct {
+ gorm.Model
+ ProjektID uint
+ Projekt Projekt
+ MandantID uint
+ Mandant Mandant
+ Titel string `json:"titel"`
+ Description string `json:"description"`
+ Status Status `json:"status"`
+}
diff --git a/query.code-workspace b/query.code-workspace
index bbf37c1..5cccae2 100644
--- a/query.code-workspace
+++ b/query.code-workspace
@@ -5,6 +5,9 @@
},
{
"path": "."
+ },
+ {
+ "path": "../eventory-mobile"
}
],
"settings": {}
diff --git a/query/gen.go b/query/gen.go
index e61b305..1655fb3 100644
--- a/query/gen.go
+++ b/query/gen.go
@@ -33,6 +33,7 @@ var (
Rechnung *rechnung
Rechnungsposition *rechnungsposition
Scanobject *scanobject
+ Task *task
User *user
Zahlung *zahlung
)
@@ -55,6 +56,7 @@ func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
Rechnung = &Q.Rechnung
Rechnungsposition = &Q.Rechnungsposition
Scanobject = &Q.Scanobject
+ Task = &Q.Task
User = &Q.User
Zahlung = &Q.Zahlung
}
@@ -78,6 +80,7 @@ func Use(db *gorm.DB, opts ...gen.DOOption) *Query {
Rechnung: newRechnung(db, opts...),
Rechnungsposition: newRechnungsposition(db, opts...),
Scanobject: newScanobject(db, opts...),
+ Task: newTask(db, opts...),
User: newUser(db, opts...),
Zahlung: newZahlung(db, opts...),
}
@@ -102,6 +105,7 @@ type Query struct {
Rechnung rechnung
Rechnungsposition rechnungsposition
Scanobject scanobject
+ Task task
User user
Zahlung zahlung
}
@@ -127,6 +131,7 @@ func (q *Query) clone(db *gorm.DB) *Query {
Rechnung: q.Rechnung.clone(db),
Rechnungsposition: q.Rechnungsposition.clone(db),
Scanobject: q.Scanobject.clone(db),
+ Task: q.Task.clone(db),
User: q.User.clone(db),
Zahlung: q.Zahlung.clone(db),
}
@@ -159,6 +164,7 @@ func (q *Query) ReplaceDB(db *gorm.DB) *Query {
Rechnung: q.Rechnung.replaceDB(db),
Rechnungsposition: q.Rechnungsposition.replaceDB(db),
Scanobject: q.Scanobject.replaceDB(db),
+ Task: q.Task.replaceDB(db),
User: q.User.replaceDB(db),
Zahlung: q.Zahlung.replaceDB(db),
}
@@ -181,6 +187,7 @@ type queryCtx struct {
Rechnung IRechnungDo
Rechnungsposition IRechnungspositionDo
Scanobject IScanobjectDo
+ Task ITaskDo
User IUserDo
Zahlung IZahlungDo
}
@@ -203,6 +210,7 @@ func (q *Query) WithContext(ctx context.Context) *queryCtx {
Rechnung: q.Rechnung.WithContext(ctx),
Rechnungsposition: q.Rechnungsposition.WithContext(ctx),
Scanobject: q.Scanobject.WithContext(ctx),
+ Task: q.Task.WithContext(ctx),
User: q.User.WithContext(ctx),
Zahlung: q.Zahlung.WithContext(ctx),
}
diff --git a/query/tasks.gen.go b/query/tasks.gen.go
new file mode 100644
index 0000000..3987f00
--- /dev/null
+++ b/query/tasks.gen.go
@@ -0,0 +1,607 @@
+// Code generated by gorm.io/gen. DO NOT EDIT.
+// Code generated by gorm.io/gen. DO NOT EDIT.
+// Code generated by gorm.io/gen. DO NOT EDIT.
+
+package query
+
+import (
+ "context"
+ "database/sql"
+
+ "gorm.io/gorm"
+ "gorm.io/gorm/clause"
+ "gorm.io/gorm/schema"
+
+ "gorm.io/gen"
+ "gorm.io/gen/field"
+
+ "gorm.io/plugin/dbresolver"
+
+ "git.kocoder.xyz/kocoded/vt/model"
+)
+
+func newTask(db *gorm.DB, opts ...gen.DOOption) task {
+ _task := task{}
+
+ _task.taskDo.UseDB(db, opts...)
+ _task.taskDo.UseModel(&model.Task{})
+
+ tableName := _task.taskDo.TableName()
+ _task.ALL = field.NewAsterisk(tableName)
+ _task.ID = field.NewUint(tableName, "id")
+ _task.CreatedAt = field.NewTime(tableName, "created_at")
+ _task.UpdatedAt = field.NewTime(tableName, "updated_at")
+ _task.DeletedAt = field.NewField(tableName, "deleted_at")
+ _task.ProjektID = field.NewUint(tableName, "projekt_id")
+ _task.MandantID = field.NewUint(tableName, "mandant_id")
+ _task.Titel = field.NewString(tableName, "titel")
+ _task.Description = field.NewString(tableName, "description")
+ _task.Status = field.NewInt(tableName, "status")
+ _task.Projekt = taskBelongsToProjekt{
+ db: db.Session(&gorm.Session{}),
+
+ RelationField: field.NewRelation("Projekt", "model.Projekt"),
+ Mandant: struct {
+ field.RelationField
+ }{
+ RelationField: field.NewRelation("Projekt.Mandant", "model.Mandant"),
+ },
+ }
+
+ _task.Mandant = taskBelongsToMandant{
+ db: db.Session(&gorm.Session{}),
+
+ RelationField: field.NewRelation("Mandant", "model.Mandant"),
+ }
+
+ _task.fillFieldMap()
+
+ return _task
+}
+
+type task struct {
+ taskDo
+
+ ALL field.Asterisk
+ ID field.Uint
+ CreatedAt field.Time
+ UpdatedAt field.Time
+ DeletedAt field.Field
+ ProjektID field.Uint
+ MandantID field.Uint
+ Titel field.String
+ Description field.String
+ Status field.Int
+ Projekt taskBelongsToProjekt
+
+ Mandant taskBelongsToMandant
+
+ fieldMap map[string]field.Expr
+}
+
+func (t task) Table(newTableName string) *task {
+ t.taskDo.UseTable(newTableName)
+ return t.updateTableName(newTableName)
+}
+
+func (t task) As(alias string) *task {
+ t.taskDo.DO = *(t.taskDo.As(alias).(*gen.DO))
+ return t.updateTableName(alias)
+}
+
+func (t *task) updateTableName(table string) *task {
+ t.ALL = field.NewAsterisk(table)
+ t.ID = field.NewUint(table, "id")
+ t.CreatedAt = field.NewTime(table, "created_at")
+ t.UpdatedAt = field.NewTime(table, "updated_at")
+ t.DeletedAt = field.NewField(table, "deleted_at")
+ t.ProjektID = field.NewUint(table, "projekt_id")
+ t.MandantID = field.NewUint(table, "mandant_id")
+ t.Titel = field.NewString(table, "titel")
+ t.Description = field.NewString(table, "description")
+ t.Status = field.NewInt(table, "status")
+
+ t.fillFieldMap()
+
+ return t
+}
+
+func (t *task) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
+ _f, ok := t.fieldMap[fieldName]
+ if !ok || _f == nil {
+ return nil, false
+ }
+ _oe, ok := _f.(field.OrderExpr)
+ return _oe, ok
+}
+
+func (t *task) fillFieldMap() {
+ t.fieldMap = make(map[string]field.Expr, 11)
+ t.fieldMap["id"] = t.ID
+ t.fieldMap["created_at"] = t.CreatedAt
+ t.fieldMap["updated_at"] = t.UpdatedAt
+ t.fieldMap["deleted_at"] = t.DeletedAt
+ t.fieldMap["projekt_id"] = t.ProjektID
+ t.fieldMap["mandant_id"] = t.MandantID
+ t.fieldMap["titel"] = t.Titel
+ t.fieldMap["description"] = t.Description
+ t.fieldMap["status"] = t.Status
+
+}
+
+func (t task) clone(db *gorm.DB) task {
+ t.taskDo.ReplaceConnPool(db.Statement.ConnPool)
+ t.Projekt.db = db.Session(&gorm.Session{Initialized: true})
+ t.Projekt.db.Statement.ConnPool = db.Statement.ConnPool
+ t.Mandant.db = db.Session(&gorm.Session{Initialized: true})
+ t.Mandant.db.Statement.ConnPool = db.Statement.ConnPool
+ return t
+}
+
+func (t task) replaceDB(db *gorm.DB) task {
+ t.taskDo.ReplaceDB(db)
+ t.Projekt.db = db.Session(&gorm.Session{})
+ t.Mandant.db = db.Session(&gorm.Session{})
+ return t
+}
+
+type taskBelongsToProjekt struct {
+ db *gorm.DB
+
+ field.RelationField
+
+ Mandant struct {
+ field.RelationField
+ }
+}
+
+func (a taskBelongsToProjekt) Where(conds ...field.Expr) *taskBelongsToProjekt {
+ if len(conds) == 0 {
+ return &a
+ }
+
+ exprs := make([]clause.Expression, 0, len(conds))
+ for _, cond := range conds {
+ exprs = append(exprs, cond.BeCond().(clause.Expression))
+ }
+ a.db = a.db.Clauses(clause.Where{Exprs: exprs})
+ return &a
+}
+
+func (a taskBelongsToProjekt) WithContext(ctx context.Context) *taskBelongsToProjekt {
+ a.db = a.db.WithContext(ctx)
+ return &a
+}
+
+func (a taskBelongsToProjekt) Session(session *gorm.Session) *taskBelongsToProjekt {
+ a.db = a.db.Session(session)
+ return &a
+}
+
+func (a taskBelongsToProjekt) Model(m *model.Task) *taskBelongsToProjektTx {
+ return &taskBelongsToProjektTx{a.db.Model(m).Association(a.Name())}
+}
+
+func (a taskBelongsToProjekt) Unscoped() *taskBelongsToProjekt {
+ a.db = a.db.Unscoped()
+ return &a
+}
+
+type taskBelongsToProjektTx struct{ tx *gorm.Association }
+
+func (a taskBelongsToProjektTx) Find() (result *model.Projekt, err error) {
+ return result, a.tx.Find(&result)
+}
+
+func (a taskBelongsToProjektTx) Append(values ...*model.Projekt) (err error) {
+ targetValues := make([]interface{}, len(values))
+ for i, v := range values {
+ targetValues[i] = v
+ }
+ return a.tx.Append(targetValues...)
+}
+
+func (a taskBelongsToProjektTx) Replace(values ...*model.Projekt) (err error) {
+ targetValues := make([]interface{}, len(values))
+ for i, v := range values {
+ targetValues[i] = v
+ }
+ return a.tx.Replace(targetValues...)
+}
+
+func (a taskBelongsToProjektTx) Delete(values ...*model.Projekt) (err error) {
+ targetValues := make([]interface{}, len(values))
+ for i, v := range values {
+ targetValues[i] = v
+ }
+ return a.tx.Delete(targetValues...)
+}
+
+func (a taskBelongsToProjektTx) Clear() error {
+ return a.tx.Clear()
+}
+
+func (a taskBelongsToProjektTx) Count() int64 {
+ return a.tx.Count()
+}
+
+func (a taskBelongsToProjektTx) Unscoped() *taskBelongsToProjektTx {
+ a.tx = a.tx.Unscoped()
+ return &a
+}
+
+type taskBelongsToMandant struct {
+ db *gorm.DB
+
+ field.RelationField
+}
+
+func (a taskBelongsToMandant) Where(conds ...field.Expr) *taskBelongsToMandant {
+ if len(conds) == 0 {
+ return &a
+ }
+
+ exprs := make([]clause.Expression, 0, len(conds))
+ for _, cond := range conds {
+ exprs = append(exprs, cond.BeCond().(clause.Expression))
+ }
+ a.db = a.db.Clauses(clause.Where{Exprs: exprs})
+ return &a
+}
+
+func (a taskBelongsToMandant) WithContext(ctx context.Context) *taskBelongsToMandant {
+ a.db = a.db.WithContext(ctx)
+ return &a
+}
+
+func (a taskBelongsToMandant) Session(session *gorm.Session) *taskBelongsToMandant {
+ a.db = a.db.Session(session)
+ return &a
+}
+
+func (a taskBelongsToMandant) Model(m *model.Task) *taskBelongsToMandantTx {
+ return &taskBelongsToMandantTx{a.db.Model(m).Association(a.Name())}
+}
+
+func (a taskBelongsToMandant) Unscoped() *taskBelongsToMandant {
+ a.db = a.db.Unscoped()
+ return &a
+}
+
+type taskBelongsToMandantTx struct{ tx *gorm.Association }
+
+func (a taskBelongsToMandantTx) Find() (result *model.Mandant, err error) {
+ return result, a.tx.Find(&result)
+}
+
+func (a taskBelongsToMandantTx) Append(values ...*model.Mandant) (err error) {
+ targetValues := make([]interface{}, len(values))
+ for i, v := range values {
+ targetValues[i] = v
+ }
+ return a.tx.Append(targetValues...)
+}
+
+func (a taskBelongsToMandantTx) Replace(values ...*model.Mandant) (err error) {
+ targetValues := make([]interface{}, len(values))
+ for i, v := range values {
+ targetValues[i] = v
+ }
+ return a.tx.Replace(targetValues...)
+}
+
+func (a taskBelongsToMandantTx) Delete(values ...*model.Mandant) (err error) {
+ targetValues := make([]interface{}, len(values))
+ for i, v := range values {
+ targetValues[i] = v
+ }
+ return a.tx.Delete(targetValues...)
+}
+
+func (a taskBelongsToMandantTx) Clear() error {
+ return a.tx.Clear()
+}
+
+func (a taskBelongsToMandantTx) Count() int64 {
+ return a.tx.Count()
+}
+
+func (a taskBelongsToMandantTx) Unscoped() *taskBelongsToMandantTx {
+ a.tx = a.tx.Unscoped()
+ return &a
+}
+
+type taskDo struct{ gen.DO }
+
+type ITaskDo interface {
+ gen.SubQuery
+ Debug() ITaskDo
+ WithContext(ctx context.Context) ITaskDo
+ WithResult(fc func(tx gen.Dao)) gen.ResultInfo
+ ReplaceDB(db *gorm.DB)
+ ReadDB() ITaskDo
+ WriteDB() ITaskDo
+ As(alias string) gen.Dao
+ Session(config *gorm.Session) ITaskDo
+ Columns(cols ...field.Expr) gen.Columns
+ Clauses(conds ...clause.Expression) ITaskDo
+ Not(conds ...gen.Condition) ITaskDo
+ Or(conds ...gen.Condition) ITaskDo
+ Select(conds ...field.Expr) ITaskDo
+ Where(conds ...gen.Condition) ITaskDo
+ Order(conds ...field.Expr) ITaskDo
+ Distinct(cols ...field.Expr) ITaskDo
+ Omit(cols ...field.Expr) ITaskDo
+ Join(table schema.Tabler, on ...field.Expr) ITaskDo
+ LeftJoin(table schema.Tabler, on ...field.Expr) ITaskDo
+ RightJoin(table schema.Tabler, on ...field.Expr) ITaskDo
+ Group(cols ...field.Expr) ITaskDo
+ Having(conds ...gen.Condition) ITaskDo
+ Limit(limit int) ITaskDo
+ Offset(offset int) ITaskDo
+ Count() (count int64, err error)
+ Scopes(funcs ...func(gen.Dao) gen.Dao) ITaskDo
+ Unscoped() ITaskDo
+ Create(values ...*model.Task) error
+ CreateInBatches(values []*model.Task, batchSize int) error
+ Save(values ...*model.Task) error
+ First() (*model.Task, error)
+ Take() (*model.Task, error)
+ Last() (*model.Task, error)
+ Find() ([]*model.Task, error)
+ FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.Task, err error)
+ FindInBatches(result *[]*model.Task, batchSize int, fc func(tx gen.Dao, batch int) error) error
+ Pluck(column field.Expr, dest interface{}) error
+ Delete(...*model.Task) (info gen.ResultInfo, err error)
+ Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error)
+ UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)
+ Updates(value interface{}) (info gen.ResultInfo, err error)
+ UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error)
+ UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)
+ UpdateColumns(value interface{}) (info gen.ResultInfo, err error)
+ UpdateFrom(q gen.SubQuery) gen.Dao
+ Attrs(attrs ...field.AssignExpr) ITaskDo
+ Assign(attrs ...field.AssignExpr) ITaskDo
+ Joins(fields ...field.RelationField) ITaskDo
+ Preload(fields ...field.RelationField) ITaskDo
+ FirstOrInit() (*model.Task, error)
+ FirstOrCreate() (*model.Task, error)
+ FindByPage(offset int, limit int) (result []*model.Task, count int64, err error)
+ ScanByPage(result interface{}, offset int, limit int) (count int64, err error)
+ Rows() (*sql.Rows, error)
+ Row() *sql.Row
+ Scan(result interface{}) (err error)
+ Returning(value interface{}, columns ...string) ITaskDo
+ UnderlyingDB() *gorm.DB
+ schema.Tabler
+}
+
+func (t taskDo) Debug() ITaskDo {
+ return t.withDO(t.DO.Debug())
+}
+
+func (t taskDo) WithContext(ctx context.Context) ITaskDo {
+ return t.withDO(t.DO.WithContext(ctx))
+}
+
+func (t taskDo) ReadDB() ITaskDo {
+ return t.Clauses(dbresolver.Read)
+}
+
+func (t taskDo) WriteDB() ITaskDo {
+ return t.Clauses(dbresolver.Write)
+}
+
+func (t taskDo) Session(config *gorm.Session) ITaskDo {
+ return t.withDO(t.DO.Session(config))
+}
+
+func (t taskDo) Clauses(conds ...clause.Expression) ITaskDo {
+ return t.withDO(t.DO.Clauses(conds...))
+}
+
+func (t taskDo) Returning(value interface{}, columns ...string) ITaskDo {
+ return t.withDO(t.DO.Returning(value, columns...))
+}
+
+func (t taskDo) Not(conds ...gen.Condition) ITaskDo {
+ return t.withDO(t.DO.Not(conds...))
+}
+
+func (t taskDo) Or(conds ...gen.Condition) ITaskDo {
+ return t.withDO(t.DO.Or(conds...))
+}
+
+func (t taskDo) Select(conds ...field.Expr) ITaskDo {
+ return t.withDO(t.DO.Select(conds...))
+}
+
+func (t taskDo) Where(conds ...gen.Condition) ITaskDo {
+ return t.withDO(t.DO.Where(conds...))
+}
+
+func (t taskDo) Order(conds ...field.Expr) ITaskDo {
+ return t.withDO(t.DO.Order(conds...))
+}
+
+func (t taskDo) Distinct(cols ...field.Expr) ITaskDo {
+ return t.withDO(t.DO.Distinct(cols...))
+}
+
+func (t taskDo) Omit(cols ...field.Expr) ITaskDo {
+ return t.withDO(t.DO.Omit(cols...))
+}
+
+func (t taskDo) Join(table schema.Tabler, on ...field.Expr) ITaskDo {
+ return t.withDO(t.DO.Join(table, on...))
+}
+
+func (t taskDo) LeftJoin(table schema.Tabler, on ...field.Expr) ITaskDo {
+ return t.withDO(t.DO.LeftJoin(table, on...))
+}
+
+func (t taskDo) RightJoin(table schema.Tabler, on ...field.Expr) ITaskDo {
+ return t.withDO(t.DO.RightJoin(table, on...))
+}
+
+func (t taskDo) Group(cols ...field.Expr) ITaskDo {
+ return t.withDO(t.DO.Group(cols...))
+}
+
+func (t taskDo) Having(conds ...gen.Condition) ITaskDo {
+ return t.withDO(t.DO.Having(conds...))
+}
+
+func (t taskDo) Limit(limit int) ITaskDo {
+ return t.withDO(t.DO.Limit(limit))
+}
+
+func (t taskDo) Offset(offset int) ITaskDo {
+ return t.withDO(t.DO.Offset(offset))
+}
+
+func (t taskDo) Scopes(funcs ...func(gen.Dao) gen.Dao) ITaskDo {
+ return t.withDO(t.DO.Scopes(funcs...))
+}
+
+func (t taskDo) Unscoped() ITaskDo {
+ return t.withDO(t.DO.Unscoped())
+}
+
+func (t taskDo) Create(values ...*model.Task) error {
+ if len(values) == 0 {
+ return nil
+ }
+ return t.DO.Create(values)
+}
+
+func (t taskDo) CreateInBatches(values []*model.Task, batchSize int) error {
+ return t.DO.CreateInBatches(values, batchSize)
+}
+
+// Save : !!! underlying implementation is different with GORM
+// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)
+func (t taskDo) Save(values ...*model.Task) error {
+ if len(values) == 0 {
+ return nil
+ }
+ return t.DO.Save(values)
+}
+
+func (t taskDo) First() (*model.Task, error) {
+ if result, err := t.DO.First(); err != nil {
+ return nil, err
+ } else {
+ return result.(*model.Task), nil
+ }
+}
+
+func (t taskDo) Take() (*model.Task, error) {
+ if result, err := t.DO.Take(); err != nil {
+ return nil, err
+ } else {
+ return result.(*model.Task), nil
+ }
+}
+
+func (t taskDo) Last() (*model.Task, error) {
+ if result, err := t.DO.Last(); err != nil {
+ return nil, err
+ } else {
+ return result.(*model.Task), nil
+ }
+}
+
+func (t taskDo) Find() ([]*model.Task, error) {
+ result, err := t.DO.Find()
+ return result.([]*model.Task), err
+}
+
+func (t taskDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.Task, err error) {
+ buf := make([]*model.Task, 0, batchSize)
+ err = t.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {
+ defer func() { results = append(results, buf...) }()
+ return fc(tx, batch)
+ })
+ return results, err
+}
+
+func (t taskDo) FindInBatches(result *[]*model.Task, batchSize int, fc func(tx gen.Dao, batch int) error) error {
+ return t.DO.FindInBatches(result, batchSize, fc)
+}
+
+func (t taskDo) Attrs(attrs ...field.AssignExpr) ITaskDo {
+ return t.withDO(t.DO.Attrs(attrs...))
+}
+
+func (t taskDo) Assign(attrs ...field.AssignExpr) ITaskDo {
+ return t.withDO(t.DO.Assign(attrs...))
+}
+
+func (t taskDo) Joins(fields ...field.RelationField) ITaskDo {
+ for _, _f := range fields {
+ t = *t.withDO(t.DO.Joins(_f))
+ }
+ return &t
+}
+
+func (t taskDo) Preload(fields ...field.RelationField) ITaskDo {
+ for _, _f := range fields {
+ t = *t.withDO(t.DO.Preload(_f))
+ }
+ return &t
+}
+
+func (t taskDo) FirstOrInit() (*model.Task, error) {
+ if result, err := t.DO.FirstOrInit(); err != nil {
+ return nil, err
+ } else {
+ return result.(*model.Task), nil
+ }
+}
+
+func (t taskDo) FirstOrCreate() (*model.Task, error) {
+ if result, err := t.DO.FirstOrCreate(); err != nil {
+ return nil, err
+ } else {
+ return result.(*model.Task), nil
+ }
+}
+
+func (t taskDo) FindByPage(offset int, limit int) (result []*model.Task, count int64, err error) {
+ result, err = t.Offset(offset).Limit(limit).Find()
+ if err != nil {
+ return
+ }
+
+ if size := len(result); 0 < limit && 0 < size && size < limit {
+ count = int64(size + offset)
+ return
+ }
+
+ count, err = t.Offset(-1).Limit(-1).Count()
+ return
+}
+
+func (t taskDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {
+ count, err = t.Count()
+ if err != nil {
+ return
+ }
+
+ err = t.Offset(offset).Limit(limit).Scan(result)
+ return
+}
+
+func (t taskDo) Scan(result interface{}) (err error) {
+ return t.DO.Scan(result)
+}
+
+func (t taskDo) Delete(models ...*model.Task) (result gen.ResultInfo, err error) {
+ return t.DO.Delete(models)
+}
+
+func (t *taskDo) withDO(do gen.Dao) *taskDo {
+ t.DO = *do.(*gen.DO)
+ return t
+}
diff --git a/repositories/message.go b/repositories/message.go
new file mode 100644
index 0000000..faf9ae5
--- /dev/null
+++ b/repositories/message.go
@@ -0,0 +1,48 @@
+package repositories
+
+import (
+ "context"
+ "errors"
+
+ "git.kocoder.xyz/kocoded/vt/fx/interfaces/stores"
+)
+
+type InMemoryMessageRepository struct {
+ m map[int]*stores.Message
+}
+
+func (i *InMemoryMessageRepository) AddMessage(_ context.Context, m *stores.Message) error {
+ i.m[m.Id] = m
+
+ return nil
+}
+
+func (i *InMemoryMessageRepository) GetMessage(ctx context.Context, id int) (*stores.Message, error) {
+ m, ok := i.m[id]
+
+ if !ok {
+ return nil, errors.New("Message not found")
+ }
+
+ return m, nil
+}
+
+func (i *InMemoryMessageRepository) ListMessages(ctx context.Context) ([]*stores.Message, error) {
+ messages := make([]*stores.Message, 0, len(i.m))
+
+ for _, v := range i.m {
+ messages = append(messages, v)
+ }
+
+ return messages, nil
+}
+
+func (i *InMemoryMessageRepository) RemoveMessage(ctx context.Context, id int) error {
+ delete(i.m, id)
+
+ return nil
+}
+
+func NewInMemeoryMessageRepository() stores.MessageStore {
+ return &InMemoryMessageRepository{}
+}
diff --git a/repositories/session.go b/repositories/session.go
new file mode 100644
index 0000000..5ddc6bb
--- /dev/null
+++ b/repositories/session.go
@@ -0,0 +1,78 @@
+package repositories
+
+import (
+ "context"
+ "fmt"
+ "log/slog"
+ "strconv"
+ "time"
+
+ "git.kocoder.xyz/kocoded/vt/fx/interfaces/stores"
+ glide "github.com/valkey-io/valkey-glide/go/v2"
+)
+
+type SessionRepository struct {
+ store *glide.Client
+ logger *slog.Logger
+}
+
+func NewSessionRepository(store *glide.Client, logger *slog.Logger) stores.SessionStore {
+ return &SessionRepository{store: store, logger: logger}
+}
+
+func (sr *SessionRepository) AddSession(s *stores.Session) {
+ // options.HSetExOptions{Expiry: options.NewExpiryIn(time.Hour * 2)}
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
+ defer cancel()
+
+ _, err := sr.store.HSet(ctx, s.Token, s.Serialize())
+ if err != nil {
+ sr.logger.Error("Error adding session to store", "error", err)
+ panic(err)
+ }
+
+ _, err = sr.store.Expire(context.Background(), s.Token, time.Hour*2)
+ if err != nil {
+ sr.logger.Error("Error setting session expiration", "error", err)
+ panic(err)
+ }
+}
+
+func (sr *SessionRepository) GetSessionFromToken(token string) (*stores.Session, error) {
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
+ defer cancel()
+
+ s, err := sr.store.HGetAll(ctx, token)
+ if err != nil {
+ sr.logger.Error("Error getting session from store", "error", err)
+ fmt.Println("PANIC - HGETALL")
+ panic(err)
+ }
+
+ _, err = sr.store.Expire(ctx, token, time.Hour*2)
+ if err != nil {
+ sr.logger.Error("Error setting session expiration", "error", err)
+ panic(err)
+ }
+
+ return (&stores.Session{}).Deserialize(token, s)
+}
+func (sr *SessionRepository) SetMandantInSession(token string, mandantId uint) error {
+ _, err := sr.store.HSet(context.Background(), token, map[string]string{"mandantid": strconv.Itoa(int(mandantId))})
+
+ if err != nil {
+ sr.logger.Error("Error setting mandant in session", "error", err)
+ return err
+ }
+
+ return nil
+}
+
+func (sr *SessionRepository) RemoveSession(token string) {
+ _, err := sr.store.HDel(context.Background(), token, []string{"userid", "mandantid"})
+
+ if err != nil {
+ sr.logger.Error("Error removing session from store", "error", err)
+ panic(err)
+ }
+}
diff --git a/routers/ansprechpartner.go b/routers/ansprechpartner.go
deleted file mode 100644
index 96baa78..0000000
--- a/routers/ansprechpartner.go
+++ /dev/null
@@ -1,128 +0,0 @@
-package routers
-
-import (
- "git.kocoder.xyz/kocoded/vt/model"
- "git.kocoder.xyz/kocoded/vt/query"
- "git.kocoder.xyz/kocoded/vt/utils"
- "github.com/gofiber/fiber/v2"
-)
-
-type ansprechpartnerRouter struct {
- utils.Application
-}
-
-func RegisterAnsprechpartnerRouter(group fiber.Router, appCtx utils.Application) {
- router := &ansprechpartnerRouter{Application: appCtx}
-
- r := group.Use(utils.IsAuthenticated(appCtx))
- r.Post("/new", router.createAnsprechpartner)
- r.Get("/all", router.getAllAnsprechpartners)
- r.Get("/:id", router.getAnsprechpartner)
- r.Get("/:id/firmen", router.getAnsprechpartnerFirmen)
- r.Put("/:id", router.updateAnsprechpartner)
- r.Delete("/:id", router.deleteAnsprechpartner)
-}
-
-func (r *ansprechpartnerRouter) createAnsprechpartner(c *fiber.Ctx) error {
- ansprechpartner := new(model.Ansprechpartner)
-
- if err := c.BodyParser(ansprechpartner); err != nil {
- return err
- }
-
- ap := query.Ansprechpartner
-
- if err := ap.Omit(ap.UpdatedAt, ap.DeletedAt).Create(ansprechpartner); err != nil {
- return err
- }
-
- return c.SendString("Hello")
-}
-
-func (r *ansprechpartnerRouter) getAllAnsprechpartners(c *fiber.Ctx) error {
- aps, err := query.Ansprechpartner.Find()
- if err != nil {
- return err
- }
-
- err = c.JSON(aps)
-
- if err != nil {
- return err
- }
- return nil
-}
-
-func (r *ansprechpartnerRouter) getAnsprechpartner(c *fiber.Ctx) error {
- id, err := c.ParamsInt("id")
-
- if err != nil {
- return err
- }
-
- ap, err := query.Ansprechpartner.Where(query.Ansprechpartner.ID.Eq(uint(id))).First()
- if err != nil {
- return err
- }
-
- return c.JSON(ap)
-}
-
-func (r *ansprechpartnerRouter) getAnsprechpartnerFirmen(c *fiber.Ctx) error {
- id, err := c.ParamsInt("id")
-
- if err != nil {
- return err
- }
-
- ap, err := query.Ansprechpartner.Where(query.Ansprechpartner.ID.Eq(uint(id))).First()
- if err != nil {
- return err
- }
-
- firmen, err := query.Ansprechpartner.Firmen.Model(ap).Find()
- if err != nil {
- return err
- }
-
- ap.Firmen = firmen
-
- return c.JSON(ap)
-}
-
-func (r *ansprechpartnerRouter) updateAnsprechpartner(c *fiber.Ctx) error {
- ansprechpartner := new(model.Ansprechpartner)
- id, err := c.ParamsInt("id")
-
- if err != nil {
- return err
- }
-
- if err = c.BodyParser(ansprechpartner); err != nil {
- return err
- }
-
- ap := query.Ansprechpartner
-
- res, err := ap.Where(ap.ID.Eq(uint(id))).Omit(ap.ID, ap.CreatedAt, ap.UpdatedAt, ap.DeletedAt).Updates(ansprechpartner)
- if err != nil {
- return err
- }
-
- return c.JSON(res)
-}
-
-func (r *ansprechpartnerRouter) deleteAnsprechpartner(c *fiber.Ctx) error {
- id, err := c.ParamsInt("id")
-
- if err != nil {
- return err
- }
-
- res, err := query.Ansprechpartner.Where(query.Ansprechpartner.ID.Value(uint(id))).Delete()
- if err != nil {
- return err
- }
-
- return c.JSON(res)
-}
diff --git a/routers/firma.go b/routers/firma.go
deleted file mode 100644
index 11b85c6..0000000
--- a/routers/firma.go
+++ /dev/null
@@ -1,105 +0,0 @@
-package routers
-
-import (
- "git.kocoder.xyz/kocoded/vt/model"
- "git.kocoder.xyz/kocoded/vt/query"
- "git.kocoder.xyz/kocoded/vt/utils"
- "github.com/gofiber/fiber/v2"
-)
-
-type firmaRouter struct {
- utils.Application
-}
-
-func RegisterFirmaRouter(group fiber.Router, appCtx utils.Application) {
- router := &firmaRouter{Application: appCtx}
-
- r := group.Use(utils.IsAuthenticated(appCtx))
- r.Post("/new", router.createFirma)
- r.Get("/all", router.getAllFirmen)
- r.Get("/:id", router.getFirma)
- r.Put("/:id", router.updateFirma)
- r.Delete("/:id", router.deleteFirma)
-}
-
-func (r *firmaRouter) createFirma(c *fiber.Ctx) error {
- firma := new(model.Firma)
-
- if err := c.BodyParser(firma); err != nil {
- return err
- }
-
- ap := query.Firma
-
- if err := ap.Omit(ap.UpdatedAt, ap.DeletedAt).Create(firma); err != nil {
- return err
- }
-
- return c.SendString("Hello")
-}
-
-func (r *firmaRouter) getAllFirmen(c *fiber.Ctx) error {
- aps, err := query.Firma.Find()
- if err != nil {
- return err
- }
-
- err = c.JSON(aps)
-
- if err != nil {
- return err
- }
- return nil
-}
-
-func (r *firmaRouter) getFirma(c *fiber.Ctx) error {
- id, err := c.ParamsInt("id")
-
- if err != nil {
- return err
- }
-
- ap, err := query.Firma.Where(query.Firma.ID.Eq(uint(id))).First()
- if err != nil {
- return err
- }
-
- return c.JSON(ap)
-}
-
-func (r *firmaRouter) updateFirma(c *fiber.Ctx) error {
- firma := new(model.Firma)
- id, err := c.ParamsInt("id")
-
- if err != nil {
- return err
- }
-
- if err = c.BodyParser(firma); err != nil {
- return err
- }
-
- ap := query.Firma
-
- res, err := ap.Where(ap.ID.Eq(uint(id))).Omit(ap.ID, ap.CreatedAt, ap.UpdatedAt, ap.DeletedAt).Updates(firma)
- if err != nil {
- return err
- }
-
- return c.JSON(res)
-}
-
-func (r *firmaRouter) deleteFirma(c *fiber.Ctx) error {
- id, err := c.ParamsInt("id")
-
- if err != nil {
- return err
- }
-
- res, err := query.Firma.Where(query.Firma.ID.Value(uint(id))).Delete()
- if err != nil {
- return err
- }
-
- return c.JSON(res)
-}
diff --git a/routers/mandant.go b/routers/mandant.go
deleted file mode 100644
index 8f1b100..0000000
--- a/routers/mandant.go
+++ /dev/null
@@ -1,65 +0,0 @@
-package routers
-
-import (
- "git.kocoder.xyz/kocoded/vt/model"
- "git.kocoder.xyz/kocoded/vt/query"
- "git.kocoder.xyz/kocoded/vt/utils"
- "github.com/gofiber/fiber/v2"
-)
-
-type mandantRouter struct {
- utils.Application
- currentMandant uint
-}
-
-func RegisterMandantRouter(group fiber.Router, appCtx utils.Application) {
- router := &mandantRouter{currentMandant: 1, Application: appCtx}
-
- r := group.Use(utils.IsAuthenticated(appCtx))
- r.Get("/current", router.getCurrentMandant)
- r.Put("/current", router.setCurrentMandant)
- r.Get("/all", router.getAllMandant)
-}
-
-func (r *mandantRouter) getCurrentMandant(c *fiber.Ctx) error {
- m := query.Mandant
-
- currentMandant, err := m.Where(m.ID.Eq(r.currentMandant)).First()
- if err != nil {
- r.Logger.Warn("Current mandant not found.", "error", err)
- return c.SendStatus(fiber.StatusInternalServerError)
- }
-
- return c.JSON(currentMandant)
-}
-
-func (r *mandantRouter) getAllMandant(c *fiber.Ctx) error {
- m := query.Mandant
-
- mandanten, err := m.Find()
- if err != nil {
- r.Logger.Warn("Current mandant not found.", "error", err)
- return c.SendStatus(fiber.StatusInternalServerError)
- }
-
- return c.JSON(mandanten)
-}
-
-func (r *mandantRouter) setCurrentMandant(c *fiber.Ctx) error {
- m := query.Mandant
- mandant := &model.Mandant{}
-
- if err := c.BodyParser(mandant); err != nil {
- return err
- }
-
- r.currentMandant = mandant.ID
-
- currentMandant, err := m.Where(m.ID.Eq(r.currentMandant)).First()
- if err != nil {
- r.Logger.Warn("Current mandant not found.", "error", err)
- return c.SendStatus(fiber.StatusInternalServerError)
- }
-
- return c.JSON(currentMandant)
-}
diff --git a/routers/mandant/v1/mandant.go b/routers/mandant/v1/mandant.go
new file mode 100644
index 0000000..728170e
--- /dev/null
+++ b/routers/mandant/v1/mandant.go
@@ -0,0 +1,96 @@
+package mandantv1
+
+import (
+ "context"
+ "errors"
+ "log/slog"
+
+ "connectrpc.com/connect"
+ "git.kocoder.xyz/kocoded/vt/fx"
+ "git.kocoder.xyz/kocoded/vt/fx/interfaces/stores"
+ mandantv1 "git.kocoder.xyz/kocoded/vt/gen/mandant/v1"
+ "git.kocoder.xyz/kocoded/vt/gen/mandant/v1/mandantv1connect"
+ "git.kocoder.xyz/kocoded/vt/interceptors"
+ "git.kocoder.xyz/kocoded/vt/query"
+ "gorm.io/gorm"
+)
+
+func NewMandantRoute(logger *slog.Logger, db *gorm.DB, sr stores.SessionStore, oidcInterceptor *interceptors.AuthenticationInterceptor) fx.Handler {
+ path, handler := mandantv1connect.NewMandantServiceHandler(&mandantService{logger: logger, db: db, sr: sr}, connect.WithInterceptors(oidcInterceptor))
+
+ return fx.NewRoute(path, handler)
+}
+
+type mandantService struct {
+ logger *slog.Logger
+ db *gorm.DB
+ sr stores.SessionStore
+}
+
+func (m *mandantService) GetAllTenants(ctx context.Context, req *mandantv1.ListTenantRequest) (*mandantv1.ListProjectsResponse, error) {
+ ma := query.Use(m.db).Mandant
+ tenants, err := ma.Find()
+
+ if err != nil {
+ m.logger.Error("Error fetching tenants", "error", err)
+ return nil, connect.NewError(connect.CodeUnknown, err)
+ }
+
+ tenantResponses := make([]*mandantv1.GetTenantResponse, len(tenants))
+
+ for i, t := range tenants {
+ tenantResponses[i] = &mandantv1.GetTenantResponse{
+ Id: int64(t.ID),
+ Name: t.Name,
+ Plan: t.Plan,
+ Logo: t.Logo,
+ Color: t.Color,
+ }
+ }
+
+ return &mandantv1.ListProjectsResponse{
+ Data: tenantResponses,
+ Meta: &mandantv1.Metadata{
+ TotalCount: int32(len(tenants)),
+ },
+ }, nil
+}
+
+func (m *mandantService) GetCurrentTenant(ctx context.Context, req *mandantv1.GetCurrentTenantRequest) (*mandantv1.GetTenantResponse, error) {
+ s, ok := interceptors.SessionFromContext(ctx)
+ if !ok {
+ return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("No session set."))
+ }
+
+ ma := query.Use(m.db).Mandant
+ res, err := ma.Where(ma.ID.Eq(s.MandantId)).First()
+ if err != nil {
+ m.logger.Error("Error fetching current tenant", "error", err)
+ return nil, connect.NewError(connect.CodeUnknown, err)
+ }
+
+ return &mandantv1.GetTenantResponse{
+ Id: int64(res.ID),
+ Name: res.Name,
+ Plan: res.Plan,
+ Logo: res.Logo,
+ Color: res.Color,
+ }, nil
+}
+
+func (m *mandantService) SetCurrentTenant(ctx context.Context, req *mandantv1.SetCurrentTenantRequest) (*mandantv1.SetCurrentTenantResponse, error) {
+ s, ok := interceptors.SessionFromContext(ctx)
+ if !ok {
+ return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("No session set."))
+ }
+
+ err := m.sr.SetMandantInSession(s.Token, uint(req.TenantId))
+ if err != nil {
+ m.logger.Error("Error setting mandant in session", "error", err)
+ return nil, connect.NewError(connect.CodeUnknown, err)
+ }
+
+ return &mandantv1.SetCurrentTenantResponse{
+ Success: true,
+ }, nil
+}
diff --git a/routers/mandant/v1/mandant.proto b/routers/mandant/v1/mandant.proto
new file mode 100644
index 0000000..91938d2
--- /dev/null
+++ b/routers/mandant/v1/mandant.proto
@@ -0,0 +1,48 @@
+syntax = "proto3";
+
+package mandant.v1;
+
+message GetCurrentTenantRequest {
+}
+
+message GetTenantRequest {
+ int64 id = 1;
+}
+
+message GetTenantResponse {
+ int64 id = 1;
+ string name = 2;
+ string plan = 3;
+ string logo = 4;
+ string color = 5;
+}
+
+message ListTenantRequest {
+ int32 page = 1;
+ int32 per_page = 2;
+ string orber_by = 3;
+ bool asc = 4;
+}
+
+message Metadata {
+ int32 totalCount = 1;
+}
+
+message ListProjectsResponse {
+ repeated GetTenantResponse data = 1;
+ Metadata meta = 2;
+}
+
+message SetCurrentTenantRequest {
+ int64 tenant_id = 1;
+}
+
+message SetCurrentTenantResponse {
+ bool success = 1;
+}
+
+service MandantService {
+ rpc GetCurrentTenant(GetCurrentTenantRequest) returns (GetTenantResponse);
+ rpc GetAllTenants(ListTenantRequest) returns (ListProjectsResponse);
+ rpc SetCurrentTenant(SetCurrentTenantRequest) returns (SetCurrentTenantResponse);
+}
diff --git a/routers/message/v1/message.proto b/routers/message/v1/message.proto
new file mode 100644
index 0000000..91938d2
--- /dev/null
+++ b/routers/message/v1/message.proto
@@ -0,0 +1,48 @@
+syntax = "proto3";
+
+package mandant.v1;
+
+message GetCurrentTenantRequest {
+}
+
+message GetTenantRequest {
+ int64 id = 1;
+}
+
+message GetTenantResponse {
+ int64 id = 1;
+ string name = 2;
+ string plan = 3;
+ string logo = 4;
+ string color = 5;
+}
+
+message ListTenantRequest {
+ int32 page = 1;
+ int32 per_page = 2;
+ string orber_by = 3;
+ bool asc = 4;
+}
+
+message Metadata {
+ int32 totalCount = 1;
+}
+
+message ListProjectsResponse {
+ repeated GetTenantResponse data = 1;
+ Metadata meta = 2;
+}
+
+message SetCurrentTenantRequest {
+ int64 tenant_id = 1;
+}
+
+message SetCurrentTenantResponse {
+ bool success = 1;
+}
+
+service MandantService {
+ rpc GetCurrentTenant(GetCurrentTenantRequest) returns (GetTenantResponse);
+ rpc GetAllTenants(ListTenantRequest) returns (ListProjectsResponse);
+ rpc SetCurrentTenant(SetCurrentTenantRequest) returns (SetCurrentTenantResponse);
+}
diff --git a/routers/messagebus/v1/messagebus.go b/routers/messagebus/v1/messagebus.go
new file mode 100644
index 0000000..dec2886
--- /dev/null
+++ b/routers/messagebus/v1/messagebus.go
@@ -0,0 +1,30 @@
+package messagebusv1
+
+import (
+ "context"
+ "time"
+
+ "connectrpc.com/connect"
+ kfx "git.kocoder.xyz/kocoded/vt/fx"
+ messagebusv1 "git.kocoder.xyz/kocoded/vt/gen/messagebus/v1"
+ "git.kocoder.xyz/kocoded/vt/gen/messagebus/v1/messagebusv1connect"
+)
+
+func NewMessagebusRoute() kfx.Handler {
+ path, handler := messagebusv1connect.NewMessageBusServiceHandler(&messagebusService{})
+
+ return kfx.NewRoute(path, handler)
+}
+
+type messagebusService struct {
+}
+
+func (mbs *messagebusService) SubscribeToConnectInvalidationRequests(ctx context.Context, req *messagebusv1.SubscribeToConnectInvalidationRequestsRequest, res *connect.ServerStream[messagebusv1.MessageBusEntity]) error {
+ for {
+ err := res.Send(&messagebusv1.MessageBusEntity{QueryKey: "Hello World!"})
+ if err != nil {
+ panic(err)
+ }
+ time.Sleep(time.Second * 2)
+ }
+}
diff --git a/routers/messagebus/v1/messagebus.proto b/routers/messagebus/v1/messagebus.proto
new file mode 100644
index 0000000..510f001
--- /dev/null
+++ b/routers/messagebus/v1/messagebus.proto
@@ -0,0 +1,18 @@
+syntax = "proto3";
+
+package messagebus.v1;
+
+enum MessageBusEntityType {
+ OTHER = 0;
+ INVALIDATION_REQUEST = 1;
+}
+
+message MessageBusEntity {
+ string queryKey = 1;
+}
+
+message SubscribeToConnectInvalidationRequestsRequest {}
+
+service MessageBusService {
+ rpc SubscribeToConnectInvalidationRequests(SubscribeToConnectInvalidationRequestsRequest) returns (stream MessageBusEntity);
+}
diff --git a/routers/project.go b/routers/project.go
deleted file mode 100644
index 9a58fd2..0000000
--- a/routers/project.go
+++ /dev/null
@@ -1,171 +0,0 @@
-package routers
-
-import (
- "strconv"
-
- "git.kocoder.xyz/kocoded/vt/model"
- "git.kocoder.xyz/kocoded/vt/query"
- "git.kocoder.xyz/kocoded/vt/utils"
- "github.com/gofiber/fiber/v2"
-)
-
-type projectRouter struct {
- utils.Application
- currentMandant uint
-}
-
-func RegisterProjectRouter(group fiber.Router, appCtx utils.Application) {
- router := &projectRouter{currentMandant: 1, Application: appCtx}
-
- r := group.Use(utils.IsAuthenticated(appCtx))
- r.Get("/all", router.getAllProjects)
- r.Post("/new", router.createNewProject)
- r.Get("/:id", router.getProject)
- r.Post("/:id/edit", router.editProject)
- r.Delete("/:id/delete", router.deleteProject)
-}
-
-func (r *projectRouter) getAllProjects(c *fiber.Ctx) error {
- p := query.Projekt
- pph := c.Get("X-PER-PAGE")
- ofh := c.Get("X-OFFSET")
-
- params := struct {
- Id string `params:"id"`
- Desc bool `params:"desc"`
- }{}
-
- if err := c.QueryParser(¶ms); err != nil {
- r.Logger.Warn("Param Parser Error: ", "err", err)
- }
-
- var pp, of int
- pp, err := strconv.Atoi(pph)
- if err != nil {
- r.Logger.Warn("Per Page header not found.", "error", err)
- return c.SendStatus(fiber.StatusInternalServerError)
- }
- of, err = strconv.Atoi(ofh)
- if err != nil {
- r.Logger.Warn("Offset header not found.", "error", err)
- return c.SendStatus(fiber.StatusInternalServerError)
- }
-
- query := p.Where().Limit(pp).Offset(of)
-
- if params.Id != "" {
- f, ok := p.GetFieldByName(params.Id)
- if ok {
- if params.Desc {
- query = query.Order(f.Desc())
- } else {
- query = query.Order(f.Asc())
- }
- }
- } else {
- query = query.Order(p.ID.Asc())
- }
-
- projects, err := query.Order(p.Name.Asc()).Find()
- if err != nil {
- r.Logger.Warn("Current mandant not found.", "error", err)
- return c.SendStatus(fiber.StatusInternalServerError)
- }
-
- cnt, err := p.Count()
- if err != nil {
- r.Logger.Warn("Current mandant not found.", "error", err)
- return c.SendStatus(fiber.StatusInternalServerError)
- }
-
- res := &PaginatedProjects{Data: projects, Meta: struct {
- TotalProjectsCount int64 `json:"totalProjectsCount"`
- }{TotalProjectsCount: cnt}}
-
- return c.JSON(res)
-}
-
-type PaginatedProjects struct {
- Data []*model.Projekt `json:"data"`
- Meta struct {
- TotalProjectsCount int64 `json:"totalProjectsCount"`
- } `json:"meta"`
-}
-
-func (r *projectRouter) getProject(c *fiber.Ctx) error {
- ids := c.Params("id")
- id, err := strconv.ParseUint(ids, 10, 32)
- if err != nil {
- r.Logger.Warn("Id is not an int.", "error", err)
- return c.SendStatus(fiber.StatusInternalServerError)
- }
-
- p := query.Projekt
-
- currentProject, err := p.Where(p.ID.Eq(uint(id))).First()
- if err != nil {
- r.Logger.Warn("Current mandant not found.", "error", err)
- return c.SendStatus(fiber.StatusInternalServerError)
- }
-
- return c.JSON(currentProject)
-}
-
-func (r *projectRouter) createNewProject(c *fiber.Ctx) error {
- p := query.Projekt
- project := &model.Projekt{}
-
- if err := c.BodyParser(project); err != nil {
- return err
- }
-
- err := p.Create(project)
- if err != nil {
- r.Logger.Warn("Couldn't create Projejct.", "error", err)
- return c.SendStatus(fiber.StatusInternalServerError)
- }
-
- utils.MessageBus.SendMBObject(int(project.MandantID), utils.MessageBusObject{Entity: []string{"projects", "list"}})
- utils.MessageBus.SendMBObject(int(project.MandantID), utils.MessageBusObject{Entity: []string{"projects", "get"}, Id: int(project.ID)})
-
- return c.JSON(project)
-}
-
-func (r *projectRouter) editProject(c *fiber.Ctx) error {
- p := query.Projekt
- project := &model.Projekt{}
-
- if err := c.BodyParser(project); err != nil {
- return err
- }
-
- res, err := p.Where(p.ID.Eq(project.ID)).Updates(project)
- if err != nil {
- r.Logger.Warn("Couldn't create Projejct.", "error", err)
- return c.SendStatus(fiber.StatusInternalServerError)
- }
-
- utils.MessageBus.SendMBObject(int(project.MandantID), utils.MessageBusObject{Entity: []string{"projects", "list"}})
- utils.MessageBus.SendMBObject(int(project.MandantID), utils.MessageBusObject{Entity: []string{"projects", "get"}, Id: int(project.ID)})
-
- return c.JSON(res)
-}
-
-func (r *projectRouter) deleteProject(c *fiber.Ctx) error {
- ids := c.Params("id")
- id, err := strconv.ParseUint(ids, 10, 32)
- if err != nil {
- r.Logger.Warn("Id is not an int.", "error", err)
- return c.SendStatus(fiber.StatusInternalServerError)
- }
-
- p := query.Projekt
-
- res, err := p.Where(p.ID.Eq(uint(id))).Delete()
- if err != nil {
- r.Logger.Warn("Couldn't create Projejct.", "error", err)
- return c.SendStatus(fiber.StatusInternalServerError)
- }
-
- return c.JSON(res)
-}
diff --git a/routers/project/v1/project.go b/routers/project/v1/project.go
new file mode 100644
index 0000000..50ab3b2
--- /dev/null
+++ b/routers/project/v1/project.go
@@ -0,0 +1,125 @@
+package projectv1
+
+import (
+ "context"
+ "errors"
+ "slices"
+
+ "connectrpc.com/connect"
+ "git.kocoder.xyz/kocoded/vt/fx"
+ projectv1 "git.kocoder.xyz/kocoded/vt/gen/project/v1"
+ "git.kocoder.xyz/kocoded/vt/gen/project/v1/projectv1connect"
+ "git.kocoder.xyz/kocoded/vt/interceptors"
+ "git.kocoder.xyz/kocoded/vt/model"
+ "git.kocoder.xyz/kocoded/vt/query"
+ "gorm.io/gorm"
+)
+
+func NewProjectRoute(db *gorm.DB, oidcInterceptor *interceptors.AuthenticationInterceptor) fx.Handler {
+ path, handler := projectv1connect.NewProjectServiceHandler(&projectService{db: db}, connect.WithInterceptors(oidcInterceptor))
+
+ return fx.NewRoute(path, handler)
+}
+
+type projectService struct{ db *gorm.DB }
+
+// ListProjects implements projectv1connect.ProjectServiceHandler.
+func (s *projectService) ListProjects(ctx context.Context, req *projectv1.ListProjectsRequest) (*projectv1.ListProjectsResponse, error) {
+ session, ok := interceptors.SessionFromContext(ctx)
+ if !ok {
+ return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("No session set in interceptor"))
+ }
+
+ p := query.Use(s.db).Projekt
+
+ query := p.Where(p.MandantID.Eq(session.MandantId)).Limit(int(req.PerPage)).Offset(int(req.Page))
+
+ if req.OrberBy != "" {
+ f, ok := p.GetFieldByName(req.OrberBy)
+ if ok {
+ if !req.Asc {
+ query = query.Order(f.Desc())
+ } else {
+ query = query.Order(f.Asc())
+ }
+ }
+ } else {
+ query = query.Order(p.ID.Asc())
+ }
+
+ projects, err := query.Find()
+ if err != nil {
+ return nil, connect.NewError(connect.CodeUnknown, err)
+ }
+
+ cnt, err := p.Where(p.MandantID.Eq(session.MandantId)).Count()
+ if err != nil {
+ return nil, connect.NewError(connect.CodeUnimplemented, err)
+ }
+
+ data := make([]*projectv1.GetProjectResponse, len(projects))
+ for i, p := range projects {
+ data[i] = &projectv1.GetProjectResponse{
+ Id: int64(p.ID),
+ Name: p.Name,
+ Description: p.Description,
+ IsMaterialized: newPointerBoolean(true),
+ IsPersonalized: newPointerBoolean(true),
+ IsConfirmed: newPointerBoolean(true),
+ IsPaid: nil,
+ IsDone: s.IsDone(p),
+ }
+ }
+
+ return &projectv1.ListProjectsResponse{
+ Data: data,
+ Meta: &projectv1.Metadata{TotalCount: int32(cnt)},
+ }, nil
+}
+
+func newPointerBoolean(val bool) *bool {
+ b := val
+ return &b
+}
+
+func (s *projectService) IsDone(p *model.Projekt) *bool {
+ t := query.Use(s.db).Task
+ tasks, err := t.Where(t.ProjektID.Eq(p.ID)).Find()
+ if err != nil {
+ panic(err)
+ }
+ if len(tasks) == 0 {
+ return nil
+ }
+ if i := slices.IndexFunc(tasks, func(t *model.Task) bool {
+ return !t.Status.IsDone()
+ }); i != -1 {
+ return newPointerBoolean(false)
+ }
+ return newPointerBoolean(true)
+}
+
+func (s *projectService) GetProject(ctx context.Context, req *projectv1.GetProjectRequest) (*projectv1.GetProjectResponse, error) {
+ session, ok := interceptors.SessionFromContext(ctx)
+ if !ok {
+ return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("No session set in interceptor"))
+ }
+
+ p := query.Use(s.db).Projekt
+
+ query, err := p.Where(p.ID.Eq(uint(req.Id))).Where(p.MandantID.Eq(session.MandantId)).First()
+ if err != nil {
+ return nil, err
+ }
+
+ return &projectv1.GetProjectResponse{
+ Id: int64(query.ID),
+ Name: query.Name,
+ Description: query.Description,
+ IsMaterialized: newPointerBoolean(true),
+ IsPersonalized: newPointerBoolean(true),
+ IsConfirmed: newPointerBoolean(true),
+ IsPaid: newPointerBoolean(true),
+ IsDone: newPointerBoolean(true),
+ }, nil
+}
diff --git a/routers/project/v1/project.proto b/routers/project/v1/project.proto
new file mode 100644
index 0000000..ea6708d
--- /dev/null
+++ b/routers/project/v1/project.proto
@@ -0,0 +1,39 @@
+syntax = "proto3";
+
+package project.v1;
+
+message GetProjectRequest {
+ int32 id = 1;
+}
+
+message GetProjectResponse {
+ int64 id = 1;
+ string name = 2;
+ string description = 3;
+ optional bool is_materialized = 5;
+ optional bool is_personalized = 6;
+ optional bool is_confirmed = 7;
+ optional bool is_paid = 8;
+ optional bool is_done = 9;
+}
+
+message ListProjectsRequest {
+ int32 page = 1;
+ int32 per_page = 2;
+ string orber_by = 3;
+ bool asc = 4;
+}
+
+message Metadata {
+ int32 totalCount = 1;
+}
+
+message ListProjectsResponse {
+ repeated GetProjectResponse data = 1;
+ Metadata meta = 2;
+}
+
+service ProjectService {
+ rpc GetProject(GetProjectRequest) returns (GetProjectResponse);
+ rpc ListProjects(ListProjectsRequest) returns (ListProjectsResponse);
+}
diff --git a/routers/todo/v1/todo.go b/routers/todo/v1/todo.go
new file mode 100644
index 0000000..26398f3
--- /dev/null
+++ b/routers/todo/v1/todo.go
@@ -0,0 +1,182 @@
+package todov1
+
+import (
+ "context"
+ "errors"
+ "log/slog"
+ "strconv"
+
+ "connectrpc.com/connect"
+ "git.kocoder.xyz/kocoded/vt/fx"
+ todov1 "git.kocoder.xyz/kocoded/vt/gen/todo/v1"
+ "git.kocoder.xyz/kocoded/vt/gen/todo/v1/todov1connect"
+ "git.kocoder.xyz/kocoded/vt/interceptors"
+ "git.kocoder.xyz/kocoded/vt/query"
+ "gorm.io/gen/field"
+ "gorm.io/gorm"
+)
+
+func NewTodoRoute(db *gorm.DB, logger *slog.Logger, oidcInterceptor *interceptors.AuthenticationInterceptor) fx.Handler {
+ path, handler := todov1connect.NewTodoServiceHandler(&todoService{logger: logger, db: db}, connect.WithInterceptors(oidcInterceptor))
+
+ return fx.NewRoute(path, handler)
+}
+
+type todoService struct {
+ logger *slog.Logger
+ db *gorm.DB
+}
+
+func (ts *todoService) GetTodo(ctx context.Context, req *todov1.GetTodosRequest) (*todov1.GetTodosResponse, error) {
+ s, ok := interceptors.SessionFromContext(ctx)
+ if !ok {
+ return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("No session set."))
+ }
+
+ t := query.Use(ts.db).Task
+ res, err := t.Where(t.ID.Eq(uint(req.Id))).Where(t.MandantID.Eq(s.MandantId)).First()
+ if err != nil {
+ return nil, connect.NewError(connect.CodeInternal, err)
+ }
+
+ tres := &todov1.GetTodosResponse{
+ Id: int64(res.ID),
+ Title: res.Titel,
+ Description: res.Description,
+ Status: todov1.Status(res.Status),
+ }
+
+ return tres, nil
+
+ // return &todov1.GetTodosResponse{
+ // Id: 1,
+ // Title: "Sample Todo",
+ // Description: "This is a sample todo item.",
+ // Status: todov1.Status_Doing,
+ // }, nil
+}
+
+func (ts *todoService) ContributeToQuery(f *todov1.Filter, q query.ITaskDo) query.ITaskDo {
+ t := query.Use(ts.db).Task
+
+ switch f.Field {
+ case todov1.Field_FieldDescription, todov1.Field_FieldTitle:
+ var field field.String
+ if f.Field == todov1.Field_FieldDescription {
+ field = t.Description
+ } else {
+ field = t.Titel
+ }
+ switch f.Operation {
+ case todov1.Operation_Equals:
+ q = q.Where(field.Eq(f.Value))
+ case todov1.Operation_NotEquals:
+ q = q.Where(field.Neq(f.Value))
+ case todov1.Operation_GreaterThan:
+ q = q.Where(field.Gt(f.Value))
+ case todov1.Operation_LessThan:
+ q = q.Where(field.Lt(f.Value))
+ case todov1.Operation_Like:
+ q = q.Where(field.Like(f.Value))
+ }
+ case todov1.Field_FieldId:
+ i, err := strconv.Atoi(f.Value)
+ if err != nil {
+ slog.Warn("Value not an int", "err", err)
+ return q
+ }
+ switch f.Operation {
+ case todov1.Operation_Equals:
+ q = q.Where(t.ID.Eq(uint(i)))
+ case todov1.Operation_NotEquals:
+ q = q.Where(t.ID.Neq(uint(i)))
+ case todov1.Operation_GreaterThan:
+ q = q.Where(t.ID.Gt(uint(i)))
+ case todov1.Operation_LessThan:
+ q = q.Where(t.ID.Lt(uint(i)))
+ }
+ case todov1.Field_FieldStatus:
+ i, err := strconv.Atoi(f.Value)
+ if err != nil {
+ slog.Warn("Value not an int", "err", err)
+ return q
+ }
+ switch f.Operation {
+ case todov1.Operation_Equals:
+ q = q.Where(t.Status.Eq(i))
+ case todov1.Operation_NotEquals:
+ q = q.Where(t.Status.Neq(i))
+ case todov1.Operation_GreaterThan:
+ q = q.Where(t.Status.Gt(i))
+ case todov1.Operation_LessThan:
+ q = q.Where(t.Status.Lt(i))
+ }
+ default:
+ return q
+ }
+ return q
+}
+
+func (ts *todoService) ListTodos(ctx context.Context, req *todov1.ListTodosRequest) (*todov1.ListTodosResponse, error) {
+ ts.logger.Info("ListTodos called", "request", req)
+ s, ok := interceptors.SessionFromContext(ctx)
+ if !ok {
+ return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("No session set."))
+ }
+
+ t := query.Use(ts.db).Task
+ taskQuery := t.Where(t.MandantID.Eq(s.MandantId))
+
+ for _, v := range req.Filters {
+ taskQuery = ts.ContributeToQuery(v, taskQuery)
+ }
+
+ taskQuery = taskQuery.Limit(int(req.PerPage)).Offset(int(req.Page))
+ if req.OrberBy != "" {
+ f, ok := t.GetFieldByName(req.OrberBy)
+ if ok {
+ if !req.Asc {
+ taskQuery = taskQuery.Order(f.Desc())
+ } else {
+ taskQuery = taskQuery.Order(f.Asc())
+ }
+ }
+ } else {
+ taskQuery = taskQuery.Order(t.ID.Asc())
+ }
+
+ tasks, err := taskQuery.Find()
+ if err != nil {
+ return nil, err
+ }
+
+ taskQuery = t.Where(t.MandantID.Eq(s.MandantId))
+
+ for _, v := range req.Filters {
+ taskQuery = ts.ContributeToQuery(v, taskQuery)
+ }
+
+ cnt, err := taskQuery.Count()
+
+ if err != nil {
+ ts.logger.Info("Counting Todos failed", "request", req, "err", err)
+ panic(err)
+ }
+
+ data := make([]*todov1.GetTodosResponse, len(tasks))
+ for i, p := range tasks {
+ data[i] = &todov1.GetTodosResponse{
+ Id: int64(p.ID),
+ Title: p.Titel,
+ Description: p.Description,
+ Status: todov1.Status(p.Status),
+ }
+ }
+
+ return &todov1.ListTodosResponse{
+ Data: data,
+ Meta: &todov1.Metadata{
+ TotalCount: int32(cnt),
+ },
+ }, nil
+}
diff --git a/routers/todo/v1/todo.proto b/routers/todo/v1/todo.proto
new file mode 100644
index 0000000..1a74caf
--- /dev/null
+++ b/routers/todo/v1/todo.proto
@@ -0,0 +1,64 @@
+syntax = "proto3";
+
+package todo.v1;
+
+enum Status {
+ Todo = 0;
+ NeedsMoreInfo = 1;
+ Doing = 2;
+ Done = 3;
+}
+
+enum Field {
+ FieldId = 0;
+ FieldTitle = 1;
+ FieldDescription = 2;
+ FieldStatus = 3;
+}
+
+enum Operation {
+ Equals = 0;
+ NotEquals = 1;
+ GreaterThan = 2;
+ LessThan = 3;
+ Like = 4;
+}
+
+message GetTodosRequest {
+ int32 id = 1;
+}
+
+message GetTodosResponse {
+ int64 id = 1;
+ string title = 2;
+ string description = 3;
+ Status status = 4;
+}
+
+message ListTodosRequest {
+ int32 page = 1;
+ int32 per_page = 2;
+ string orber_by = 3;
+ bool asc = 4;
+ repeated Filter filters = 5;
+}
+
+message Filter {
+ Field field = 1;
+ string value = 2;
+ Operation operation = 3;
+}
+
+message Metadata {
+ int32 totalCount = 1;
+}
+
+message ListTodosResponse {
+ repeated GetTodosResponse data = 1;
+ Metadata meta = 2;
+}
+
+service TodoService {
+ rpc GetTodo(GetTodosRequest) returns (GetTodosResponse);
+ rpc ListTodos(ListTodosRequest) returns (ListTodosResponse);
+}
diff --git a/routers/user.go b/routers/user.go
deleted file mode 100644
index 3b47310..0000000
--- a/routers/user.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package routers
-
-import (
- "git.kocoder.xyz/kocoded/vt/query"
- "git.kocoder.xyz/kocoded/vt/utils"
- "github.com/gofiber/fiber/v2"
-)
-
-type userRouter struct {
- utils.Application
-}
-
-func RegisterUserRouter(group fiber.Router, appCtx utils.Application) {
- router := &userRouter{Application: appCtx}
-
- r := group.Use(utils.IsAuthenticated(appCtx))
- r.Get("/current", router.getCurrentUserInfo)
-}
-
-func (r *userRouter) getCurrentUserInfo(c *fiber.Ctx) error {
- u := query.User
-
- session := c.Locals("USER_KEY").(*utils.Session)
-
- currentUser, err := u.Where(u.ID.Eq(session.UserID)).First()
- if err != nil {
- r.Logger.Warn("Current mandant not found.", "error", err)
- return c.SendStatus(fiber.StatusInternalServerError)
- }
-
- return c.JSON(currentUser)
-}
diff --git a/utils/applicationCtx.go b/utils/applicationCtx.go
deleted file mode 100644
index 3a2e4dc..0000000
--- a/utils/applicationCtx.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package utils
-
-import (
- "context"
- "errors"
- "log/slog"
- "strconv"
- "time"
-
- glide "github.com/valkey-io/valkey-glide/go/v2"
- "gorm.io/gorm"
-)
-
-type Application struct {
- Logger *slog.Logger
- DB *gorm.DB
- Client *glide.Client
-}
-
-type Session struct {
- Token string
- UserID uint
- MandantId uint
-}
-
-func (s *Session) Deserialize(t string, m map[string]string) (*Session, error) {
- userid, err := strconv.Atoi(m["userid"])
- if err != nil {
- return nil, errors.New("Userid from cache not an int")
- }
-
- mandantid, err := strconv.Atoi(m["mandantid"])
- if err != nil {
- return nil, errors.New("Mandantid from cache not an int")
- }
-
- s.Token = t
- s.UserID = uint(userid)
- s.MandantId = uint(mandantid)
-
- return s, nil
-}
-
-func (s *Session) Serialize() map[string]string {
- m := make(map[string]string)
- m["userid"] = strconv.Itoa(int(s.UserID))
- m["mandantid"] = strconv.Itoa(int(s.MandantId))
- return m
-}
-
-func (a *Application) AddSession(s *Session) {
- // options.HSetExOptions{Expiry: options.NewExpiryIn(time.Hour * 2)}
- _, err := a.Client.HSet(context.Background(), s.Token, s.Serialize())
- if err != nil {
- panic(err)
- }
-
- _, err = a.Client.Expire(context.Background(), s.Token, time.Hour*2)
- if err != nil {
- panic(err)
- }
-}
-
-func (a *Application) GetSessionFromToken(token string) (*Session, error) {
- s, err := a.Client.HGetAll(context.Background(), token)
- if err != nil {
- panic(err)
- }
-
- _, err = a.Client.Expire(context.Background(), token, time.Hour*2)
- if err != nil {
- panic(err)
- }
-
- return (&Session{}).Deserialize(token, s)
-}
-
-func (a *Application) RemoveSession(token string) {
- _, err := a.Client.HDel(context.Background(), token, []string{"userid", "mandantid"})
-
- if err != nil {
- panic(err)
- }
-}
diff --git a/utils/authentication.go b/utils/authentication.go
deleted file mode 100644
index 2ed7e28..0000000
--- a/utils/authentication.go
+++ /dev/null
@@ -1,173 +0,0 @@
-package utils
-
-import (
- "context"
- "fmt"
- "net/http"
- "os"
- "time"
-
- "git.kocoder.xyz/kocoded/vt/model"
- "github.com/coreos/go-oidc/v3/oidc"
- "github.com/gofiber/fiber/v2"
- "github.com/gofiber/fiber/v2/middleware/adaptor"
- "golang.org/x/oauth2"
-)
-
-func setCallbackCookie(w http.ResponseWriter, r *http.Request, name, value string) {
- setCallbackCookieExp(w, r, name, value, int(time.Hour.Seconds()))
-}
-
-func setCallbackCookieExp(w http.ResponseWriter, r *http.Request, name, value string, maxAge int) {
- c := &http.Cookie{
- Name: name,
- Value: value,
- Path: "/",
- MaxAge: maxAge,
- Secure: r.TLS != nil,
- HttpOnly: true,
- }
- http.SetCookie(w, c)
-}
-
-type Info struct {
-}
-
-func CreateOIDCClient(ctx context.Context, app *fiber.App, appCtx Application) {
- provider, err := oidc.NewProvider(ctx, "https://keycloak.kocoder.xyz/realms/che")
- if err != nil {
- appCtx.Logger.Error("Error generating OIDC Provider. ", "error", err)
- }
-
- oauthConfig := oauth2.Config{
- ClientID: os.Getenv("CLIENT_ID"),
- ClientSecret: os.Getenv("CLIENT_SECRET"),
- RedirectURL: os.Getenv("BACKEND_URI") + "/api/auth/callback",
-
- Endpoint: provider.Endpoint(),
-
- Scopes: []string{oidc.ScopeOpenID, oidc.ScopeOfflineAccess, "profile", "email"},
- }
-
- app.Get("/api/auth", adaptor.HTTPHandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- state, err := RandString(16)
- if err != nil {
- appCtx.Logger.Warn("Unable to create a state", "error", err)
- http.Error(w, "Unable to create a state", http.StatusInternalServerError)
- }
-
- setCallbackCookie(w, r, "state", state)
-
- http.Redirect(w, r, oauthConfig.AuthCodeURL(state), http.StatusFound)
- }))
-
- app.Get("/api/auth/callback", adaptor.HTTPHandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- state, err := r.Cookie("state")
- if err != nil {
- appCtx.Logger.Warn("State cookie not found", "error", err)
- http.Error(w, "state not found", http.StatusBadRequest)
- return
- }
-
- if r.URL.Query().Get("state") != state.Value {
- appCtx.Logger.Warn("State cookie and header not matching", "error", err)
- http.Error(w, "states not matching", http.StatusBadRequest)
- return
- }
-
- oauth2Token, err := oauthConfig.Exchange(ctx, r.URL.Query().Get("code"))
- if err != nil {
- appCtx.Logger.Warn("Failed to exchange token", "error", err)
- http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusInternalServerError)
- return
- }
-
- userInfo, err := provider.UserInfo(ctx, oauth2.StaticTokenSource(oauth2Token))
- if err != nil {
- appCtx.Logger.Warn("failed to get userinfo", "error", err)
- http.Error(w, "Failed to get userinfo: "+err.Error(), http.StatusInternalServerError)
- return
- }
-
- resp := struct {
- Token *oauth2.Token
- UserInfo *oidc.UserInfo
- }{oauth2Token, userInfo}
-
- claims := &model.User{}
- err = resp.UserInfo.Claims(claims)
- if err != nil {
- panic(err)
- }
- fmt.Println(claims)
-
- user := &model.User{}
- if appCtx.DB.Where(model.User{Email: resp.UserInfo.Email}).Assign(claims).FirstOrCreate(user).Error != nil {
- appCtx.Logger.Warn("Failed to create user in DB")
- http.Error(w, "failed to create user", http.StatusInternalServerError)
- return
- }
-
- setCallbackCookieExp(w, r, "state", "", -1)
-
- cookie, err := RandString(24)
- if err != nil {
- appCtx.Logger.Warn("Couldn't generate session-cookie.")
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-
- setCallbackCookieExp(w, r, "auth-cookie", cookie, int(time.Hour.Seconds()))
- appCtx.AddSession(&Session{Token: cookie, UserID: user.ID, MandantId: 1})
-
- http.Redirect(w, r, os.Getenv("FRONTEND_URI")+"/dashboard", http.StatusFound)
- }))
-
- app.Get("/api/auth/currentSession", func(c *fiber.Ctx) error {
- authToken := c.Cookies("auth-cookie")
-
- session, err := appCtx.GetSessionFromToken(authToken)
- if err != nil {
- return err
- }
-
- return c.JSON(session)
- })
-
- app.Get("/api/auth/logout", func(c *fiber.Ctx) error {
- authToken := c.Cookies("auth-cookie")
-
- appCtx.RemoveSession(authToken)
-
- cookie := new(fiber.Cookie)
- cookie.Name = "auth-cookie"
- cookie.Expires = time.Now().Add(-1 * time.Minute)
-
- c.Cookie(cookie)
-
- return c.Redirect(os.Getenv("FRONTEND_URI"))
- })
-}
-
-type keyType struct{}
-
-var UserKey keyType
-
-func IsAuthenticated(appCtx Application) fiber.Handler {
- fmt.Println("Gettings Session")
-
- return func(c *fiber.Ctx) error {
- authToken := c.Cookies("auth-cookie")
- fmt.Println("Gettings Session", "sessiontoken", authToken)
- session, err := appCtx.GetSessionFromToken(authToken)
- if err != nil {
- appCtx.Logger.Warn("Unauthorized GET Attempt", "reason", err)
- return c.SendStatus(fiber.StatusUnauthorized)
- }
-
- fmt.Println("Saving Session", "session", session)
- c.Locals("USER_KEY", session)
-
- return c.Next()
- }
-}
diff --git a/utils/cache.go b/utils/cache.go
index c170db8..f3f105d 100644
--- a/utils/cache.go
+++ b/utils/cache.go
@@ -1,35 +1,43 @@
package utils
+// Keep (used in cmd/api/main.go)
+
+// Cache utilities for Valkey Glide
import (
"context"
- "fmt"
+ "log/slog"
+ "os"
"strconv"
glide "github.com/valkey-io/valkey-glide/go/v2"
"github.com/valkey-io/valkey-glide/go/v2/config"
)
-func SetupCache(host, port, user, pass string) (*glide.Client, bool) {
+func NewCache(logger *slog.Logger) *glide.Client {
+ user := os.Getenv("VALKEY_USER")
+ pass := os.Getenv("VALKEY_PASS")
+ host := os.Getenv("VALKEY_HOST")
+ port := os.Getenv("VALKEY_PORT")
p, err := strconv.Atoi(port)
+
if err != nil {
- fmt.Println("VALKEY_PORT is not a number")
- return nil, false
+ logger.Error("VALKEY_PORT is not a number", "error", err, "port", port)
+ panic(err)
}
config := config.NewClientConfiguration().WithAddress(&config.NodeAddress{Host: host, Port: p}).WithCredentials(config.NewServerCredentials(user, pass))
client, err := glide.NewClient(config)
if err != nil {
- fmt.Println("There was an error: ", err)
- return nil, false
+ logger.Error("There was an error in glide.NewClient!", "err", err)
+ panic(err)
}
- res, err := client.Ping(context.Background())
+ _, err = client.Ping(context.Background())
if err != nil {
- fmt.Println("There was an error: ", err)
- return nil, false
+ logger.Error("There was an error pinging the client initially!", "err", err)
+ panic(err)
}
- fmt.Println(res) // PONG
- return client, true
+ return client
}
diff --git a/utils/db.go b/utils/db.go
index 5e9846f..4be7db4 100644
--- a/utils/db.go
+++ b/utils/db.go
@@ -1,21 +1,30 @@
package utils
+// Keep (used in cmd/api/main.go)
+
+// Database utilities for GORM
+// using PostgreSQL driver and OpenTelemetry tracing
import (
"log/slog"
+ "os"
"git.kocoder.xyz/kocoded/vt/model"
- "git.kocoder.xyz/kocoded/vt/query"
"gorm.io/driver/postgres"
- "gorm.io/gen"
"gorm.io/gorm"
+
+ "gorm.io/plugin/opentelemetry/tracing"
)
-func SetupDatabase(dsn string, logger *slog.Logger) *gorm.DB {
- db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
+func NewDB(logger *slog.Logger) *gorm.DB {
+ db, err := gorm.Open(postgres.Open(os.Getenv("DB_DSN")), &gorm.Config{})
if err != nil {
logger.Error("Error connecting to the Database", "error", err)
}
+ if err := db.Use(tracing.NewPlugin()); err != nil {
+ panic(err)
+ }
+
err = db.SetupJoinTable(model.Ansprechpartner{}, "Firmen", model.FirmaAnsprechpartner{})
if err != nil {
logger.Error("Error setting up Join Tables", "error", err)
@@ -24,26 +33,10 @@ func SetupDatabase(dsn string, logger *slog.Logger) *gorm.DB {
if err != nil {
logger.Error("Error setting up Join Tables", "error", err)
}
- err = db.AutoMigrate(&model.Mandant{}, &model.User{}, &model.Ansprechpartner{}, &model.FirmaAnsprechpartner{}, &model.Firma{}, &model.Projekt{})
+ err = db.AutoMigrate(&model.Mandant{}, &model.User{}, &model.Ansprechpartner{}, &model.FirmaAnsprechpartner{}, &model.Firma{}, &model.Projekt{}, &model.Task{})
if err != nil {
logger.Error("Error setting up Join Tables", "error", err)
}
- g := gen.NewGenerator(gen.Config{
- OutPath: "query",
- Mode: gen.WithoutContext | gen.WithDefaultQuery | gen.WithQueryInterface, // generate mode
- })
-
- // gormdb, _ := gorm.Open(mysql.Open("root:@(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=True&loc=Local"))
- g.UseDB(db) // reuse your gorm db
-
- // Generate basic type-safe DAO API for struct `model.User` following conventions
- g.ApplyBasic(model.Mandant{}, model.User{}, model.Ansprechpartner{}, model.Dokument{}, model.Firma{}, model.Kalender{}, model.Kalendereintrag{}, model.Kostenstelle{}, model.Lager{}, model.Lagerplatz{}, model.Material{}, model.Nachricht{}, model.Projekt{}, model.Rechnung{}, model.Rechnungsposition{}, model.Scanobject{}, model.User{}, model.Zahlung{}, model.FirmaAnsprechpartner{})
-
- // Generate the code
- g.Execute()
-
- query.SetDefault(db)
-
return db
}
diff --git a/utils/healthcheck.go b/utils/healthcheck.go
new file mode 100644
index 0000000..b201888
--- /dev/null
+++ b/utils/healthcheck.go
@@ -0,0 +1,26 @@
+package utils
+
+// Keep (used in cmd/api/main.go)
+
+// Healthcheck utilities for gRPC services
+// using connect-go and grpchealth
+import (
+ "connectrpc.com/grpchealth"
+ "git.kocoder.xyz/kocoded/vt/fx"
+ "git.kocoder.xyz/kocoded/vt/gen/project/v1/projectv1connect"
+ "git.kocoder.xyz/kocoded/vt/gen/todo/v1/todov1connect"
+)
+
+func NewHealthchecker() grpchealth.Checker {
+ checker := grpchealth.NewStaticChecker()
+
+ checker.SetStatus(projectv1connect.ProjectServiceName, grpchealth.StatusServing)
+ checker.SetStatus(todov1connect.TodoServiceName, grpchealth.StatusServing)
+
+ return checker
+}
+
+func NewHealthCheckV1(healthChecker grpchealth.Checker) fx.Handler {
+ path, handler := grpchealth.NewHandler(healthChecker)
+ return fx.NewRoute(path, handler)
+}
diff --git a/utils/messagebus.go b/utils/messagebus.go
deleted file mode 100644
index 91b4ca4..0000000
--- a/utils/messagebus.go
+++ /dev/null
@@ -1,68 +0,0 @@
-package utils
-
-import (
- "encoding/json"
- "io"
- "log/slog"
-
- "github.com/gofiber/contrib/websocket"
-)
-
-var MessageBus = &messagebus{mandanten: make(map[int][]*websocket.Conn)}
-
-type messagebus struct {
- mandanten map[int][]*websocket.Conn
-}
-
-func (mb *messagebus) AddConn(mid int, c *websocket.Conn) {
- mb.mandanten[mid] = append(mb.mandanten[mid], c)
-
- ReadLoop(c)
-}
-
-func ReadLoop(c *websocket.Conn) {
- for {
- var t int
- var r io.Reader
- var err error
-
- if t, r, err = c.NextReader(); err != nil {
- c.Close()
- break
- }
-
- bytes, err := io.ReadAll(r)
-
- slog.Info("READLOOP: ", "Type", t, "value", bytes)
- }
-}
-
-func (mb *messagebus) SendMBObject(mid int, mbo MessageBusObject) {
- bytes, err := json.Marshal(mbo)
- if err != nil {
- slog.Info("marshal", "error", err)
- }
-
- i := 0
- for _, c := range mb.mandanten[mid] {
- if err := c.WriteMessage(websocket.TextMessage, bytes); err != nil {
- slog.Info("conn to remove", "conns", mb.mandanten)
- mb.RemoveConn(mid, i)
- slog.Info("write", "error", err, "index", i)
- } else {
- i++
- }
- }
-}
-
-func (mb *messagebus) RemoveConn(mid int, i int) {
- s := mb.mandanten[mid]
- s[i] = s[len(s)-1]
- mb.mandanten[mid] = s[:len(s)-1]
- slog.Info("conn removed", "conns", mb.mandanten)
-}
-
-type MessageBusObject struct {
- Entity []string `json:"entity"`
- Id any `json:"id"`
-}
diff --git a/utils/middleware.go b/utils/middleware.go
deleted file mode 100644
index 1eb2910..0000000
--- a/utils/middleware.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package utils
-
-import (
- "os"
-
- "github.com/gofiber/fiber/v2"
- "github.com/gofiber/fiber/v2/middleware/compress"
- "github.com/gofiber/fiber/v2/middleware/cors"
- "github.com/gofiber/fiber/v2/middleware/helmet"
- "github.com/gofiber/fiber/v2/middleware/idempotency"
- "github.com/gofiber/fiber/v2/middleware/logger"
- "github.com/gofiber/fiber/v2/middleware/monitor"
- "github.com/gofiber/fiber/v2/middleware/pprof"
- "github.com/gofiber/fiber/v2/middleware/recover"
- "github.com/gofiber/fiber/v2/middleware/requestid"
- "github.com/gofiber/fiber/v2/middleware/skip"
-)
-
-func RegisterMiddlewares(app *fiber.App) {
- app.Use(requestid.New())
- app.Use(compress.New())
- app.Use(helmet.New())
- app.Use(cors.New(cors.Config{
- AllowOrigins: os.Getenv("FRONTEND_URI"),
- AllowCredentials: true,
- }))
- // app.Use(csrf.New())
- // app.Use(healthcheck.New(healthcheck.Config{}))
- app.Use(idempotency.New())
- // app.Use(limiter.New())
- app.Use(logger.New())
- app.Use("/dbg/monitor", monitor.New())
- app.Use(pprof.New())
- app.Use(recover.New())
- app.Use(skip.New(AddPaginationParams, func(c *fiber.Ctx) bool {
- return c.Method() != fiber.MethodGet
- }))
-}
diff --git a/utils/oidc.go b/utils/oidc.go
new file mode 100644
index 0000000..7f71ce4
--- /dev/null
+++ b/utils/oidc.go
@@ -0,0 +1,26 @@
+package utils
+
+import (
+ "context"
+ "log/slog"
+ "os"
+
+ "github.com/coreos/go-oidc/v3/oidc"
+)
+
+func NewOIDCProvider(logger *slog.Logger) *oidc.Provider {
+ issuerUrl := os.Getenv("OIDC_ISSUER_URL")
+
+ provider, err := oidc.NewProvider(context.Background(), issuerUrl)
+
+ if err != nil {
+ logger.Error("Error generating OIDC Provider. ", "error", err, "url", issuerUrl)
+ panic(err)
+ }
+
+ return provider
+}
+
+func NewOIDCVerifier(provider *oidc.Provider) *oidc.IDTokenVerifier {
+ return provider.Verifier(&oidc.Config{ClientID: "account"})
+}
diff --git a/utils/pagination.go b/utils/pagination.go
deleted file mode 100644
index e539ae9..0000000
--- a/utils/pagination.go
+++ /dev/null
@@ -1,68 +0,0 @@
-package utils
-
-import (
- "errors"
- "strconv"
-
- "github.com/gofiber/fiber/v2"
-)
-
-type OffsetPaginationError struct {
- Page int
- Pages int
- NextPage int
- LastPage int
-}
-
-func (p *OffsetPaginationError) Error() string {
- return "Not an error, just for offsetbased pagination."
-}
-
-func NewOffsetPaginationError(page int, pages int) error {
- var nextPage int
- if page+1 <= pages {
- nextPage = page + 1
- } else {
- nextPage = -1
- }
-
- return &OffsetPaginationError{Page: page, Pages: pages, NextPage: nextPage, LastPage: page - 1}
-}
-
-type KeysetPaginationError struct {
- Key int
- NextKey int
- PreviousKey int
-}
-
-func (p *KeysetPaginationError) Error() string {
- return "Not an error, just for Keysetbased pagination."
-}
-
-func NewKeysetPaginationError(key int, next int, previous int) error {
- return &KeysetPaginationError{Key: key, NextKey: next, PreviousKey: previous}
-}
-
-func AddPaginationParams(c *fiber.Ctx) error {
- err := c.Next()
- if err != nil {
-
- var offset *OffsetPaginationError
- if errors.As(err, &offset) {
- c.Append("X-Page", strconv.Itoa(offset.Page))
- c.Append("X-Pages", strconv.Itoa(offset.Pages))
- c.Append("X-Next-Page", strconv.Itoa(offset.NextPage))
- c.Append("X-Last-Page", strconv.Itoa(offset.LastPage))
- return nil
- }
-
- var keyset *KeysetPaginationError
- if errors.As(err, &keyset) {
- c.Append("X-Key", strconv.Itoa(keyset.Key))
- c.Append("X-Previous-Key", strconv.Itoa(keyset.PreviousKey))
- c.Append("X-Next-Key", strconv.Itoa(keyset.NextKey))
- return nil
- }
- }
- return err
-}
diff --git a/utils/random.go b/utils/random.go
deleted file mode 100644
index d7c7684..0000000
--- a/utils/random.go
+++ /dev/null
@@ -1,15 +0,0 @@
-package utils
-
-import (
- "crypto/rand"
- "encoding/base64"
- "io"
-)
-
-func RandString(nByte int) (string, error) {
- b := make([]byte, nByte)
- if _, err := io.ReadFull(rand.Reader, b); err != nil {
- return "", err
- }
- return base64.RawURLEncoding.EncodeToString(b), nil
-}
diff --git a/utils/reflection.go b/utils/reflection.go
new file mode 100644
index 0000000..c38cb7a
--- /dev/null
+++ b/utils/reflection.go
@@ -0,0 +1,37 @@
+package utils
+
+// Keep (used in cmd/api/main.go)
+
+// Reflection utilities for gRPC services
+// using connect-go and grpcreflect
+import (
+ "connectrpc.com/grpcreflect"
+ "git.kocoder.xyz/kocoded/vt/fx"
+ "git.kocoder.xyz/kocoded/vt/gen/mandant/v1/mandantv1connect"
+ "git.kocoder.xyz/kocoded/vt/gen/messagebus/v1/messagebusv1connect"
+ "git.kocoder.xyz/kocoded/vt/gen/project/v1/projectv1connect"
+ "git.kocoder.xyz/kocoded/vt/gen/todo/v1/todov1connect"
+)
+
+func NewReflector() *grpcreflect.Reflector {
+ strings := []string{
+ todov1connect.TodoServiceName,
+ projectv1connect.ProjectServiceName,
+ mandantv1connect.MandantServiceName,
+ messagebusv1connect.MessageBusServiceName,
+ }
+
+ return grpcreflect.NewReflector(grpcreflect.NamerFunc(func() []string {
+ return strings
+ }))
+}
+
+func NewReflectorV1(reflector *grpcreflect.Reflector) fx.Handler {
+ path, handler := grpcreflect.NewHandlerV1(reflector)
+ return fx.NewRoute(path, handler)
+}
+
+func NewReflectorV1Alpha1(reflector *grpcreflect.Reflector) fx.Handler {
+ path, handler := grpcreflect.NewHandlerV1Alpha(reflector)
+ return fx.NewRoute(path, handler)
+}