diff --git a/services/panel-service/.env.example b/services/panel-service/.env.example new file mode 100644 index 0000000..4c8f5f6 --- /dev/null +++ b/services/panel-service/.env.example @@ -0,0 +1,11 @@ +POSTGRES_USER=postgres +POSTGRES_PASS=postgres +POSTGRES_HOST=localhost:5432 +POSTGRES_DATABASE=postgres + +REDIS_HOST=localhost:6379 +REDIS_PASS=redis + +KAFKA_BROKERS=localhost:9092 + +LOG_LEVEL=debug \ No newline at end of file diff --git a/services/panel-service/Dockerfile b/services/panel-service/Dockerfile new file mode 100644 index 0000000..aa7ee97 --- /dev/null +++ b/services/panel-service/Dockerfile @@ -0,0 +1,17 @@ +FROM golang:1.20 AS build +WORKDIR /app + +# Install required modules +COPY go.mod go.sum ./ +RUN go mod download + +# Build the service +COPY . . +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /bin/panel-service ./cmd/panel-service + +# Runtime environment (for minifying image size) +FROM gcr.io/distroless/static-debian12 + +COPY --from=build /bin/panel-service . +EXPOSE 9090 +CMD ["./panel-service"] \ No newline at end of file diff --git a/services/panel-service/Makefile b/services/panel-service/Makefile new file mode 100644 index 0000000..0fa6dc9 --- /dev/null +++ b/services/panel-service/Makefile @@ -0,0 +1,11 @@ +migration-new: + migrate create -ext sql -dir internal/postgres/migrations -seq ${MIGRATION_NAME} + +migration-upgrade: + migrate -path internal/postgres/migrations/ -database "postgres://${POSTGRES_USER}:${POSTGRES_PASS}@${POSTGRES_HOST}/${POSTGRES_DATABASE}?sslmode=disable" up + +migration-downgrade: + migrate -path internal/postgres/migrations/ -database "postgres://${POSTGRES_USER}:${POSTGRES_PASS}@${POSTGRES_HOST}/${POSTGRES_DATABASE}?sslmode=disable" down + +protobufs-compile: + protoc --proto_path=../../protobufs/ --go_out=. --go_opt=Mpanel.proto=./internal/rpc/panelv1 --go-grpc_out=. --go-grpc_opt=Mpanel.proto=./internal/rpc/panelv1 panel.proto \ No newline at end of file diff --git a/services/panel-service/README.md b/services/panel-service/README.md new file mode 100644 index 0000000..f2b44ba --- /dev/null +++ b/services/panel-service/README.md @@ -0,0 +1,62 @@ +# Panel Service + +## Event Documentation + +* Events Produced: + * **Topic:** "``panel``" | **Schema:** "``PanelEvent``" protobuf + * Type: ``"created"`` | Data: ``Panel`` + * Type: ``"updated"`` | Data: ``Panel`` + * Type: ``"deleted"`` | Data: ``Panel`` (only with ``Panel.id`` attribute) + +* Events Consumed: + * N/A + +## Configuration + +### Environment Variables + +**PostgreSQL:** + +``POSTGRES_USER`` (Required) + +* e.g. "postgres" + +``POSTGRES_PASS`` (Required) + +* e.g. "postgres" + +``POSTGRES_HOST`` (Required) + +* e.g. "localhost:5432" + +``POSTGRES_DATABASE`` (Required) + +* e.g. "postgres" + +--- + +**Redis:** + +``REDIS_HOST`` (Required) + +* e.g. "localhost:6379" + +``REDIS_PASS`` (Required) + +* e.g. "redis" + +--- + +**Kafka:** + +``KAFKA_BROKERS`` (Required) + +* e.g. "localhost:9092" or "localhost:9092,localhost:9093" + +--- + +**Other:** + +``LOG_LEVEL`` (Default: "info") + +* i.e. "debug", "info", "warn", "error", "fatal", "panic" or "disabled" diff --git a/services/panel-service/cmd/panel-service/main.go b/services/panel-service/cmd/panel-service/main.go new file mode 100644 index 0000000..7e7a867 --- /dev/null +++ b/services/panel-service/cmd/panel-service/main.go @@ -0,0 +1,37 @@ +package main + +import ( + "context" + + "github.com/rs/zerolog" + + "github.com/hexolan/panels/panel-service/internal" + "github.com/hexolan/panels/panel-service/internal/postgres" + "github.com/hexolan/panels/panel-service/internal/redis" + "github.com/hexolan/panels/panel-service/internal/kafka" + "github.com/hexolan/panels/panel-service/internal/rpc" + "github.com/hexolan/panels/panel-service/internal/service" +) + +func main() { + zerolog.TimeFieldFormat = zerolog.TimeFormatUnix + + // Load the configuration + cfg := internal.NewConfig() + zerolog.SetGlobalLevel(cfg.GetLogLevel()) + + // Loading the dependencies + ctx := context.Background() + db := postgres.NewPostgresInterface(ctx, cfg) + rdb := redis.NewRedisInterface(ctx, cfg) + events := kafka.NewPanelEventProducer(cfg) + + // Create the repositories and services + databaseRepo := postgres.NewPanelRepository(db) + cacheRepo := redis.NewPanelRepository(rdb, databaseRepo) + service := service.NewPanelService(events, cacheRepo) + + // Create and serve RPC + rpcServer := rpc.NewRPCServer(service) + rpcServer.Serve() +} \ No newline at end of file diff --git a/services/panel-service/cmd/panel-service/tools.go b/services/panel-service/cmd/panel-service/tools.go new file mode 100644 index 0000000..5c15732 --- /dev/null +++ b/services/panel-service/cmd/panel-service/tools.go @@ -0,0 +1,12 @@ +//go:build tools +package main + +import ( + // Protobuf Generation + _ "google.golang.org/protobuf/cmd/protoc-gen-go" + _ "google.golang.org/grpc/cmd/protoc-gen-go-grpc" + + // DB Migrations + _ "github.com/golang-migrate/migrate/v4" + _ "github.com/golang-migrate/migrate/v4/database/postgres" +) \ No newline at end of file diff --git a/services/panel-service/go.mod b/services/panel-service/go.mod new file mode 100644 index 0000000..8ef22fc --- /dev/null +++ b/services/panel-service/go.mod @@ -0,0 +1,43 @@ +module github.com/hexolan/panels/panel-service + +go 1.20 + +require ( + github.com/doug-martin/goqu/v9 v9.18.0 + github.com/go-ozzo/ozzo-validation/v4 v4.3.0 + github.com/golang-migrate/migrate/v4 v4.16.2 + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0-rc.5 + github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa + github.com/jackc/pgx/v5 v5.3.1 + github.com/redis/go-redis/v9 v9.0.5 + github.com/rs/zerolog v1.30.0 + github.com/segmentio/kafka-go v0.4.42 + google.golang.org/grpc v1.57.0 + google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 + google.golang.org/protobuf v1.31.0 +) + +require ( + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/puddle/v2 v2.2.0 // indirect + github.com/klauspost/compress v1.16.7 // indirect + github.com/lib/pq v1.10.2 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect + github.com/pierrec/lz4/v4 v4.1.18 // indirect + github.com/stretchr/testify v1.8.4 // indirect + go.uber.org/atomic v1.7.0 // indirect + golang.org/x/crypto v0.12.0 // indirect + golang.org/x/net v0.14.0 // indirect + golang.org/x/sync v0.3.0 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/text v0.12.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect +) diff --git a/services/panel-service/go.sum b/services/panel-service/go.sum new file mode 100644 index 0000000..3c9ec7c --- /dev/null +++ b/services/panel-service/go.sum @@ -0,0 +1,167 @@ +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao= +github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +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/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dhui/dktest v0.3.16 h1:i6gq2YQEtcrjKbeJpBkWjE8MmLZPYllcjOFbTZuPDnw= +github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= +github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/doug-martin/goqu/v9 v9.18.0 h1:/6bcuEtAe6nsSMVK/M+fOiXUNfyFF3yYtE07DBPFMYY= +github.com/doug-martin/goqu/v9 v9.18.0/go.mod h1:nf0Wc2/hV3gYK9LiyqIrzBEVGlI8qW3GuDCEobC4wBQ= +github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= +github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/golang-migrate/migrate/v4 v4.16.2 h1:8coYbMKUyInrFk1lfGfRovTLAW7PhWp8qQDT2iKfuoA= +github.com/golang-migrate/migrate/v4 v4.16.2/go.mod h1:pfcJX4nPHaVdc5nmdCikFBWtm+UBpiZjRNNsyBbp0/o= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0-rc.5 h1:3IZOAnD058zZllQTZNBioTlrzrBG/IjpiZ133IEtusM= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.0-rc.5/go.mod h1:xbKERva94Pw2cPen0s79J3uXmGzbbpDYFBFDlZ4mV/w= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw= +github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= +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-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= +github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= +github.com/jackc/puddle/v2 v2.2.0 h1:RdcDk92EJBuBS55nQMMYFXTxwstHug4jkhT5pq8VxPk= +github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= +github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= +github.com/pierrec/lz4/v4 v4.1.18/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/redis/go-redis/v9 v9.0.5 h1:CuQcn5HIEeK7BgElubPP8CGtE0KakrnbBSTLjathl5o= +github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c= +github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w= +github.com/segmentio/kafka-go v0.4.42 h1:qffhBZCz4WcWyNuHEclHjIMLs2slp6mZO8px+5W5tfU= +github.com/segmentio/kafka-go v0.4.42/go.mod h1:d0g15xPMqoUookug0OU75DhGZxXwCFxSLeJ4uphwJzg= +github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +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-20201119102817-f84b799fce68/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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/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= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= +google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 h1:rNBFJjBCOgVr9pWD7rs/knKL4FRTKgpZmsRfV214zcA= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0/go.mod h1:Dk1tviKTvMCz5tvh7t+fh94dhmQVHuCt2OzJB3CTW9Y= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/services/panel-service/internal/config.go b/services/panel-service/internal/config.go new file mode 100644 index 0000000..f75fc8e --- /dev/null +++ b/services/panel-service/internal/config.go @@ -0,0 +1,89 @@ +package internal + +import ( + "os" + "fmt" + "strings" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +func NewConfig() Config { + // Parse the log level + logLvl, err := zerolog.ParseLevel(optFromEnvFallback("LOG_LEVEL", "info")) + if err != nil { + log.Fatal().Err(err).Msg("invalid log level specified") + } + + // Parse the kafka brokers + kafkaBrokers := strings.Split(optFromEnvRequire("KAFKA_BROKERS"), ",") + if len(kafkaBrokers) == 0 { + log.Fatal().Err(err).Msg("no kafka brokers provided in configuration") + } + + // Create the config + cfg := Config{ + RedisHost: optFromEnvRequire("REDIS_HOST"), + RedisPass: optFromEnvRequire("REDIS_PASS"), + KafkaBrokers: kafkaBrokers, + LogLevel: logLvl, + } + + // Assemble the Config.PostgresURL + cfg.SetPostgresURL( + optFromEnvRequire("POSTGRES_USER"), + optFromEnvRequire("POSTGRES_PASS"), + optFromEnvRequire("POSTGRES_HOST"), + optFromEnvRequire("POSTGRES_DATABASE"), + ) + + return cfg +} + +func optFromEnv(opt string) *string { + optValue, exists := os.LookupEnv(opt) + if !exists || optValue == "" { + return nil + } + return &optValue +} + +func optFromEnvRequire(opt string) string { + optValue := optFromEnv(opt) + if optValue == nil { + log.Fatal().Str("option", opt).Msg("failed to load required config option") + } + return *optValue +} + +func optFromEnvFallback(opt string, fallback string) string { + optValue := optFromEnv(opt) + if optValue == nil { + return fallback + } + return *optValue +} + +type Config struct { + PostgresURL string + + RedisHost string + RedisPass string + + KafkaBrokers []string + + LogLevel zerolog.Level +} + +func (cfg *Config) SetPostgresURL(user string, pass string, host string, db string) { + cfg.PostgresURL = fmt.Sprintf("postgresql://%s:%s@%s/%s?sslmode=disable", user, pass, host, db) +} + +func (cfg Config) GetPostgresURL() string { + return cfg.PostgresURL +} + +func (cfg Config) GetLogLevel() zerolog.Level { + return cfg.LogLevel +} \ No newline at end of file diff --git a/services/panel-service/internal/errors.go b/services/panel-service/internal/errors.go new file mode 100644 index 0000000..620f874 --- /dev/null +++ b/services/panel-service/internal/errors.go @@ -0,0 +1,80 @@ +package internal + +import ( + "fmt" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func NewServiceError(code ErrorCode, msg string) error { + return &ServiceError{ + code: code, + msg: msg, + } +} + +func NewServiceErrorf(code ErrorCode, msg string, args ...interface{}) error { + return NewServiceError(code, fmt.Sprintf(msg, args...)) +} + +func WrapServiceError(original_err error, code ErrorCode, msg string) error { + return &ServiceError{ + code: code, + msg: msg, + original_err: original_err, + } +} + +type ErrorCode int32 + +const ( + UnknownErrorCode ErrorCode = iota + NotFoundErrorCode + ConflictErrorCode + ForbiddenErrorCode + InvalidArgumentErrorCode + ConnectionErrorCode +) + +func (c ErrorCode) GRPCCode() codes.Code { + codeMap := map[ErrorCode]codes.Code{ + UnknownErrorCode: codes.Unknown, + NotFoundErrorCode: codes.NotFound, + ConflictErrorCode: codes.AlreadyExists, + ForbiddenErrorCode: codes.PermissionDenied, + InvalidArgumentErrorCode: codes.InvalidArgument, + ConnectionErrorCode: codes.Unavailable, + } + + grpcCode, mapped := codeMap[c] + if mapped { + return grpcCode + } + return codes.Unknown +} + +type ServiceError struct { + code ErrorCode + msg string + original_err error +} + +func (e ServiceError) Error() string { + if e.original_err != nil { + return fmt.Sprintf("%s: %s", e.msg, e.original_err.Error()) + } + return e.msg +} + +func (e ServiceError) Code() ErrorCode { + return e.code +} + +func (e ServiceError) GRPCStatus() *status.Status { + return status.New(e.Code().GRPCCode(), e.msg) +} + +func (e ServiceError) Unwrap() error { + return e.original_err +} \ No newline at end of file diff --git a/services/panel-service/internal/kafka/panel.go b/services/panel-service/internal/kafka/panel.go new file mode 100644 index 0000000..8f1b59d --- /dev/null +++ b/services/panel-service/internal/kafka/panel.go @@ -0,0 +1,62 @@ +package kafka + +import ( + "context" + + "github.com/rs/zerolog/log" + "github.com/segmentio/kafka-go" + "google.golang.org/protobuf/proto" + + "github.com/hexolan/panels/panel-service/internal" + "github.com/hexolan/panels/panel-service/internal/rpc/panelv1" +) + +type PanelEventProducer struct { + writer *kafka.Writer +} + +func NewPanelEventProducer(cfg internal.Config) PanelEventProducer { + writer := &kafka.Writer{ + Addr: kafka.TCP(cfg.KafkaBrokers...), + Topic: "panel", + Balancer: &kafka.LeastBytes{}, + } + + return PanelEventProducer{writer: writer} +} + +func (ep PanelEventProducer) SendEvent(event *panelv1.PanelEvent) { + // Encode the protobuf event + evtBytes, err := proto.Marshal(event) + if err != nil { + log.Panic().Err(err).Msg("failed to marshal event") + } + + // Write to kafka + err = ep.writer.WriteMessages(context.Background(), kafka.Message{Value: evtBytes}) + if err != nil { + // todo: implement recovery method e.g. storing failed event dispatches on DB to send on recovery (such as from Kafka going offline) + log.Panic().Err(err).Msg("failed to dispatch event") + } +} + +func (ep PanelEventProducer) DispatchCreatedEvent(panel internal.Panel) { + go ep.SendEvent(&panelv1.PanelEvent{ + Type: "created", + Data: panelv1.PanelToProto(&panel), + }) +} + +func (ep PanelEventProducer) DispatchUpdatedEvent(panel internal.Panel) { + go ep.SendEvent(&panelv1.PanelEvent{ + Type: "updated", + Data: panelv1.PanelToProto(&panel), + }) +} + +func (ep PanelEventProducer) DispatchDeletedEvent(id int64) { + go ep.SendEvent(&panelv1.PanelEvent{ + Type: "deleted", + Data: &panelv1.Panel{Id: internal.StringifyPanelId(id)}, + }) +} \ No newline at end of file diff --git a/services/panel-service/internal/panel.go b/services/panel-service/internal/panel.go new file mode 100644 index 0000000..7bed024 --- /dev/null +++ b/services/panel-service/internal/panel.go @@ -0,0 +1,78 @@ +package internal + +import ( + "context" + "regexp" + "strconv" + + "github.com/go-ozzo/ozzo-validation/v4" + "github.com/jackc/pgx/v5/pgtype" +) + +// Panel Model +type Panel struct { + Id int64 `json:"id"` + + Name string `json:"name"` + Description string `json:"description"` + + CreatedAt pgtype.Timestamp `json:"created_at"` + UpdatedAt pgtype.Timestamp `json:"updated_at"` +} + +func StringifyPanelId(id int64) string { + return strconv.FormatInt(id, 10) +} + +func DestringifyPanelId(reprId string) (int64, error) { + id, err := strconv.ParseInt(reprId, 10, 64) + if err != nil || id < 1 { + return 0, NewServiceError(InvalidArgumentErrorCode, "invalid panel id") + } + return id, nil +} + +// Model for creating panels +type PanelCreate struct { + Name string `json:"name"` + Description string `json:"description"` +} + +func (p *PanelCreate) Validate() error { + return validation.ValidateStruct( + p, + validation.Field(&p.Name, validation.Required, validation.Length(3, 32), validation.Match(regexp.MustCompile("^[^_]\\w+[^_]$"))), + validation.Field(&p.Description, validation.Required, validation.Length(3, 512)), + ) +} + +// Model for updating a panel +type PanelUpdate struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` +} + +func (p *PanelUpdate) Validate() error { + return validation.ValidateStruct( + p, + validation.Field(&p.Name, validation.NilOrNotEmpty, validation.Length(3, 32), validation.Match(regexp.MustCompile("^[^_]\\w+[^_]$"))), + validation.Field(&p.Description, validation.NilOrNotEmpty, validation.Length(3, 512)), + ) +} + +// Interface methods +type PanelService interface { + PanelRepository + + GetPanelByName(ctx context.Context, name string) (*Panel, error) + UpdatePanelByName(ctx context.Context, name string, data PanelUpdate) (*Panel, error) + DeletePanelByName(ctx context.Context, name string) error +} + +type PanelRepository interface { + CreatePanel(ctx context.Context, data PanelCreate) (*Panel, error) + GetPanel(ctx context.Context, id int64) (*Panel, error) + GetPanelIdFromName(ctx context.Context, name string) (*int64, error) + UpdatePanel(ctx context.Context, id int64, data PanelUpdate) (*Panel, error) + DeletePanel(ctx context.Context, id int64) error +} \ No newline at end of file diff --git a/services/panel-service/internal/postgres/migrations/000001_initialise.down.sql b/services/panel-service/internal/postgres/migrations/000001_initialise.down.sql new file mode 100644 index 0000000..ea13495 --- /dev/null +++ b/services/panel-service/internal/postgres/migrations/000001_initialise.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS panels CASCADE; \ No newline at end of file diff --git a/services/panel-service/internal/postgres/migrations/000001_initialise.up.sql b/services/panel-service/internal/postgres/migrations/000001_initialise.up.sql new file mode 100644 index 0000000..3041583 --- /dev/null +++ b/services/panel-service/internal/postgres/migrations/000001_initialise.up.sql @@ -0,0 +1,10 @@ +CREATE TABLE panels ( + "id" serial PRIMARY KEY, + "name" varchar(32) NOT NULL, + "description" varchar(512) NOT NULL, + "created_at" timestamp NOT NULL DEFAULT timezone('utc', now()), + "updated_at" timestamp +); +CREATE UNIQUE INDEX panels_name_unique ON "panels" (LOWER("name")); + +INSERT INTO panels ("name", "description") VALUES ('Panel', 'The de facto panel.') ON CONFLICT DO NOTHING; \ No newline at end of file diff --git a/services/panel-service/internal/postgres/panel.go b/services/panel-service/internal/postgres/panel.go new file mode 100644 index 0000000..09c13bd --- /dev/null +++ b/services/panel-service/internal/postgres/panel.go @@ -0,0 +1,140 @@ +package postgres + +import ( + "context" + "errors" + "strings" + "encoding/json" + + "github.com/rs/zerolog/log" + "github.com/doug-martin/goqu/v9" + _ "github.com/doug-martin/goqu/v9/dialect/postgres" + "github.com/jackc/pgerrcode" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgxpool" + + "github.com/hexolan/panels/panel-service/internal" +) + +type panelDatabaseRepo struct { + db *pgxpool.Pool +} + +func NewPanelRepository(db *pgxpool.Pool) internal.PanelRepository { + return panelDatabaseRepo{ + db: db, + } +} + +func (r panelDatabaseRepo) transformToPatchData(data internal.PanelUpdate) goqu.Record { + // Ensure updated_at field is changed + patchData := goqu.Record{"updated_at": goqu.L("timezone('utc', now())")} + + // Marshal the data to remove omitted keys + marshalled, _ := json.Marshal(data) + _ = json.Unmarshal(marshalled, &patchData) + return patchData +} + +func (r panelDatabaseRepo) GetPanelIdFromName(ctx context.Context, name string) (*int64, error) { + var id int64 + err := r.db.QueryRow(ctx, "SELECT id FROM panels WHERE LOWER(name)=LOWER($1)", name).Scan(&id) + if err != nil { + if err == pgx.ErrNoRows { + return nil, internal.WrapServiceError(err, internal.NotFoundErrorCode, "panel not found") + } else if strings.Contains(err.Error(), "failed to connect to") { + return nil, internal.WrapServiceError(err, internal.ConnectionErrorCode, "failed to connect to database") + } + log.Error().Err(err).Msg("unaccounted error whilst getting panel ID from name") + return nil, internal.WrapServiceError(err, internal.UnknownErrorCode, "failed to get panel") + } + + return &id, nil +} + +func (r panelDatabaseRepo) CreatePanel(ctx context.Context, data internal.PanelCreate) (*internal.Panel, error) { + var id int64 + err := r.db.QueryRow(ctx, "INSERT INTO panels (name, description) VALUES ($1, $2) RETURNING id", data.Name, data.Description).Scan(&id) + if err != nil { + var pgErr *pgconn.PgError + if errors.As(err, &pgErr) { + if pgerrcode.IsIntegrityConstraintViolation(pgErr.Code) { + return nil, internal.WrapServiceError(err, internal.ConflictErrorCode, "panel name not unique") + } + } else if strings.Contains(err.Error(), "failed to connect to") { + return nil, internal.WrapServiceError(err, internal.ConnectionErrorCode, "failed to connect to database") + } + log.Error().Err(err).Msg("unaccounted error whilst creating panel") + return nil, internal.WrapServiceError(err, internal.UnknownErrorCode, "failed to create panel") + } + + return r.GetPanel(ctx, id) +} + +func (r panelDatabaseRepo) GetPanel(ctx context.Context, id int64) (*internal.Panel, error) { + var panel internal.Panel + row := r.db.QueryRow(ctx, "SELECT id, name, description, created_at, updated_at FROM panels WHERE id=$1", id) + err := row.Scan(&panel.Id, &panel.Name, &panel.Description, &panel.CreatedAt, &panel.UpdatedAt) + if err != nil { + if err == pgx.ErrNoRows { + return nil, internal.WrapServiceError(err, internal.NotFoundErrorCode, "panel not found") + } else if strings.Contains(err.Error(), "failed to connect to") { + return nil, internal.WrapServiceError(err, internal.ConnectionErrorCode, "failed to connect to database") + } + log.Error().Err(err).Msg("unaccounted error whilst getting panel") + return nil, internal.WrapServiceError(err, internal.UnknownErrorCode, "failed to get panel") + } + + return &panel, nil +} + +func (r panelDatabaseRepo) UpdatePanel(ctx context.Context, id int64, data internal.PanelUpdate) (*internal.Panel, error) { + patchData := r.transformToPatchData(data) + + // Build a statement to update the panel + statement, args, _ := goqu.Dialect("postgres").Update("panels").Prepared(true).Set(patchData).Where(goqu.C("id").Eq(id)).ToSQL() + + // Execute the query + result, err := r.db.Exec(ctx, statement, args...) + if err != nil { + var pgErr *pgconn.PgError + if errors.As(err, &pgErr) { + if pgerrcode.IsIntegrityConstraintViolation(pgErr.Code) { + return nil, internal.WrapServiceError(err, internal.ConflictErrorCode, "panel name not unique") + } + } else if strings.Contains(err.Error(), "failed to connect to") { + return nil, internal.WrapServiceError(err, internal.ConnectionErrorCode, "failed to connect to database") + } + log.Error().Err(err).Msg("unaccounted error whilst updating panel") + return nil, internal.WrapServiceError(err, internal.UnknownErrorCode, "failed to update panel") + } + + // Check if any rows were affected from the query + rows_affected := result.RowsAffected() + if rows_affected != 1 { + return nil, internal.NewServiceError(internal.NotFoundErrorCode, "panel not found") + } + + return r.GetPanel(ctx, id) +} + +func (r panelDatabaseRepo) DeletePanel(ctx context.Context, id int64) error { + // Attempt to delete the panel + result, err := r.db.Exec(ctx, "DELETE FROM panels WHERE id=$1", id) + if err != nil { + if strings.Contains(err.Error(), "failed to connect to") { + return internal.WrapServiceError(err, internal.ConnectionErrorCode, "failed to connect to database") + } + log.Error().Err(err).Msg("unaccounted error whilst deleting panel") + return internal.WrapServiceError(err, internal.UnknownErrorCode, "failed to delete panel") + } + + // Check if any rows were affected + rows_affected := result.RowsAffected() + if rows_affected != 1 { + return internal.NewServiceError(internal.NotFoundErrorCode, "panel not found") + } + + return nil +} \ No newline at end of file diff --git a/services/panel-service/internal/postgres/postgres.go b/services/panel-service/internal/postgres/postgres.go new file mode 100644 index 0000000..7f66155 --- /dev/null +++ b/services/panel-service/internal/postgres/postgres.go @@ -0,0 +1,24 @@ +package postgres + +import ( + "context" + + "github.com/rs/zerolog/log" + "github.com/jackc/pgx/v5/pgxpool" + + "github.com/hexolan/panels/panel-service/internal" +) + +func NewPostgresInterface(ctx context.Context, cfg internal.Config) *pgxpool.Pool { + db, err := pgxpool.New(ctx, cfg.GetPostgresURL()) + if err != nil { + log.Panic().Err(err).Caller().Msg("") + } + + err = db.Ping(ctx) + if err != nil { + log.Warn().Err(err).Msg("failed Postgres ping") + } + + return db +} \ No newline at end of file diff --git a/services/panel-service/internal/redis/panel.go b/services/panel-service/internal/redis/panel.go new file mode 100644 index 0000000..7a9cfef --- /dev/null +++ b/services/panel-service/internal/redis/panel.go @@ -0,0 +1,123 @@ +package redis + +import ( + "time" + "context" + "encoding/json" + + "github.com/rs/zerolog/log" + "github.com/redis/go-redis/v9" + + "github.com/hexolan/panels/panel-service/internal" +) + +type panelCacheRepo struct { + rdb *redis.Client + + repo internal.PanelRepository +} + +func NewPanelRepository(rdb *redis.Client, repo internal.PanelRepository) internal.PanelRepository { + return panelCacheRepo{ + rdb: rdb, + repo: repo, + } +} + +func (r panelCacheRepo) getCachedPanel(ctx context.Context, id int64) *internal.Panel { + value, err := r.rdb.Get(ctx, internal.StringifyPanelId(id)).Result() + if err == redis.Nil { + return nil + } else if err != nil { + log.Error().Err(err).Msg("failed to get cached panel") + return nil + } + + var panel internal.Panel + err = json.Unmarshal([]byte(value), &panel) + if err != nil { + log.Error().Err(err).Msg("failed to unmarshal cached panel") + return nil + } + + return &panel +} + +func (r panelCacheRepo) purgeCachedPanel(ctx context.Context, id int64) { + err := r.rdb.Del(ctx, internal.StringifyPanelId(id)).Err() + if err != nil && err != redis.Nil { + log.Error().Err(err).Msg("error while purging cached panel") + } +} + +func (r panelCacheRepo) cachePanel(ctx context.Context, panel *internal.Panel) { + value, err := json.Marshal(panel) + if err != nil { + log.Error().Err(err).Msg("failed to marshal panel for caching") + return + } + + err = r.rdb.Set(ctx, internal.StringifyPanelId(panel.Id), string(value), 5 * time.Minute).Err() + if err != nil { + log.Error().Err(err).Msg("failed to cache panel") + return + } +} + +func (r panelCacheRepo) GetPanelIdFromName(ctx context.Context, name string) (*int64, error) { + // This is not cached for safety with UpdatePanel and DeletePanel methods. + return r.repo.GetPanelIdFromName(ctx, name) +} + +func (r panelCacheRepo) CreatePanel(ctx context.Context, data internal.PanelCreate) (*internal.Panel, error) { + // Create the panel with the downstream DB repo. + panel, err := r.repo.CreatePanel(ctx, data) + if err != nil { + return panel, err + } + + // Cache and return the created panel. + r.cachePanel(ctx, panel) + return panel, err +} + +func (r panelCacheRepo) GetPanel(ctx context.Context, id int64) (*internal.Panel, error) { + // Check for a cached version of the panel. + if panel := r.getCachedPanel(ctx, id); panel != nil { + return panel, nil + } + + // Panel is not cached. Fetch from the DB repo. + panel, err := r.repo.GetPanel(ctx, id) + if err != nil { + return panel, err + } + + // Cache and return the fetched panel. + r.cachePanel(ctx, panel) + return panel, err +} + +func (r panelCacheRepo) UpdatePanel(ctx context.Context, id int64, data internal.PanelUpdate) (*internal.Panel, error) { + // Update the panel at the downstream repo. + panel, err := r.repo.UpdatePanel(ctx, id, data) + if err != nil { + return panel, err + } + + // Cache and return the updated panel. + r.cachePanel(ctx, panel) + return panel, err +} + +func (r panelCacheRepo) DeletePanel(ctx context.Context, id int64) error { + // Delete the panel downstream. + err := r.repo.DeletePanel(ctx, id) + if err != nil { + return err + } + + // Purge any cached version of the panel. + r.purgeCachedPanel(ctx, id) + return err +} \ No newline at end of file diff --git a/services/panel-service/internal/redis/redis.go b/services/panel-service/internal/redis/redis.go new file mode 100644 index 0000000..fb696b9 --- /dev/null +++ b/services/panel-service/internal/redis/redis.go @@ -0,0 +1,29 @@ +package redis + +import ( + "time" + "context" + + "github.com/rs/zerolog/log" + "github.com/redis/go-redis/v9" + + "github.com/hexolan/panels/panel-service/internal" +) + +func NewRedisInterface(ctx context.Context, cfg internal.Config) *redis.Client { + rdb := redis.NewClient(&redis.Options{ + Addr: cfg.RedisHost, + Password: cfg.RedisPass, + DB: 0, + + DialTimeout: time.Millisecond * 250, + ReadTimeout: time.Millisecond * 500, + }) + + _, err := rdb.Ping(ctx).Result() + if err != nil { + log.Warn().Err(err).Msg("failed Redis ping") + } + + return rdb +} \ No newline at end of file diff --git a/services/panel-service/internal/rpc/panel.go b/services/panel-service/internal/rpc/panel.go new file mode 100644 index 0000000..fb92487 --- /dev/null +++ b/services/panel-service/internal/rpc/panel.go @@ -0,0 +1,162 @@ +package rpc + +import ( + "context" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + emptypb "google.golang.org/protobuf/types/known/emptypb" + + "github.com/hexolan/panels/panel-service/internal" + pb "github.com/hexolan/panels/panel-service/internal/rpc/panelv1" +) + +type panelServer struct { + pb.UnimplementedPanelServiceServer + + service internal.PanelService +} + +func NewPanelServer(service internal.PanelService) panelServer { + return panelServer{service: service} +} + +func (svr *panelServer) CreatePanel(ctx context.Context, request *pb.CreatePanelRequest) (*pb.Panel, error) { + // Ensure the required args are provided + if request.GetData() == nil { + return nil, status.Error(codes.InvalidArgument, "malformed request") + } + + // Convert to business model + data := pb.PanelCreateFromProto(request.GetData()) + + // Attempt to create the panel + panel, err := svr.service.CreatePanel(ctx, data) + if err != nil { + return nil, err + } + + return pb.PanelToProto(panel), nil +} + +func (svr *panelServer) GetPanel(ctx context.Context, request *pb.GetPanelByIdRequest) (*pb.Panel, error) { + // Ensure the required args are provided + if request.GetId() == "" { + return nil, status.Error(codes.InvalidArgument, "panel id not provided") + } + + // Convert to business model + id, err := internal.DestringifyPanelId(request.GetId()) + if err != nil { + return nil, status.Error(codes.InvalidArgument, "invalid panel id") + } + + // Attempt to get the panel + panel, err := svr.service.GetPanel(ctx, id) + if err != nil { + return nil, err + } + return pb.PanelToProto(panel), nil +} + +func (svr *panelServer) GetPanelByName(ctx context.Context, request *pb.GetPanelByNameRequest) (*pb.Panel, error) { + // Ensure the required args are provided + var name string = request.GetName() + if request.GetName() == "" { + return nil, status.Error(codes.InvalidArgument, "invalid panel name") + } + + // Attempt to delete the panel + panel, err := svr.service.GetPanelByName(ctx, name) + if err != nil { + return nil, err + } + return pb.PanelToProto(panel), nil +} + +func (svr *panelServer) UpdatePanel(ctx context.Context, request *pb.UpdatePanelByIdRequest) (*pb.Panel, error) { + // Ensure the required args are provided + if request.GetId() == "" { + return nil, status.Error(codes.InvalidArgument, "panel id not provided") + } + + // Convert to ID to business model + id, err := internal.DestringifyPanelId(request.GetId()) + if err != nil { + return nil, status.Error(codes.InvalidArgument, "invalid panel id") + } + + // Ensure that the data values have been provided + if request.GetData() == nil { + return nil, status.Error(codes.InvalidArgument, "malformed request") + } + + // Convert data to business model + data := pb.PanelUpdateFromProto(request.GetData()) + + // Attempt to update the panel + panel, err := svr.service.UpdatePanel(ctx, id, data) + if err != nil { + return nil, err + } + + return pb.PanelToProto(panel), nil +} + +func (svr *panelServer) UpdatePanelByName(ctx context.Context, request *pb.UpdatePanelByNameRequest) (*pb.Panel, error) { + // Ensure the required args are provided + var name string = request.GetName() + if name == "" { + return nil, status.Error(codes.InvalidArgument, "invalid panel name") + } + + if request.GetData() == nil { + return nil, status.Error(codes.InvalidArgument, "malformed request") + } + + // Convert data to business model + data := pb.PanelUpdateFromProto(request.GetData()) + + // Attempt to update the panel + panel, err := svr.service.UpdatePanelByName(ctx, name, data) + if err != nil { + return nil, err + } + + return pb.PanelToProto(panel), nil +} + +func (svr *panelServer) DeletePanel(ctx context.Context, request *pb.DeletePanelByIdRequest) (*emptypb.Empty, error) { + // Ensure the required args are provided + if request.GetId() == "" { + return nil, status.Error(codes.InvalidArgument, "panel id not provided") + } + + // Convert id to business model + id, err := internal.DestringifyPanelId(request.GetId()) + if err != nil { + return nil, status.Error(codes.InvalidArgument, "invalid panel id") + } + + // Attempt to delete the panel + err = svr.service.DeletePanel(ctx, id) + if err != nil { + return nil, err + } + return &emptypb.Empty{}, nil +} + +func (svr *panelServer) DeletePanelByName(ctx context.Context, request *pb.DeletePanelByNameRequest) (*emptypb.Empty, error) { + // Ensure the required args are provided + var name string = request.GetName() + if name == "" { + return nil, status.Error(codes.InvalidArgument, "invalid panel name") + } + + // Attempt to delete the panel + err := svr.service.DeletePanelByName(ctx, name) + if err != nil { + return nil, err + } + return &emptypb.Empty{}, nil +} \ No newline at end of file diff --git a/services/panel-service/internal/rpc/panelv1/conversion.go b/services/panel-service/internal/rpc/panelv1/conversion.go new file mode 100644 index 0000000..b7588f4 --- /dev/null +++ b/services/panel-service/internal/rpc/panelv1/conversion.go @@ -0,0 +1,58 @@ +package panelv1 + +import ( + "encoding/json" + + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/hexolan/panels/panel-service/internal" +) + +// Panel -> Protobuf 'Panel' +func PanelToProto(panel *internal.Panel) *Panel { + proto := Panel{ + Id: internal.StringifyPanelId(panel.Id), + Name: panel.Name, + Description: panel.Description, + CreatedAt: timestamppb.New(panel.CreatedAt.Time), + } + + // convert nullable attributes to PB form (if present) + if panel.UpdatedAt.Valid == true { + proto.UpdatedAt = timestamppb.New(panel.UpdatedAt.Time) + } + + return &proto +} + +// Protobuf 'Panel' -> Panel +func PanelFromProto(proto *Panel) (*internal.Panel, error) { + marshalled, err := json.Marshal(proto) + if err != nil { + return nil, err + } + + var panel internal.Panel + err = json.Unmarshal(marshalled, &panel) + if err != nil { + return nil, err + } + + return &panel, nil +} + +// Protobuf 'PanelMutable' -> PanelCreate +func PanelCreateFromProto(proto *PanelMutable) internal.PanelCreate { + return internal.PanelCreate{ + Name: proto.GetName(), + Description: proto.GetDescription(), + } +} + +// Protobuf 'PanelMutable' -> PanelUpdate +func PanelUpdateFromProto(proto *PanelMutable) internal.PanelUpdate { + return internal.PanelUpdate{ + Name: proto.Name, + Description: proto.Description, + } +} \ No newline at end of file diff --git a/services/panel-service/internal/rpc/panelv1/panel.pb.go b/services/panel-service/internal/rpc/panelv1/panel.pb.go new file mode 100644 index 0000000..aab4723 --- /dev/null +++ b/services/panel-service/internal/rpc/panelv1/panel.pb.go @@ -0,0 +1,861 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v4.23.4 +// source: panel.proto + +package panelv1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +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 Panel struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,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"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` +} + +func (x *Panel) Reset() { + *x = Panel{} + if protoimpl.UnsafeEnabled { + mi := &file_panel_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Panel) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Panel) ProtoMessage() {} + +func (x *Panel) ProtoReflect() protoreflect.Message { + mi := &file_panel_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Panel.ProtoReflect.Descriptor instead. +func (*Panel) Descriptor() ([]byte, []int) { + return file_panel_proto_rawDescGZIP(), []int{0} +} + +func (x *Panel) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Panel) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Panel) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *Panel) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +func (x *Panel) GetUpdatedAt() *timestamppb.Timestamp { + if x != nil { + return x.UpdatedAt + } + return nil +} + +type PanelMutable struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name *string `protobuf:"bytes,1,opt,name=name,proto3,oneof" json:"name,omitempty"` + Description *string `protobuf:"bytes,2,opt,name=description,proto3,oneof" json:"description,omitempty"` +} + +func (x *PanelMutable) Reset() { + *x = PanelMutable{} + if protoimpl.UnsafeEnabled { + mi := &file_panel_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PanelMutable) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PanelMutable) ProtoMessage() {} + +func (x *PanelMutable) ProtoReflect() protoreflect.Message { + mi := &file_panel_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PanelMutable.ProtoReflect.Descriptor instead. +func (*PanelMutable) Descriptor() ([]byte, []int) { + return file_panel_proto_rawDescGZIP(), []int{1} +} + +func (x *PanelMutable) GetName() string { + if x != nil && x.Name != nil { + return *x.Name + } + return "" +} + +func (x *PanelMutable) GetDescription() string { + if x != nil && x.Description != nil { + return *x.Description + } + return "" +} + +type CreatePanelRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Data *PanelMutable `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *CreatePanelRequest) Reset() { + *x = CreatePanelRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_panel_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreatePanelRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreatePanelRequest) ProtoMessage() {} + +func (x *CreatePanelRequest) ProtoReflect() protoreflect.Message { + mi := &file_panel_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreatePanelRequest.ProtoReflect.Descriptor instead. +func (*CreatePanelRequest) Descriptor() ([]byte, []int) { + return file_panel_proto_rawDescGZIP(), []int{2} +} + +func (x *CreatePanelRequest) GetData() *PanelMutable { + if x != nil { + return x.Data + } + return nil +} + +type GetPanelByIdRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *GetPanelByIdRequest) Reset() { + *x = GetPanelByIdRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_panel_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetPanelByIdRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetPanelByIdRequest) ProtoMessage() {} + +func (x *GetPanelByIdRequest) ProtoReflect() protoreflect.Message { + mi := &file_panel_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetPanelByIdRequest.ProtoReflect.Descriptor instead. +func (*GetPanelByIdRequest) Descriptor() ([]byte, []int) { + return file_panel_proto_rawDescGZIP(), []int{3} +} + +func (x *GetPanelByIdRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type GetPanelByNameRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *GetPanelByNameRequest) Reset() { + *x = GetPanelByNameRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_panel_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetPanelByNameRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetPanelByNameRequest) ProtoMessage() {} + +func (x *GetPanelByNameRequest) ProtoReflect() protoreflect.Message { + mi := &file_panel_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetPanelByNameRequest.ProtoReflect.Descriptor instead. +func (*GetPanelByNameRequest) Descriptor() ([]byte, []int) { + return file_panel_proto_rawDescGZIP(), []int{4} +} + +func (x *GetPanelByNameRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type UpdatePanelByIdRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Data *PanelMutable `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *UpdatePanelByIdRequest) Reset() { + *x = UpdatePanelByIdRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_panel_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdatePanelByIdRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdatePanelByIdRequest) ProtoMessage() {} + +func (x *UpdatePanelByIdRequest) ProtoReflect() protoreflect.Message { + mi := &file_panel_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdatePanelByIdRequest.ProtoReflect.Descriptor instead. +func (*UpdatePanelByIdRequest) Descriptor() ([]byte, []int) { + return file_panel_proto_rawDescGZIP(), []int{5} +} + +func (x *UpdatePanelByIdRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *UpdatePanelByIdRequest) GetData() *PanelMutable { + if x != nil { + return x.Data + } + return nil +} + +type UpdatePanelByNameRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Data *PanelMutable `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *UpdatePanelByNameRequest) Reset() { + *x = UpdatePanelByNameRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_panel_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdatePanelByNameRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdatePanelByNameRequest) ProtoMessage() {} + +func (x *UpdatePanelByNameRequest) ProtoReflect() protoreflect.Message { + mi := &file_panel_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdatePanelByNameRequest.ProtoReflect.Descriptor instead. +func (*UpdatePanelByNameRequest) Descriptor() ([]byte, []int) { + return file_panel_proto_rawDescGZIP(), []int{6} +} + +func (x *UpdatePanelByNameRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *UpdatePanelByNameRequest) GetData() *PanelMutable { + if x != nil { + return x.Data + } + return nil +} + +type DeletePanelByIdRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *DeletePanelByIdRequest) Reset() { + *x = DeletePanelByIdRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_panel_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeletePanelByIdRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeletePanelByIdRequest) ProtoMessage() {} + +func (x *DeletePanelByIdRequest) ProtoReflect() protoreflect.Message { + mi := &file_panel_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeletePanelByIdRequest.ProtoReflect.Descriptor instead. +func (*DeletePanelByIdRequest) Descriptor() ([]byte, []int) { + return file_panel_proto_rawDescGZIP(), []int{7} +} + +func (x *DeletePanelByIdRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type DeletePanelByNameRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *DeletePanelByNameRequest) Reset() { + *x = DeletePanelByNameRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_panel_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeletePanelByNameRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeletePanelByNameRequest) ProtoMessage() {} + +func (x *DeletePanelByNameRequest) ProtoReflect() protoreflect.Message { + mi := &file_panel_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeletePanelByNameRequest.ProtoReflect.Descriptor instead. +func (*DeletePanelByNameRequest) Descriptor() ([]byte, []int) { + return file_panel_proto_rawDescGZIP(), []int{8} +} + +func (x *DeletePanelByNameRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +// Kafka Event Schema +type PanelEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + Data *Panel `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *PanelEvent) Reset() { + *x = PanelEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_panel_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PanelEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PanelEvent) ProtoMessage() {} + +func (x *PanelEvent) ProtoReflect() protoreflect.Message { + mi := &file_panel_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PanelEvent.ProtoReflect.Descriptor instead. +func (*PanelEvent) Descriptor() ([]byte, []int) { + return file_panel_proto_rawDescGZIP(), []int{9} +} + +func (x *PanelEvent) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *PanelEvent) GetData() *Panel { + if x != nil { + return x.Data + } + return nil +} + +var File_panel_proto protoreflect.FileDescriptor + +var file_panel_proto_rawDesc = []byte{ + 0x0a, 0x0b, 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x70, + 0x61, 0x6e, 0x65, 0x6c, 0x73, 0x2e, 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x1a, 0x1b, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, + 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc3, 0x01, 0x0a, + 0x05, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x0a, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, + 0x41, 0x74, 0x22, 0x67, 0x0a, 0x0c, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x4d, 0x75, 0x74, 0x61, 0x62, + 0x6c, 0x65, 0x12, 0x17, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x48, 0x00, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x25, 0x0a, 0x0b, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x48, 0x01, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x88, + 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x47, 0x0a, 0x12, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x31, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1d, 0x2e, 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x73, 0x2e, 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x2e, 0x76, + 0x31, 0x2e, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x4d, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x04, + 0x64, 0x61, 0x74, 0x61, 0x22, 0x25, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x50, 0x61, 0x6e, 0x65, 0x6c, + 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x2b, 0x0a, 0x15, 0x47, + 0x65, 0x74, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x42, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x5b, 0x0a, 0x16, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, + 0x69, 0x64, 0x12, 0x31, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1d, 0x2e, 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x73, 0x2e, 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x2e, + 0x76, 0x31, 0x2e, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x4d, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x52, + 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x61, 0x0a, 0x18, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, + 0x61, 0x6e, 0x65, 0x6c, 0x42, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x31, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x73, 0x2e, 0x70, 0x61, 0x6e, + 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x4d, 0x75, 0x74, 0x61, 0x62, + 0x6c, 0x65, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x28, 0x0a, 0x16, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, + 0x69, 0x64, 0x22, 0x2e, 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x6e, 0x65, + 0x6c, 0x42, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x22, 0x4c, 0x0a, 0x0a, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x12, 0x2a, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x73, 0x2e, 0x70, 0x61, 0x6e, 0x65, + 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, + 0x32, 0xd4, 0x04, 0x0a, 0x0c, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x4c, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x6e, 0x65, 0x6c, + 0x12, 0x23, 0x2e, 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x73, 0x2e, 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x73, 0x2e, 0x70, + 0x61, 0x6e, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x22, 0x00, 0x12, + 0x4a, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x12, 0x24, 0x2e, 0x70, 0x61, + 0x6e, 0x65, 0x6c, 0x73, 0x2e, 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, + 0x74, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x16, 0x2e, 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x73, 0x2e, 0x70, 0x61, 0x6e, 0x65, 0x6c, + 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x22, 0x00, 0x12, 0x52, 0x0a, 0x0e, 0x47, + 0x65, 0x74, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x42, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x26, 0x2e, + 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x73, 0x2e, 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x42, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x73, 0x2e, 0x70, + 0x61, 0x6e, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x22, 0x00, 0x12, + 0x50, 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x12, 0x27, + 0x2e, 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x73, 0x2e, 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x2e, 0x76, 0x31, + 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x42, 0x79, 0x49, 0x64, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x73, + 0x2e, 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x22, + 0x00, 0x12, 0x58, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x6e, 0x65, 0x6c, + 0x42, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x2e, 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x73, 0x2e, + 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, + 0x61, 0x6e, 0x65, 0x6c, 0x42, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x16, 0x2e, 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x73, 0x2e, 0x70, 0x61, 0x6e, 0x65, 0x6c, + 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x22, 0x00, 0x12, 0x50, 0x0a, 0x0b, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x12, 0x27, 0x2e, 0x70, 0x61, 0x6e, + 0x65, 0x6c, 0x73, 0x2e, 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x58, 0x0a, + 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x42, 0x79, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x29, 0x2e, 0x70, 0x61, 0x6e, 0x65, 0x6c, 0x73, 0x2e, 0x70, 0x61, 0x6e, 0x65, + 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x6e, 0x65, 0x6c, + 0x42, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_panel_proto_rawDescOnce sync.Once + file_panel_proto_rawDescData = file_panel_proto_rawDesc +) + +func file_panel_proto_rawDescGZIP() []byte { + file_panel_proto_rawDescOnce.Do(func() { + file_panel_proto_rawDescData = protoimpl.X.CompressGZIP(file_panel_proto_rawDescData) + }) + return file_panel_proto_rawDescData +} + +var file_panel_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_panel_proto_goTypes = []interface{}{ + (*Panel)(nil), // 0: panels.panel.v1.Panel + (*PanelMutable)(nil), // 1: panels.panel.v1.PanelMutable + (*CreatePanelRequest)(nil), // 2: panels.panel.v1.CreatePanelRequest + (*GetPanelByIdRequest)(nil), // 3: panels.panel.v1.GetPanelByIdRequest + (*GetPanelByNameRequest)(nil), // 4: panels.panel.v1.GetPanelByNameRequest + (*UpdatePanelByIdRequest)(nil), // 5: panels.panel.v1.UpdatePanelByIdRequest + (*UpdatePanelByNameRequest)(nil), // 6: panels.panel.v1.UpdatePanelByNameRequest + (*DeletePanelByIdRequest)(nil), // 7: panels.panel.v1.DeletePanelByIdRequest + (*DeletePanelByNameRequest)(nil), // 8: panels.panel.v1.DeletePanelByNameRequest + (*PanelEvent)(nil), // 9: panels.panel.v1.PanelEvent + (*timestamppb.Timestamp)(nil), // 10: google.protobuf.Timestamp + (*emptypb.Empty)(nil), // 11: google.protobuf.Empty +} +var file_panel_proto_depIdxs = []int32{ + 10, // 0: panels.panel.v1.Panel.created_at:type_name -> google.protobuf.Timestamp + 10, // 1: panels.panel.v1.Panel.updated_at:type_name -> google.protobuf.Timestamp + 1, // 2: panels.panel.v1.CreatePanelRequest.data:type_name -> panels.panel.v1.PanelMutable + 1, // 3: panels.panel.v1.UpdatePanelByIdRequest.data:type_name -> panels.panel.v1.PanelMutable + 1, // 4: panels.panel.v1.UpdatePanelByNameRequest.data:type_name -> panels.panel.v1.PanelMutable + 0, // 5: panels.panel.v1.PanelEvent.data:type_name -> panels.panel.v1.Panel + 2, // 6: panels.panel.v1.PanelService.CreatePanel:input_type -> panels.panel.v1.CreatePanelRequest + 3, // 7: panels.panel.v1.PanelService.GetPanel:input_type -> panels.panel.v1.GetPanelByIdRequest + 4, // 8: panels.panel.v1.PanelService.GetPanelByName:input_type -> panels.panel.v1.GetPanelByNameRequest + 5, // 9: panels.panel.v1.PanelService.UpdatePanel:input_type -> panels.panel.v1.UpdatePanelByIdRequest + 6, // 10: panels.panel.v1.PanelService.UpdatePanelByName:input_type -> panels.panel.v1.UpdatePanelByNameRequest + 7, // 11: panels.panel.v1.PanelService.DeletePanel:input_type -> panels.panel.v1.DeletePanelByIdRequest + 8, // 12: panels.panel.v1.PanelService.DeletePanelByName:input_type -> panels.panel.v1.DeletePanelByNameRequest + 0, // 13: panels.panel.v1.PanelService.CreatePanel:output_type -> panels.panel.v1.Panel + 0, // 14: panels.panel.v1.PanelService.GetPanel:output_type -> panels.panel.v1.Panel + 0, // 15: panels.panel.v1.PanelService.GetPanelByName:output_type -> panels.panel.v1.Panel + 0, // 16: panels.panel.v1.PanelService.UpdatePanel:output_type -> panels.panel.v1.Panel + 0, // 17: panels.panel.v1.PanelService.UpdatePanelByName:output_type -> panels.panel.v1.Panel + 11, // 18: panels.panel.v1.PanelService.DeletePanel:output_type -> google.protobuf.Empty + 11, // 19: panels.panel.v1.PanelService.DeletePanelByName:output_type -> google.protobuf.Empty + 13, // [13:20] is the sub-list for method output_type + 6, // [6:13] 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_panel_proto_init() } +func file_panel_proto_init() { + if File_panel_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_panel_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Panel); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_panel_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PanelMutable); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_panel_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreatePanelRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_panel_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetPanelByIdRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_panel_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetPanelByNameRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_panel_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdatePanelByIdRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_panel_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdatePanelByNameRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_panel_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeletePanelByIdRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_panel_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeletePanelByNameRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_panel_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PanelEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_panel_proto_msgTypes[1].OneofWrappers = []interface{}{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_panel_proto_rawDesc, + NumEnums: 0, + NumMessages: 10, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_panel_proto_goTypes, + DependencyIndexes: file_panel_proto_depIdxs, + MessageInfos: file_panel_proto_msgTypes, + }.Build() + File_panel_proto = out.File + file_panel_proto_rawDesc = nil + file_panel_proto_goTypes = nil + file_panel_proto_depIdxs = nil +} diff --git a/services/panel-service/internal/rpc/panelv1/panel_grpc.pb.go b/services/panel-service/internal/rpc/panelv1/panel_grpc.pb.go new file mode 100644 index 0000000..74a0694 --- /dev/null +++ b/services/panel-service/internal/rpc/panelv1/panel_grpc.pb.go @@ -0,0 +1,322 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v4.23.4 +// source: panel.proto + +package panelv1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// PanelServiceClient is the client API for PanelService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type PanelServiceClient interface { + CreatePanel(ctx context.Context, in *CreatePanelRequest, opts ...grpc.CallOption) (*Panel, error) + GetPanel(ctx context.Context, in *GetPanelByIdRequest, opts ...grpc.CallOption) (*Panel, error) + GetPanelByName(ctx context.Context, in *GetPanelByNameRequest, opts ...grpc.CallOption) (*Panel, error) + UpdatePanel(ctx context.Context, in *UpdatePanelByIdRequest, opts ...grpc.CallOption) (*Panel, error) + UpdatePanelByName(ctx context.Context, in *UpdatePanelByNameRequest, opts ...grpc.CallOption) (*Panel, error) + DeletePanel(ctx context.Context, in *DeletePanelByIdRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) + DeletePanelByName(ctx context.Context, in *DeletePanelByNameRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) +} + +type panelServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewPanelServiceClient(cc grpc.ClientConnInterface) PanelServiceClient { + return &panelServiceClient{cc} +} + +func (c *panelServiceClient) CreatePanel(ctx context.Context, in *CreatePanelRequest, opts ...grpc.CallOption) (*Panel, error) { + out := new(Panel) + err := c.cc.Invoke(ctx, "/panels.panel.v1.PanelService/CreatePanel", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *panelServiceClient) GetPanel(ctx context.Context, in *GetPanelByIdRequest, opts ...grpc.CallOption) (*Panel, error) { + out := new(Panel) + err := c.cc.Invoke(ctx, "/panels.panel.v1.PanelService/GetPanel", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *panelServiceClient) GetPanelByName(ctx context.Context, in *GetPanelByNameRequest, opts ...grpc.CallOption) (*Panel, error) { + out := new(Panel) + err := c.cc.Invoke(ctx, "/panels.panel.v1.PanelService/GetPanelByName", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *panelServiceClient) UpdatePanel(ctx context.Context, in *UpdatePanelByIdRequest, opts ...grpc.CallOption) (*Panel, error) { + out := new(Panel) + err := c.cc.Invoke(ctx, "/panels.panel.v1.PanelService/UpdatePanel", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *panelServiceClient) UpdatePanelByName(ctx context.Context, in *UpdatePanelByNameRequest, opts ...grpc.CallOption) (*Panel, error) { + out := new(Panel) + err := c.cc.Invoke(ctx, "/panels.panel.v1.PanelService/UpdatePanelByName", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *panelServiceClient) DeletePanel(ctx context.Context, in *DeletePanelByIdRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, "/panels.panel.v1.PanelService/DeletePanel", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *panelServiceClient) DeletePanelByName(ctx context.Context, in *DeletePanelByNameRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, "/panels.panel.v1.PanelService/DeletePanelByName", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// PanelServiceServer is the server API for PanelService service. +// All implementations must embed UnimplementedPanelServiceServer +// for forward compatibility +type PanelServiceServer interface { + CreatePanel(context.Context, *CreatePanelRequest) (*Panel, error) + GetPanel(context.Context, *GetPanelByIdRequest) (*Panel, error) + GetPanelByName(context.Context, *GetPanelByNameRequest) (*Panel, error) + UpdatePanel(context.Context, *UpdatePanelByIdRequest) (*Panel, error) + UpdatePanelByName(context.Context, *UpdatePanelByNameRequest) (*Panel, error) + DeletePanel(context.Context, *DeletePanelByIdRequest) (*emptypb.Empty, error) + DeletePanelByName(context.Context, *DeletePanelByNameRequest) (*emptypb.Empty, error) + mustEmbedUnimplementedPanelServiceServer() +} + +// UnimplementedPanelServiceServer must be embedded to have forward compatible implementations. +type UnimplementedPanelServiceServer struct { +} + +func (UnimplementedPanelServiceServer) CreatePanel(context.Context, *CreatePanelRequest) (*Panel, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreatePanel not implemented") +} +func (UnimplementedPanelServiceServer) GetPanel(context.Context, *GetPanelByIdRequest) (*Panel, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetPanel not implemented") +} +func (UnimplementedPanelServiceServer) GetPanelByName(context.Context, *GetPanelByNameRequest) (*Panel, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetPanelByName not implemented") +} +func (UnimplementedPanelServiceServer) UpdatePanel(context.Context, *UpdatePanelByIdRequest) (*Panel, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdatePanel not implemented") +} +func (UnimplementedPanelServiceServer) UpdatePanelByName(context.Context, *UpdatePanelByNameRequest) (*Panel, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdatePanelByName not implemented") +} +func (UnimplementedPanelServiceServer) DeletePanel(context.Context, *DeletePanelByIdRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeletePanel not implemented") +} +func (UnimplementedPanelServiceServer) DeletePanelByName(context.Context, *DeletePanelByNameRequest) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeletePanelByName not implemented") +} +func (UnimplementedPanelServiceServer) mustEmbedUnimplementedPanelServiceServer() {} + +// UnsafePanelServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to PanelServiceServer will +// result in compilation errors. +type UnsafePanelServiceServer interface { + mustEmbedUnimplementedPanelServiceServer() +} + +func RegisterPanelServiceServer(s grpc.ServiceRegistrar, srv PanelServiceServer) { + s.RegisterService(&PanelService_ServiceDesc, srv) +} + +func _PanelService_CreatePanel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreatePanelRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PanelServiceServer).CreatePanel(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/panels.panel.v1.PanelService/CreatePanel", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PanelServiceServer).CreatePanel(ctx, req.(*CreatePanelRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _PanelService_GetPanel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetPanelByIdRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PanelServiceServer).GetPanel(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/panels.panel.v1.PanelService/GetPanel", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PanelServiceServer).GetPanel(ctx, req.(*GetPanelByIdRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _PanelService_GetPanelByName_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetPanelByNameRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PanelServiceServer).GetPanelByName(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/panels.panel.v1.PanelService/GetPanelByName", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PanelServiceServer).GetPanelByName(ctx, req.(*GetPanelByNameRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _PanelService_UpdatePanel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdatePanelByIdRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PanelServiceServer).UpdatePanel(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/panels.panel.v1.PanelService/UpdatePanel", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PanelServiceServer).UpdatePanel(ctx, req.(*UpdatePanelByIdRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _PanelService_UpdatePanelByName_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdatePanelByNameRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PanelServiceServer).UpdatePanelByName(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/panels.panel.v1.PanelService/UpdatePanelByName", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PanelServiceServer).UpdatePanelByName(ctx, req.(*UpdatePanelByNameRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _PanelService_DeletePanel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeletePanelByIdRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PanelServiceServer).DeletePanel(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/panels.panel.v1.PanelService/DeletePanel", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PanelServiceServer).DeletePanel(ctx, req.(*DeletePanelByIdRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _PanelService_DeletePanelByName_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeletePanelByNameRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PanelServiceServer).DeletePanelByName(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/panels.panel.v1.PanelService/DeletePanelByName", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PanelServiceServer).DeletePanelByName(ctx, req.(*DeletePanelByNameRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// PanelService_ServiceDesc is the grpc.ServiceDesc for PanelService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var PanelService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "panels.panel.v1.PanelService", + HandlerType: (*PanelServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CreatePanel", + Handler: _PanelService_CreatePanel_Handler, + }, + { + MethodName: "GetPanel", + Handler: _PanelService_GetPanel_Handler, + }, + { + MethodName: "GetPanelByName", + Handler: _PanelService_GetPanelByName_Handler, + }, + { + MethodName: "UpdatePanel", + Handler: _PanelService_UpdatePanel_Handler, + }, + { + MethodName: "UpdatePanelByName", + Handler: _PanelService_UpdatePanelByName_Handler, + }, + { + MethodName: "DeletePanel", + Handler: _PanelService_DeletePanel_Handler, + }, + { + MethodName: "DeletePanelByName", + Handler: _PanelService_DeletePanelByName_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "panel.proto", +} diff --git a/services/panel-service/internal/rpc/rpc.go b/services/panel-service/internal/rpc/rpc.go new file mode 100644 index 0000000..b2c83b2 --- /dev/null +++ b/services/panel-service/internal/rpc/rpc.go @@ -0,0 +1,77 @@ +package rpc + +import ( + "net" + "context" + + "google.golang.org/grpc" + "google.golang.org/grpc/health" + "google.golang.org/grpc/health/grpc_health_v1" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging" + + "github.com/hexolan/panels/panel-service/internal" + "github.com/hexolan/panels/panel-service/internal/rpc/panelv1" +) + +type RPCServer struct { + grpcSvr *grpc.Server +} + +func NewRPCServer(panelService internal.PanelService) *RPCServer { + logger := log.Logger.With().Timestamp().Str("src", "rpc").Logger() + + svr := grpc.NewServer( + grpc.ChainUnaryInterceptor( + logging.UnaryServerInterceptor(loggingInterceptor(logger)), + ), + grpc.ChainStreamInterceptor( + logging.StreamServerInterceptor(loggingInterceptor(logger)), + ), + ) + + // Panels Service Server + panelSvr := NewPanelServer(panelService) + panelv1.RegisterPanelServiceServer(svr, &panelSvr) + + // Health Check Server + healthSvr := health.NewServer() + grpc_health_v1.RegisterHealthServer(svr, healthSvr) + + return &RPCServer{grpcSvr: svr} +} + +func loggingInterceptor(logger zerolog.Logger) logging.Logger { + return logging.LoggerFunc(func(ctx context.Context, lvl logging.Level, msg string, fields ...any) { + logger := logger.With().Fields(fields).Logger() + + switch lvl { + case logging.LevelError: + logger.Error().Msg(msg) + case logging.LevelWarn: + logger.Warn().Msg(msg) + case logging.LevelInfo: + logger.Info().Msg(msg) + case logging.LevelDebug: + logger.Debug().Msg(msg) + default: + logger.Debug().Interface("unknown-log-level", lvl).Msg(msg) + } + }) +} + +func (r *RPCServer) Serve() { + // Prepare the listening port. + lis, err := net.Listen("tcp", "0.0.0.0:9090") + if err != nil { + log.Panic().Err(err).Caller().Msg("failed to listen on RPC port (:9090)") + } + + // Begin serving gRPC. + log.Info().Str("address", lis.Addr().String()).Msg("attempting to serve RPC...") + err = r.grpcSvr.Serve(lis) + if err != nil { + log.Panic().Err(err).Caller().Msg("failed to serve RPC") + } +} \ No newline at end of file diff --git a/services/panel-service/internal/service/panel.go b/services/panel-service/internal/service/panel.go new file mode 100644 index 0000000..4d54ae0 --- /dev/null +++ b/services/panel-service/internal/service/panel.go @@ -0,0 +1,126 @@ +package service + +import ( + "context" + + "github.com/hexolan/panels/panel-service/internal" + "github.com/hexolan/panels/panel-service/internal/kafka" +) + +type panelService struct { + events kafka.PanelEventProducer + + repo internal.PanelRepository +} + +func NewPanelService(events kafka.PanelEventProducer, repo internal.PanelRepository) internal.PanelService { + return panelService{ + events: events, + repo: repo, + } +} + +func (srv panelService) GetPanelIdFromName(ctx context.Context, name string) (*int64, error) { + return srv.repo.GetPanelIdFromName(ctx, name) +} + +func (srv panelService) CreatePanel(ctx context.Context, data internal.PanelCreate) (*internal.Panel, error) { + // Validate the data + err := data.Validate() + if err != nil { + return nil, internal.NewServiceErrorf(internal.InvalidArgumentErrorCode, "invalid argument: %s", err.Error()) + } + + // Create the panel + panel, err := srv.repo.CreatePanel(ctx, data) + + // Dispatch panel created event + if err == nil { + srv.events.DispatchCreatedEvent(*panel) + } + + return panel, err +} + +func (srv panelService) GetPanel(ctx context.Context, id int64) (*internal.Panel, error) { + return srv.repo.GetPanel(ctx, id) +} + +func (srv panelService) GetPanelByName(ctx context.Context, name string) (*internal.Panel, error) { + // Get the panel ID from the provided name + id, err := srv.GetPanelIdFromName(ctx, name) + if err != nil { + return nil, err + } + + // Pass to service method for GetPanel (by id). + return srv.GetPanel(ctx, *id) +} + +func (srv panelService) UpdatePanel(ctx context.Context, id int64, data internal.PanelUpdate) (*internal.Panel, error) { + // Validate the data. + if data == (internal.PanelUpdate{}) { + return nil, internal.NewServiceError(internal.InvalidArgumentErrorCode, "no data values provided") + } + + err := data.Validate() + if err != nil { + return nil, internal.NewServiceErrorf(internal.InvalidArgumentErrorCode, "invalid argument: %s", err.Error()) + } + + // Perform some checks if the target is a primary panel + if id == 1 { + if data.Name != nil && *data.Name != "" { + return nil, internal.NewServiceError(internal.ForbiddenErrorCode, "cannot modify name of primary panel") + } + } + + // Update the panel + panel, err := srv.repo.UpdatePanel(ctx, id, data) + + // Dispatch panel updated event + if err == nil { + srv.events.DispatchUpdatedEvent(*panel) + } + + return panel, err +} + +func (srv panelService) UpdatePanelByName(ctx context.Context, name string, data internal.PanelUpdate) (*internal.Panel, error) { + // Get the panel ID from the provided name + id, err := srv.GetPanelIdFromName(ctx, name) + if err != nil { + return nil, err + } + + // Pass to service method for UpdatePanel (by id). + return srv.UpdatePanel(ctx, *id, data) +} + +func (srv panelService) DeletePanel(ctx context.Context, id int64) error { + // Ensure the target is not the primary panel + if id == 1 { + return internal.NewServiceError(internal.ForbiddenErrorCode, "cannot delete primary panel") + } + + // Delete the panel. + err := srv.repo.DeletePanel(ctx, id) + + // Dispatch panel deleted event + if err == nil { + srv.events.DispatchDeletedEvent(id) + } + + return err +} + +func (srv panelService) DeletePanelByName(ctx context.Context, name string) error { + // Get the panel ID from the provided name + id, err := srv.GetPanelIdFromName(ctx, name) + if err != nil { + return err + } + + // Pass to service method for DeletePanel (by id). + return srv.DeletePanel(ctx, *id) +} \ No newline at end of file