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) +}