From ce97830d553f2f386ccbbc47f2c95d06572abb0b Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Tue, 18 Feb 2025 19:49:26 +0300 Subject: [PATCH 01/94] Linter, makefile --- .golangci.yml | 4 ++++ Makefile | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 .golangci.yml create mode 100644 Makefile diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..392ff07 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,4 @@ +linters: + enable-all: true + disable: + - exportloopref diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cafa713 --- /dev/null +++ b/Makefile @@ -0,0 +1,35 @@ +BUILD_VERSION = "v1.0.0" +BUILD_DATE = $(shell date +'%Y/%m/%d %H:%M:%S') +BUILD_COMMIT = $(shell git rev-parse --short HEAD) + +NAME=$(shell basename "$(PWD)") +DIR=$(shell pwd) + +## lint: start linter +lint: + @echo " > Start linter" + @golangci-lint run + +## fmt: start fmt +fmt: + @echo " > Start fmt" + @goimports -local "github.com/bjlag/go-keeper" -d -w $$(find . -type f -name '*.go' -not -path "*_mock.go") + +## test: start testing +test: + @echo " > Testing" + @go test -v $(DIR)/... + +## tidy: start `go mod tidy` +tidy: + @echo " > Go mod tidy" + @GOPATH=$(GOPATH) GOBIN=$(GOBIN) go mod tidy + +.PHONY: help +all: help +help: Makefile + @echo + @echo " Choose a command run in "$(NAME)":" + @echo + @sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /' + @echo \ No newline at end of file From daf2d64254f1df784931cf0e248a9376bd81379d Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Wed, 19 Feb 2025 00:10:30 +0300 Subject: [PATCH 02/94] DB and migrator --- .gitignore | 4 +- .golangci.yml | 16 ++++- Makefile | 53 ++++++++++++++- cmd/client/main.go | 1 - cmd/migrator/main.go | 56 ++++++++++++++++ cmd/server/main.go | 1 - docker/docker-compose.local.yaml | 16 +++++ go.mod | 20 ++++++ go.sum | 93 ++++++++++++++++++++++++++ internal/infrastructure/db/pg/pg.go | 40 +++++++++++ migrations/1_create_users_table.up.sql | 12 ++++ 11 files changed, 305 insertions(+), 7 deletions(-) create mode 100644 cmd/migrator/main.go create mode 100644 docker/docker-compose.local.yaml create mode 100644 go.sum create mode 100644 internal/infrastructure/db/pg/pg.go create mode 100644 migrations/1_create_users_table.up.sql diff --git a/.gitignore b/.gitignore index a9a04ed..043dc46 100644 --- a/.gitignore +++ b/.gitignore @@ -14,9 +14,11 @@ # Binaries cmd/client/main -cmd/client/agent +cmd/client/client cmd/server/main cmd/server/server +cmd/migrator/main +cmd/migrator/migrator # Dependency directories (remove the comment below to include it) vendor/ diff --git a/.golangci.yml b/.golangci.yml index 392ff07..a3dc54a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,4 +1,18 @@ linters: enable-all: true disable: - - exportloopref + - nlreturn + - varnamelen + - wsl + - perfsprint + - depguard + - forbidigo + - exhaustruct + - tenv + +linters-settings: + gci: + sections: + - standard # Standard lib + - default # External dependencies + - prefix(github.com/bjlag/go-keeper) # Internal packages \ No newline at end of file diff --git a/Makefile b/Makefile index cafa713..412d6de 100644 --- a/Makefile +++ b/Makefile @@ -2,31 +2,78 @@ BUILD_VERSION = "v1.0.0" BUILD_DATE = $(shell date +'%Y/%m/%d %H:%M:%S') BUILD_COMMIT = $(shell git rev-parse --short HEAD) -NAME=$(shell basename "$(PWD)") -DIR=$(shell pwd) +NAME = $(shell basename "$(PWD)") +DIR = $(shell pwd) +DOCKER_FILE = "docker-compose.local.yaml" + +.PHONY: all +all: help + +## up: start app +.PHONY: up +up: docker-up wait-db migrate + +## down: stop app +.PHONY: down +down: docker-down + +wait-db: + @echo " > Wait DB" + @sleep 5 + +## clean: stop app and remove volumes +.PHONY: clean +clean: docker-down-clear + +## docker-up: start docker +.PHONY: docker-up +docker-up: + @echo " > Start docker" + @docker-compose -f $(DIR)/docker/$(DOCKER_FILE) up -d + +## docker-down: stop docker +.PHONY: docker-down +docker-down: + @echo " > Stop docker" + @docker-compose -f $(DIR)/docker/$(DOCKER_FILE) down --remove-orphans + +## docker-down-clear: stop docker and remove volumes +.PHONY: docker-down-clear +docker-down-clear: + @echo " > Stop docker and remove volumes" + @docker-compose -f $(DIR)/docker/$(DOCKER_FILE) down -v --remove-orphans + +## migrate: apply migrations +.PHONY: migrate +migrate: + @echo " > Apply migrations" + @go run $(DIR)/cmd/migrator/main.go -dir="./migrations" ## lint: start linter +.PHONY: lint lint: @echo " > Start linter" @golangci-lint run ## fmt: start fmt +.PHONY: fmt fmt: @echo " > Start fmt" @goimports -local "github.com/bjlag/go-keeper" -d -w $$(find . -type f -name '*.go' -not -path "*_mock.go") ## test: start testing +.PHONY: test test: @echo " > Testing" @go test -v $(DIR)/... ## tidy: start `go mod tidy` +.PHONY: tidy tidy: @echo " > Go mod tidy" @GOPATH=$(GOPATH) GOBIN=$(GOBIN) go mod tidy .PHONY: help -all: help help: Makefile @echo @echo " Choose a command run in "$(NAME)":" diff --git a/cmd/client/main.go b/cmd/client/main.go index 7905807..da29a2c 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -1,5 +1,4 @@ package main func main() { - } diff --git a/cmd/migrator/main.go b/cmd/migrator/main.go new file mode 100644 index 0000000..0fa5e99 --- /dev/null +++ b/cmd/migrator/main.go @@ -0,0 +1,56 @@ +package main + +import ( + "errors" + "flag" + "fmt" + "log" + + "github.com/golang-migrate/migrate/v4" + "github.com/golang-migrate/migrate/v4/database/pgx/v5" + _ "github.com/golang-migrate/migrate/v4/source/file" + + "github.com/bjlag/go-keeper/internal/infrastructure/db/pg" +) + +const ( + sourcePathDefault = "./migrations" +) + +func main() { + var sourcePath string + + flag.StringVar(&sourcePath, "dir", sourcePathDefault, "Migrations directory") + flag.Parse() + + fmt.Println("Migrations directory: ", sourcePath) + + dsn := "postgresql://postgres:secret@localhost:5444/master?sslmode=disable" + db := pg.New(dsn).Connect() + + driver, err := pgx.WithInstance(db.DB, &pgx.Config{ + MigrationsTable: "migrations", + }) + if err != nil { + log.Fatal(err) + } + + m, err := migrate.NewWithDatabaseInstance( + fmt.Sprintf("file://%s", sourcePath), + "master", + driver, + ) + if err != nil { + log.Panic(err) + } + + if err = m.Up(); err != nil { + if errors.Is(err, migrate.ErrNoChange) { + fmt.Println("No changes") + return + } + log.Panic(err) + } + + fmt.Println("Migrations applied") +} diff --git a/cmd/server/main.go b/cmd/server/main.go index 7905807..da29a2c 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -1,5 +1,4 @@ package main func main() { - } diff --git a/docker/docker-compose.local.yaml b/docker/docker-compose.local.yaml new file mode 100644 index 0000000..1060762 --- /dev/null +++ b/docker/docker-compose.local.yaml @@ -0,0 +1,16 @@ +version: "3.8" +services: + pg: + image: postgres:16.4-alpine3.20 + container_name: go-keeper-server-db + restart: always + environment: + - POSTGRES_DB=master + - POSTGRES_PASSWORD=secret + ports: + - "5444:5432" + volumes: + - pg:/var/lib/postgresql/data + +volumes: + pg: diff --git a/go.mod b/go.mod index c8a2e76..75cefe6 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,23 @@ module github.com/bjlag/go-keeper go 1.23 + +require ( + github.com/golang-migrate/migrate/v4 v4.18.2 + github.com/jmoiron/sqlx v1.4.0 + github.com/lib/pq v1.10.9 +) + +require ( + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.7.2 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + go.uber.org/atomic v1.11.0 // indirect + golang.org/x/crypto v0.33.0 // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/text v0.22.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..909c2c7 --- /dev/null +++ b/go.sum @@ -0,0 +1,93 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +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/dhui/dktest v0.4.4 h1:+I4s6JRE1yGuqflzwqG+aIaMdgXIorCf5P98JnaAWa8= +github.com/dhui/dktest v0.4.4/go.mod h1:4+22R4lgsdAXrDyaH4Nqx2JEz2hLp49MqQmm9HLCQhM= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4= +github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-migrate/migrate/v4 v4.18.2 h1:2VSCMz7x7mjyTXx3m2zPokOY82LTRgxK1yQYKo6wWQ8= +github.com/golang-migrate/migrate/v4 v4.18.2/go.mod h1:2CM6tJvn2kqPXwnXO/d3rAQYiyoIm180VsO8PRX6Rpk= +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-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0= +github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/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-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI= +github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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/internal/infrastructure/db/pg/pg.go b/internal/infrastructure/db/pg/pg.go new file mode 100644 index 0000000..aa2439f --- /dev/null +++ b/internal/infrastructure/db/pg/pg.go @@ -0,0 +1,40 @@ +package pg + +import ( + "log" + "time" + + "github.com/jmoiron/sqlx" + _ "github.com/lib/pq" +) + +const ( + maxOpenConnects = 5 + maxIdleConnects = 5 + connMaxLifetime = 5 * time.Minute + connMaxIdleTime = 5 * time.Minute +) + +type PG struct { + dsn string +} + +func New(dsn string) *PG { + return &PG{ + dsn: dsn, + } +} + +func (p *PG) Connect() *sqlx.DB { + db, err := sqlx.Connect("pgx", p.dsn) + if err != nil { + log.Panic(err) + } + + db.SetMaxOpenConns(maxOpenConnects) + db.SetMaxIdleConns(maxIdleConnects) + db.SetConnMaxLifetime(connMaxLifetime) + db.SetConnMaxIdleTime(connMaxIdleTime) + + return db +} diff --git a/migrations/1_create_users_table.up.sql b/migrations/1_create_users_table.up.sql new file mode 100644 index 0000000..94defee --- /dev/null +++ b/migrations/1_create_users_table.up.sql @@ -0,0 +1,12 @@ +CREATE TABLE IF NOT EXISTS users ( + guid uuid PRIMARY KEY NOT NULL, + email varchar(20) NOT NULL, + password_hash varchar(60) NOT NULL +); + +CREATE UNIQUE INDEX users_email_uniq_idx ON users (email); + +COMMENT ON TABLE users IS 'Пользователи'; +COMMENT ON COLUMN users.guid IS 'GUID'; +COMMENT ON COLUMN users.email IS 'Email'; +COMMENT ON COLUMN users.password_hash IS 'Хеш пароля'; \ No newline at end of file From 18806d798e70a530c97095c0a72a033b9fa59e72 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Wed, 19 Feb 2025 19:32:16 +0300 Subject: [PATCH 03/94] Migrator config --- .gitignore | 4 ++-- .golangci.yml | 3 +++ Makefile | 2 +- cmd/migrator/config.go | 15 +++++++++++++ cmd/migrator/main.go | 35 ++++++++++++++++++++++------- config/migrator.yaml.dist | 10 +++++++++ go.mod | 5 +++++ go.sum | 17 ++++++++++++++ internal/infrastructure/db/pg/pg.go | 2 +- 9 files changed, 81 insertions(+), 12 deletions(-) create mode 100644 cmd/migrator/config.go create mode 100644 config/migrator.yaml.dist diff --git a/.gitignore b/.gitignore index 043dc46..1f0d777 100644 --- a/.gitignore +++ b/.gitignore @@ -27,5 +27,5 @@ vendor/ .idea .vscode -# Data -data/ +# Configs +config/migrator.yaml \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml index a3dc54a..abce2e8 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -9,6 +9,9 @@ linters: - forbidigo - exhaustruct - tenv + - tagalign + - nosprintfhostport + - lll linters-settings: gci: diff --git a/Makefile b/Makefile index 412d6de..c7586d1 100644 --- a/Makefile +++ b/Makefile @@ -47,7 +47,7 @@ docker-down-clear: .PHONY: migrate migrate: @echo " > Apply migrations" - @go run $(DIR)/cmd/migrator/main.go -dir="./migrations" + @go run $(DIR)/cmd/migrator -c="./config/migrator.yaml" ## lint: start linter .PHONY: lint diff --git a/cmd/migrator/config.go b/cmd/migrator/config.go new file mode 100644 index 0000000..62ae862 --- /dev/null +++ b/cmd/migrator/config.go @@ -0,0 +1,15 @@ +package main + +type Config struct { + Env string `yaml:"env" env:"ENV" env-default:"dev" env-description:"Environment"` + SourcePath string `yaml:"sourcePath" env:"MIGRATIONS_SOURCE_PATH" env-default:"./migrations" env-description:"Migrations source path"` + MigrationsTable string `yaml:"migrationsTable" env:"MIGRATIONS_TABLE" env-default:"migrations" env-description:"Migrations table name"` + + Database struct { + Host string `yaml:"host" env:"DB_HOST" env-description:"Database host"` + Port string `yaml:"port" env:"DB_PORT" env-description:"Database port"` + Name string `yaml:"name" env:"DB_NAME" env-description:"Database name"` + User string `yaml:"user" env:"DB_USER" env-description:"Database user"` + Password string `yaml:"password" env:"DB_PASSWORD" env-description:"Database password"` + } `yaml:"database"` +} diff --git a/cmd/migrator/main.go b/cmd/migrator/main.go index 0fa5e99..ca26192 100644 --- a/cmd/migrator/main.go +++ b/cmd/migrator/main.go @@ -9,35 +9,54 @@ import ( "github.com/golang-migrate/migrate/v4" "github.com/golang-migrate/migrate/v4/database/pgx/v5" _ "github.com/golang-migrate/migrate/v4/source/file" + "github.com/ilyakaznacheev/cleanenv" "github.com/bjlag/go-keeper/internal/infrastructure/db/pg" ) const ( - sourcePathDefault = "./migrations" + configPathDefault = "./config/migrator.yaml" ) func main() { - var sourcePath string + var configPath string - flag.StringVar(&sourcePath, "dir", sourcePathDefault, "Migrations directory") + flag.StringVar(&configPath, "c", configPathDefault, "Configuration directory") flag.Parse() - fmt.Println("Migrations directory: ", sourcePath) + var cfg Config + if err := cleanenv.ReadConfig(configPath, &cfg); err != nil { + log.Fatal(err) + } - dsn := "postgresql://postgres:secret@localhost:5444/master?sslmode=disable" + fmt.Println("Environment: ", cfg.Env) + fmt.Println("Migrations directory: ", cfg.SourcePath) + fmt.Println("Migrations table name: ", cfg.MigrationsTable) + fmt.Println("Database host: ", cfg.Database.Host) + fmt.Println("Database port: ", cfg.Database.Port) + fmt.Println("Database name: ", cfg.Database.Name) + fmt.Println("Database user: ", cfg.Database.User) + + dsn := fmt.Sprintf( + "postgresql://%s:%s@%s:%s/%s?sslmode=disable", + cfg.Database.User, + cfg.Database.Password, + cfg.Database.Host, + cfg.Database.Port, + cfg.Database.Name, + ) db := pg.New(dsn).Connect() driver, err := pgx.WithInstance(db.DB, &pgx.Config{ - MigrationsTable: "migrations", + MigrationsTable: cfg.MigrationsTable, }) if err != nil { log.Fatal(err) } m, err := migrate.NewWithDatabaseInstance( - fmt.Sprintf("file://%s", sourcePath), - "master", + fmt.Sprintf("file://%s", cfg.SourcePath), + cfg.Database.Name, driver, ) if err != nil { diff --git a/config/migrator.yaml.dist b/config/migrator.yaml.dist new file mode 100644 index 0000000..9862e0a --- /dev/null +++ b/config/migrator.yaml.dist @@ -0,0 +1,10 @@ +env: dev +source_path: "./migrations" +migrations_table: "migrations" + +database: + host: localhost + port: 5444 + name: master + user: postgres + password: secret \ No newline at end of file diff --git a/go.mod b/go.mod index 75cefe6..a13a685 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,13 @@ go 1.23 require ( github.com/golang-migrate/migrate/v4 v4.18.2 + github.com/ilyakaznacheev/cleanenv v1.5.0 github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.10.9 ) require ( + github.com/BurntSushi/toml v1.4.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 // indirect @@ -16,8 +18,11 @@ require ( github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgx/v5 v5.7.2 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/joho/godotenv v1.5.1 // indirect go.uber.org/atomic v1.11.0 // indirect golang.org/x/crypto v0.33.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/text v0.22.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect ) diff --git a/go.sum b/go.sum index 909c2c7..51a96c0 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,9 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -34,6 +37,8 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY 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/ilyakaznacheev/cleanenv v1.5.0 h1:0VNZXggJE2OYdXE87bfSSwGxeiGt9moSR2lOrsHHvr4= +github.com/ilyakaznacheev/cleanenv v1.5.0/go.mod h1:a5aDzaJrLCQZsazHol1w8InnDcOX0OColm64SlIi6gk= github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0= github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= @@ -46,6 +51,12 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= @@ -64,6 +75,8 @@ 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/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -88,6 +101,10 @@ golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 h1:slmdOY3vp8a7KQbHkL+FLbvbkgMqmXojpFUO/jENuqQ= +olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw= diff --git a/internal/infrastructure/db/pg/pg.go b/internal/infrastructure/db/pg/pg.go index aa2439f..c3bd64b 100644 --- a/internal/infrastructure/db/pg/pg.go +++ b/internal/infrastructure/db/pg/pg.go @@ -5,7 +5,7 @@ import ( "time" "github.com/jmoiron/sqlx" - _ "github.com/lib/pq" + _ "github.com/lib/pq" // postgres driver ) const ( From db828540ba27f920d50bb96d16cbf747ddca4d82 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Thu, 20 Feb 2025 11:14:53 +0300 Subject: [PATCH 04/94] Logger --- .golangci.yml | 4 ++ cmd/migrator/config.go | 18 +++--- cmd/migrator/main.go | 79 ++++++++++++++--------- go.mod | 5 ++ go.sum | 6 ++ internal/infrastructure/db/pg/dsn.go | 14 ++++ internal/infrastructure/db/pg/dsn_test.go | 15 +++++ internal/infrastructure/db/pg/pg.go | 10 +-- internal/infrastructure/db/pg/pg_test.go | 15 +++++ internal/infrastructure/logger/logger.go | 38 +++++++++++ migrations/1_create_users_table.up.sql | 11 ++-- 11 files changed, 168 insertions(+), 47 deletions(-) create mode 100644 internal/infrastructure/db/pg/dsn.go create mode 100644 internal/infrastructure/db/pg/dsn_test.go create mode 100644 internal/infrastructure/db/pg/pg_test.go create mode 100644 internal/infrastructure/logger/logger.go diff --git a/.golangci.yml b/.golangci.yml index abce2e8..9cb2ac3 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -12,6 +12,10 @@ linters: - tagalign - nosprintfhostport - lll + - loggercheck + - paralleltest + - tagliatelle + - gochecknoglobals linters-settings: gci: diff --git a/cmd/migrator/config.go b/cmd/migrator/config.go index 62ae862..638453d 100644 --- a/cmd/migrator/config.go +++ b/cmd/migrator/config.go @@ -1,15 +1,15 @@ package main type Config struct { - Env string `yaml:"env" env:"ENV" env-default:"dev" env-description:"Environment"` - SourcePath string `yaml:"sourcePath" env:"MIGRATIONS_SOURCE_PATH" env-default:"./migrations" env-description:"Migrations source path"` - MigrationsTable string `yaml:"migrationsTable" env:"MIGRATIONS_TABLE" env-default:"migrations" env-description:"Migrations table name"` + Env string `yaml:"env" env:"ENV" env-default:"dev" env-description:"Environment" json:"env"` + SourcePath string `yaml:"sourcePath" env:"MIGRATIONS_SOURCE_PATH" env-default:"./migrations" env-description:"Migrations source path" json:"source_path"` + MigrationsTable string `yaml:"migrationsTable" env:"MIGRATIONS_TABLE" env-default:"migrations" env-description:"Migrations table name" json:"migrations_table"` Database struct { - Host string `yaml:"host" env:"DB_HOST" env-description:"Database host"` - Port string `yaml:"port" env:"DB_PORT" env-description:"Database port"` - Name string `yaml:"name" env:"DB_NAME" env-description:"Database name"` - User string `yaml:"user" env:"DB_USER" env-description:"Database user"` - Password string `yaml:"password" env:"DB_PASSWORD" env-description:"Database password"` - } `yaml:"database"` + Host string `yaml:"host" env:"DB_HOST" env-description:"Database host" json:"host"` + Port string `yaml:"port" env:"DB_PORT" env-description:"Database port" json:"port"` + Name string `yaml:"name" env:"DB_NAME" env-description:"Database name" json:"name"` + User string `yaml:"user" env:"DB_USER" env-description:"Database user" json:"user"` + Password string `yaml:"password" env:"DB_PASSWORD" env-description:"Database password" json:"password"` + } `yaml:"database" json:"database"` } diff --git a/cmd/migrator/main.go b/cmd/migrator/main.go index ca26192..0c55244 100644 --- a/cmd/migrator/main.go +++ b/cmd/migrator/main.go @@ -4,14 +4,17 @@ import ( "errors" "flag" "fmt" - "log" + logNative "log" "github.com/golang-migrate/migrate/v4" "github.com/golang-migrate/migrate/v4/database/pgx/v5" _ "github.com/golang-migrate/migrate/v4/source/file" "github.com/ilyakaznacheev/cleanenv" + "github.com/jmoiron/sqlx" + "go.uber.org/zap" "github.com/bjlag/go-keeper/internal/infrastructure/db/pg" + "github.com/bjlag/go-keeper/internal/infrastructure/logger" ) const ( @@ -19,6 +22,12 @@ const ( ) func main() { + defer func() { + if r := recover(); r != nil { + logNative.Fatalf("panic occurred: %v", r) + } + }() + var configPath string flag.StringVar(&configPath, "c", configPathDefault, "Configuration directory") @@ -26,32 +35,50 @@ func main() { var cfg Config if err := cleanenv.ReadConfig(configPath, &cfg); err != nil { - log.Fatal(err) + panic(err) } - fmt.Println("Environment: ", cfg.Env) - fmt.Println("Migrations directory: ", cfg.SourcePath) - fmt.Println("Migrations table name: ", cfg.MigrationsTable) - fmt.Println("Database host: ", cfg.Database.Host) - fmt.Println("Database port: ", cfg.Database.Port) - fmt.Println("Database name: ", cfg.Database.Name) - fmt.Println("Database user: ", cfg.Database.User) - - dsn := fmt.Sprintf( - "postgresql://%s:%s@%s:%s/%s?sslmode=disable", - cfg.Database.User, - cfg.Database.Password, - cfg.Database.Host, - cfg.Database.Port, - cfg.Database.Name, - ) - db := pg.New(dsn).Connect() + log := logger.Get(cfg.Env) + defer func() { + _ = log.Sync() + }() + + log.Debug("Config loaded", zap.Any("config", cfg)) + + dbConf := cfg.Database + db, err := pg.New(pg.GetDSN(dbConf.Host, dbConf.Port, dbConf.Name, dbConf.User, dbConf.Password)).Connect() + if err != nil { + panic(err) + } + + m, err := initMigrator(db, cfg) + if err != nil { + log.Error("Failed to initialize migrator", zap.Error(err)) + panic(err) + } + + log.Info("Applying migrations") + + if err = m.Up(); err != nil { + if errors.Is(err, migrate.ErrNoChange) { + log.Info("No changes") + return + } + log.Error("Migration failed", zap.Error(err)) + return + } + + log.Info("Migrations applied") +} + +func initMigrator(db *sqlx.DB, cfg Config) (*migrate.Migrate, error) { + const op = "initMigrator" driver, err := pgx.WithInstance(db.DB, &pgx.Config{ MigrationsTable: cfg.MigrationsTable, }) if err != nil { - log.Fatal(err) + return nil, fmt.Errorf("%s get driver: %w", op, err) } m, err := migrate.NewWithDatabaseInstance( @@ -60,16 +87,8 @@ func main() { driver, ) if err != nil { - log.Panic(err) - } - - if err = m.Up(); err != nil { - if errors.Is(err, migrate.ErrNoChange) { - fmt.Println("No changes") - return - } - log.Panic(err) + return nil, fmt.Errorf("%s create migrator instance: %w", op, err) } - fmt.Println("Migrations applied") + return m, nil } diff --git a/go.mod b/go.mod index a13a685..82f5a60 100644 --- a/go.mod +++ b/go.mod @@ -7,10 +7,13 @@ require ( github.com/ilyakaznacheev/cleanenv v1.5.0 github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.10.9 + github.com/stretchr/testify v1.9.0 + go.uber.org/zap v1.27.0 ) require ( github.com/BurntSushi/toml v1.4.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 // indirect @@ -19,7 +22,9 @@ require ( github.com/jackc/pgx/v5 v5.7.2 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/joho/godotenv v1.5.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.33.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/text v0.22.0 // indirect diff --git a/go.sum b/go.sum index 51a96c0..86135b8 100644 --- a/go.sum +++ b/go.sum @@ -92,6 +92,12 @@ go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt3 go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= diff --git a/internal/infrastructure/db/pg/dsn.go b/internal/infrastructure/db/pg/dsn.go new file mode 100644 index 0000000..839bad9 --- /dev/null +++ b/internal/infrastructure/db/pg/dsn.go @@ -0,0 +1,14 @@ +package pg + +import "fmt" + +func GetDSN(host, port, name, user, pass string) string { + return fmt.Sprintf( + "postgresql://%s:%s@%s:%s/%s?sslmode=disable", + user, + pass, + host, + port, + name, + ) +} diff --git a/internal/infrastructure/db/pg/dsn_test.go b/internal/infrastructure/db/pg/dsn_test.go new file mode 100644 index 0000000..0b74e94 --- /dev/null +++ b/internal/infrastructure/db/pg/dsn_test.go @@ -0,0 +1,15 @@ +package pg_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/bjlag/go-keeper/internal/infrastructure/db/pg" +) + +func TestGetDSN(t *testing.T) { + got := pg.GetDSN("host", "1111", "database_name", "user", "pass") + + assert.Equal(t, "postgresql://user:pass@host:1111/database_name?sslmode=disable", got) +} diff --git a/internal/infrastructure/db/pg/pg.go b/internal/infrastructure/db/pg/pg.go index c3bd64b..3164536 100644 --- a/internal/infrastructure/db/pg/pg.go +++ b/internal/infrastructure/db/pg/pg.go @@ -1,7 +1,7 @@ package pg import ( - "log" + "fmt" "time" "github.com/jmoiron/sqlx" @@ -25,10 +25,12 @@ func New(dsn string) *PG { } } -func (p *PG) Connect() *sqlx.DB { +func (p *PG) Connect() (*sqlx.DB, error) { + const op = "pg.Connect" + db, err := sqlx.Connect("pgx", p.dsn) if err != nil { - log.Panic(err) + return nil, fmt.Errorf("%s: %w", op, err) } db.SetMaxOpenConns(maxOpenConnects) @@ -36,5 +38,5 @@ func (p *PG) Connect() *sqlx.DB { db.SetConnMaxLifetime(connMaxLifetime) db.SetConnMaxIdleTime(connMaxIdleTime) - return db + return db, nil } diff --git a/internal/infrastructure/db/pg/pg_test.go b/internal/infrastructure/db/pg/pg_test.go new file mode 100644 index 0000000..2e2eed6 --- /dev/null +++ b/internal/infrastructure/db/pg/pg_test.go @@ -0,0 +1,15 @@ +package pg_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/bjlag/go-keeper/internal/infrastructure/db/pg" +) + +func TestPG_Connect(t *testing.T) { + got, err := pg.New("bad_dsn").Connect() + assert.Error(t, err) //nolint:testifylint + assert.Nil(t, got) +} diff --git a/internal/infrastructure/logger/logger.go b/internal/infrastructure/logger/logger.go new file mode 100644 index 0000000..e2f1424 --- /dev/null +++ b/internal/infrastructure/logger/logger.go @@ -0,0 +1,38 @@ +package logger + +import ( + "os" + "runtime" + "sync" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +var ( + once sync.Once + logger *zap.Logger +) + +func Get(env string) *zap.Logger { + once.Do(func() { + var config zap.Config + + if env == "prod" { + config = zap.NewProductionConfig() + } else { + config = zap.NewDevelopmentConfig() + } + + config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder + config.InitialFields = map[string]interface{}{ + "env": env, + "pid": os.Getpid(), + "go_version": runtime.Version(), + } + + logger = zap.Must(config.Build()) + }) + + return logger +} diff --git a/migrations/1_create_users_table.up.sql b/migrations/1_create_users_table.up.sql index 94defee..93bce7b 100644 --- a/migrations/1_create_users_table.up.sql +++ b/migrations/1_create_users_table.up.sql @@ -1,7 +1,9 @@ CREATE TABLE IF NOT EXISTS users ( - guid uuid PRIMARY KEY NOT NULL, - email varchar(20) NOT NULL, - password_hash varchar(60) NOT NULL + guid uuid PRIMARY KEY NOT NULL, + email varchar(20) NOT NULL, + password_hash varchar(60) NOT NULL, + created_at timestamptz NOT NULL DEFAULT NOW(), + updated_at timestamptz NOT NULL DEFAULT NOW() ); CREATE UNIQUE INDEX users_email_uniq_idx ON users (email); @@ -9,4 +11,5 @@ CREATE UNIQUE INDEX users_email_uniq_idx ON users (email); COMMENT ON TABLE users IS 'Пользователи'; COMMENT ON COLUMN users.guid IS 'GUID'; COMMENT ON COLUMN users.email IS 'Email'; -COMMENT ON COLUMN users.password_hash IS 'Хеш пароля'; \ No newline at end of file +COMMENT ON COLUMN users.created_at IS 'Дата создания пользователя'; +COMMENT ON COLUMN users.updated_at IS 'Дата изменения пользователя'; \ No newline at end of file From e8f3a78eb0c35cd4aba8226073ec91a85a3f1f9d Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Thu, 20 Feb 2025 18:57:46 +0300 Subject: [PATCH 05/94] auth: proto --- Makefile | 6 + go.mod | 5 + go.sum | 10 ++ internal/generated/rpc/keeper.pb.go | 200 +++++++++++++++++++++++ internal/generated/rpc/keeper_grpc.pb.go | 119 ++++++++++++++ proto/keeper.proto | 19 +++ 6 files changed, 359 insertions(+) create mode 100644 internal/generated/rpc/keeper.pb.go create mode 100644 internal/generated/rpc/keeper_grpc.pb.go create mode 100644 proto/keeper.proto diff --git a/Makefile b/Makefile index c7586d1..aaa7443 100644 --- a/Makefile +++ b/Makefile @@ -73,6 +73,12 @@ tidy: @echo " > Go mod tidy" @GOPATH=$(GOPATH) GOBIN=$(GOBIN) go mod tidy +## proto: generate grpc client/server from proto files +.PHONY: proto +proto: + @echo " > Generate gRPC" + @protoc --go_out=. --go_opt=paths=import --go-grpc_out=. --go-grpc_opt=paths=import --go-grpc_opt=require_unimplemented_servers=false proto/* + .PHONY: help help: Makefile @echo diff --git a/go.mod b/go.mod index 82f5a60..f6a0e33 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,8 @@ require ( github.com/lib/pq v1.10.9 github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 + google.golang.org/grpc v1.64.1 + google.golang.org/protobuf v1.34.2 ) require ( @@ -26,8 +28,11 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.33.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/sync v0.11.0 // indirect + golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect ) diff --git a/go.sum b/go.sum index 86135b8..216527b 100644 --- a/go.sum +++ b/go.sum @@ -32,6 +32,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-migrate/migrate/v4 v4.18.2 h1:2VSCMz7x7mjyTXx3m2zPokOY82LTRgxK1yQYKo6wWQ8= github.com/golang-migrate/migrate/v4 v4.18.2/go.mod h1:2CM6tJvn2kqPXwnXO/d3rAQYiyoIm180VsO8PRX6Rpk= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/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= @@ -100,12 +102,20 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 h1:mxSlqyb8ZAHsYDCfiXN1EDdNTdvjUJSLY+OnAUtYNYA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/internal/generated/rpc/keeper.pb.go b/internal/generated/rpc/keeper.pb.go new file mode 100644 index 0000000..4903adf --- /dev/null +++ b/internal/generated/rpc/keeper.pb.go @@ -0,0 +1,200 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.4 +// protoc v5.29.3 +// source: proto/keeper.proto + +package rpc + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type RegisterIn struct { + state protoimpl.MessageState `protogen:"open.v1"` + Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` + Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RegisterIn) Reset() { + *x = RegisterIn{} + mi := &file_proto_keeper_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RegisterIn) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterIn) ProtoMessage() {} + +func (x *RegisterIn) ProtoReflect() protoreflect.Message { + mi := &file_proto_keeper_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegisterIn.ProtoReflect.Descriptor instead. +func (*RegisterIn) Descriptor() ([]byte, []int) { + return file_proto_keeper_proto_rawDescGZIP(), []int{0} +} + +func (x *RegisterIn) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *RegisterIn) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +type RegisterOut struct { + state protoimpl.MessageState `protogen:"open.v1"` + AccessToken string `protobuf:"bytes,1,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` + RefreshToken string `protobuf:"bytes,2,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RegisterOut) Reset() { + *x = RegisterOut{} + mi := &file_proto_keeper_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RegisterOut) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterOut) ProtoMessage() {} + +func (x *RegisterOut) ProtoReflect() protoreflect.Message { + mi := &file_proto_keeper_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegisterOut.ProtoReflect.Descriptor instead. +func (*RegisterOut) Descriptor() ([]byte, []int) { + return file_proto_keeper_proto_rawDescGZIP(), []int{1} +} + +func (x *RegisterOut) GetAccessToken() string { + if x != nil { + return x.AccessToken + } + return "" +} + +func (x *RegisterOut) GetRefreshToken() string { + if x != nil { + return x.RefreshToken + } + return "" +} + +var File_proto_keeper_proto protoreflect.FileDescriptor + +var file_proto_keeper_proto_rawDesc = string([]byte{ + 0x0a, 0x12, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x22, 0x3e, 0x0a, 0x0a, + 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, + 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, + 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x55, 0x0a, 0x0b, + 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x61, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, + 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x32, 0x3d, 0x0a, 0x06, 0x4b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x12, 0x33, 0x0a, + 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x12, 0x2e, 0x6b, 0x65, 0x65, 0x70, + 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x1a, 0x13, 0x2e, + 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, + 0x75, 0x74, 0x42, 0x18, 0x5a, 0x16, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, + 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, +}) + +var ( + file_proto_keeper_proto_rawDescOnce sync.Once + file_proto_keeper_proto_rawDescData []byte +) + +func file_proto_keeper_proto_rawDescGZIP() []byte { + file_proto_keeper_proto_rawDescOnce.Do(func() { + file_proto_keeper_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_proto_keeper_proto_rawDesc), len(file_proto_keeper_proto_rawDesc))) + }) + return file_proto_keeper_proto_rawDescData +} + +var file_proto_keeper_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_proto_keeper_proto_goTypes = []any{ + (*RegisterIn)(nil), // 0: keeper.RegisterIn + (*RegisterOut)(nil), // 1: keeper.RegisterOut +} +var file_proto_keeper_proto_depIdxs = []int32{ + 0, // 0: keeper.Keeper.Register:input_type -> keeper.RegisterIn + 1, // 1: keeper.Keeper.Register:output_type -> keeper.RegisterOut + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_proto_keeper_proto_init() } +func file_proto_keeper_proto_init() { + if File_proto_keeper_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_keeper_proto_rawDesc), len(file_proto_keeper_proto_rawDesc)), + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_proto_keeper_proto_goTypes, + DependencyIndexes: file_proto_keeper_proto_depIdxs, + MessageInfos: file_proto_keeper_proto_msgTypes, + }.Build() + File_proto_keeper_proto = out.File + file_proto_keeper_proto_goTypes = nil + file_proto_keeper_proto_depIdxs = nil +} diff --git a/internal/generated/rpc/keeper_grpc.pb.go b/internal/generated/rpc/keeper_grpc.pb.go new file mode 100644 index 0000000..9c89436 --- /dev/null +++ b/internal/generated/rpc/keeper_grpc.pb.go @@ -0,0 +1,119 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v5.29.3 +// source: proto/keeper.proto + +package rpc + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// 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.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + Keeper_Register_FullMethodName = "/keeper.Keeper/Register" +) + +// KeeperClient is the client API for Keeper 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 KeeperClient interface { + Register(ctx context.Context, in *RegisterIn, opts ...grpc.CallOption) (*RegisterOut, error) +} + +type keeperClient struct { + cc grpc.ClientConnInterface +} + +func NewKeeperClient(cc grpc.ClientConnInterface) KeeperClient { + return &keeperClient{cc} +} + +func (c *keeperClient) Register(ctx context.Context, in *RegisterIn, opts ...grpc.CallOption) (*RegisterOut, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RegisterOut) + err := c.cc.Invoke(ctx, Keeper_Register_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// KeeperServer is the server API for Keeper service. +// All implementations should embed UnimplementedKeeperServer +// for forward compatibility. +type KeeperServer interface { + Register(context.Context, *RegisterIn) (*RegisterOut, error) +} + +// UnimplementedKeeperServer should be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedKeeperServer struct{} + +func (UnimplementedKeeperServer) Register(context.Context, *RegisterIn) (*RegisterOut, error) { + return nil, status.Errorf(codes.Unimplemented, "method Register not implemented") +} +func (UnimplementedKeeperServer) testEmbeddedByValue() {} + +// UnsafeKeeperServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to KeeperServer will +// result in compilation errors. +type UnsafeKeeperServer interface { + mustEmbedUnimplementedKeeperServer() +} + +func RegisterKeeperServer(s grpc.ServiceRegistrar, srv KeeperServer) { + // If the following call pancis, it indicates UnimplementedKeeperServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&Keeper_ServiceDesc, srv) +} + +func _Keeper_Register_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RegisterIn) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(KeeperServer).Register(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Keeper_Register_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(KeeperServer).Register(ctx, req.(*RegisterIn)) + } + return interceptor(ctx, in, info, handler) +} + +// Keeper_ServiceDesc is the grpc.ServiceDesc for Keeper service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Keeper_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "keeper.Keeper", + HandlerType: (*KeeperServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Register", + Handler: _Keeper_Register_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "proto/keeper.proto", +} diff --git a/proto/keeper.proto b/proto/keeper.proto new file mode 100644 index 0000000..192ee49 --- /dev/null +++ b/proto/keeper.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; + +package keeper; + +option go_package = "internal/generated/rpc"; + +service Keeper { + rpc Register(RegisterIn) returns (RegisterOut); +} + +message RegisterIn { + string email = 1; + string password = 2; +} + +message RegisterOut { + string access_token = 1; + string refresh_token = 2; +} From 3093b2e11511cb78c41ccdf8d82d522a31984025 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Thu, 20 Feb 2025 23:47:56 +0300 Subject: [PATCH 06/94] auth: grpc server --- cmd/server/main.go | 28 ++++++++ internal/app/app.go | 40 +++++++++++ .../infrastructure/rpc/interceptor/logger.go | 25 +++++++ internal/infrastructure/rpc/server/method.go | 28 ++++++++ internal/infrastructure/rpc/server/option.go | 19 +++++ internal/infrastructure/rpc/server/server.go | 70 +++++++++++++++++++ internal/rpc/register/handler.go | 27 +++++++ 7 files changed, 237 insertions(+) create mode 100644 internal/app/app.go create mode 100644 internal/infrastructure/rpc/interceptor/logger.go create mode 100644 internal/infrastructure/rpc/server/method.go create mode 100644 internal/infrastructure/rpc/server/option.go create mode 100644 internal/infrastructure/rpc/server/server.go create mode 100644 internal/rpc/register/handler.go diff --git a/cmd/server/main.go b/cmd/server/main.go index da29a2c..708a7a6 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -1,4 +1,32 @@ package main +import ( + "context" + logNative "log" + "os/signal" + "syscall" + + "github.com/bjlag/go-keeper/internal/app" + "github.com/bjlag/go-keeper/internal/infrastructure/logger" +) + func main() { + defer func() { + if r := recover(); r != nil { + logNative.Fatalf("panic occurred: %v", r) + } + }() + + ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) + defer cancel() + + log := logger.Get("dev") + defer func() { + _ = log.Sync() + }() + + err := app.NewApp(log).Run(ctx) + if err != nil { + panic(err) + } } diff --git a/internal/app/app.go b/internal/app/app.go new file mode 100644 index 0000000..2b21050 --- /dev/null +++ b/internal/app/app.go @@ -0,0 +1,40 @@ +package app + +import ( + "context" + "fmt" + + "go.uber.org/zap" + + "github.com/bjlag/go-keeper/internal/infrastructure/rpc/server" + "github.com/bjlag/go-keeper/internal/rpc/register" +) + +type App struct { + log *zap.Logger +} + +func NewApp(log *zap.Logger) *App { + return &App{ + log: log, + } +} + +func (a *App) Run(ctx context.Context) error { + const op = "app.Run" + + server := server.NewServer( + server.WithLogger(a.log), + server.WithHandler(server.RegisterMethod, register.New().Handle), + ) + + err := server.Start(ctx) + if err != nil { + a.log.Error("Failed to start gRPC server", zap.Error(err)) + return fmt.Errorf("%s: %w", op, err) + } + + a.log.Info("Server shutdown gracefully") + + return nil +} diff --git a/internal/infrastructure/rpc/interceptor/logger.go b/internal/infrastructure/rpc/interceptor/logger.go new file mode 100644 index 0000000..5a6e59d --- /dev/null +++ b/internal/infrastructure/rpc/interceptor/logger.go @@ -0,0 +1,25 @@ +package interceptor + +import ( + "context" + "go.uber.org/zap" + + "google.golang.org/grpc" + "google.golang.org/grpc/status" +) + +func LoggerServerInterceptor(log *zap.Logger) grpc.UnaryServerInterceptor { + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + resp, err := handler(ctx, req) + + log.Info("Got RPC request", + zap.String("method", info.FullMethod), + zap.Any("request", req), + zap.Any("response", resp), + zap.Error(err), + zap.String("code", status.Code(err).String()), + ) + + return resp, err + } +} diff --git a/internal/infrastructure/rpc/server/method.go b/internal/infrastructure/rpc/server/method.go new file mode 100644 index 0000000..8cf83f0 --- /dev/null +++ b/internal/infrastructure/rpc/server/method.go @@ -0,0 +1,28 @@ +package server + +import ( + "context" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + pb "github.com/bjlag/go-keeper/internal/generated/rpc" +) + +const ( + RegisterMethod = "Register" +) + +func (s Server) Register(ctx context.Context, in *pb.RegisterIn) (*pb.RegisterOut, error) { + handler, ok := s.handlers[RegisterMethod] + if !ok { + return nil, status.Errorf(codes.NotFound, "handler for %s methos not found", RegisterMethod) + } + + h, ok := handler.(func(context.Context, *pb.RegisterIn) (*pb.RegisterOut, error)) + if !ok { + return nil, status.Errorf(codes.Internal, "handler for %s method not found", RegisterMethod) + } + + return h(ctx, in) +} diff --git a/internal/infrastructure/rpc/server/option.go b/internal/infrastructure/rpc/server/option.go new file mode 100644 index 0000000..ac0510e --- /dev/null +++ b/internal/infrastructure/rpc/server/option.go @@ -0,0 +1,19 @@ +package server + +import ( + "go.uber.org/zap" +) + +type Option func(*Server) + +func WithLogger(logger *zap.Logger) Option { + return func(s *Server) { + s.log = logger + } +} + +func WithHandler(method string, handler any) Option { + return func(s *Server) { + s.handlers[method] = handler + } +} diff --git a/internal/infrastructure/rpc/server/server.go b/internal/infrastructure/rpc/server/server.go new file mode 100644 index 0000000..50331d6 --- /dev/null +++ b/internal/infrastructure/rpc/server/server.go @@ -0,0 +1,70 @@ +package server + +import ( + "context" + "fmt" + "net" + + "go.uber.org/zap" + "golang.org/x/sync/errgroup" + "google.golang.org/grpc" + + pb "github.com/bjlag/go-keeper/internal/generated/rpc" + "github.com/bjlag/go-keeper/internal/infrastructure/rpc/interceptor" +) + +type Server struct { + pb.UnimplementedKeeperServer + + handlers map[string]any + log *zap.Logger +} + +func NewServer(opts ...Option) *Server { + s := &Server{ + handlers: make(map[string]any), + } + + for _, opt := range opts { + opt(s) + } + + return s +} + +func (s Server) Start(ctx context.Context) error { + const op = "rpc.Start" + + s.log.Info("Starting gRPC server") + + listen, err := net.Listen("tcp", ":8080") + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + grpcServer := grpc.NewServer( + grpc.ChainUnaryInterceptor( + interceptor.LoggerServerInterceptor(s.log), + ), + ) + + pb.RegisterKeeperServer(grpcServer, s) + + g, gCtx := errgroup.WithContext(ctx) + g.Go(func() error { + return grpcServer.Serve(listen) + }) + g.Go(func() error { + <-gCtx.Done() + + s.log.Info("Shutting down gRPC server") + grpcServer.GracefulStop() + + return nil + }) + if err := g.Wait(); err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + return nil +} diff --git a/internal/rpc/register/handler.go b/internal/rpc/register/handler.go new file mode 100644 index 0000000..c0dfaa8 --- /dev/null +++ b/internal/rpc/register/handler.go @@ -0,0 +1,27 @@ +package register + +import ( + "context" + + pb "github.com/bjlag/go-keeper/internal/generated/rpc" +) + +type Handler struct{} + +func New() *Handler { + return &Handler{} +} + +func (h *Handler) Handle(ctx context.Context, in *pb.RegisterIn) (*pb.RegisterOut, error) { + // todo проверить есть ли пользователь с указанным емейлом + // todo проверить политику паролей + // todo захешировать пароль + // todo добавить пользователя в базу + // todo выпустить токены + // todo вернуть токены + + return &pb.RegisterOut{ + AccessToken: "xxxx", + RefreshToken: "yyyy", + }, nil +} From f339b43f5257c51e57c09f1fdb192b686502676c Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Sat, 22 Feb 2025 11:47:20 +0300 Subject: [PATCH 07/94] auth: server config --- .gitignore | 4 ++- cmd/server/main.go | 26 ++++++++++++++++--- config/server.yaml.dist | 12 +++++++++ internal/app/app.go | 9 ++++--- internal/app/config.go | 18 +++++++++++++ .../infrastructure/rpc/interceptor/logger.go | 2 +- internal/infrastructure/rpc/server/option.go | 7 +++++ internal/infrastructure/rpc/server/server.go | 9 +++++-- 8 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 config/server.yaml.dist create mode 100644 internal/app/config.go diff --git a/.gitignore b/.gitignore index 1f0d777..335c5c6 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,6 @@ vendor/ .vscode # Configs -config/migrator.yaml \ No newline at end of file +config/migrator.yaml +config/server.yaml +config/client.yaml \ No newline at end of file diff --git a/cmd/server/main.go b/cmd/server/main.go index 708a7a6..77a64e3 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -2,14 +2,22 @@ package main import ( "context" + "flag" logNative "log" "os/signal" "syscall" + "github.com/ilyakaznacheev/cleanenv" + "go.uber.org/zap" + "github.com/bjlag/go-keeper/internal/app" "github.com/bjlag/go-keeper/internal/infrastructure/logger" ) +const ( + configPathDefault = "./config/server.yaml" +) + func main() { defer func() { if r := recover(); r != nil { @@ -17,15 +25,27 @@ func main() { } }() - ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) - defer cancel() + var configPath string + + flag.StringVar(&configPath, "c", configPathDefault, "Configuration directory") + flag.Parse() + + var cfg app.Config + if err := cleanenv.ReadConfig(configPath, &cfg); err != nil { + panic(err) + } log := logger.Get("dev") defer func() { _ = log.Sync() }() - err := app.NewApp(log).Run(ctx) + log.Debug("Config loaded", zap.Any("config", cfg)) + + ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) + defer cancel() + + err := app.NewApp(cfg, log).Run(ctx) if err != nil { panic(err) } diff --git a/config/server.yaml.dist b/config/server.yaml.dist new file mode 100644 index 0000000..9c21dd2 --- /dev/null +++ b/config/server.yaml.dist @@ -0,0 +1,12 @@ +env: dev + +address: + host: localhost + port: 8080 + +database: + host: localhost + port: 5444 + name: master + user: postgres + password: secret \ No newline at end of file diff --git a/internal/app/app.go b/internal/app/app.go index 2b21050..ebb25fb 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -11,11 +11,13 @@ import ( ) type App struct { + cfg Config log *zap.Logger } -func NewApp(log *zap.Logger) *App { +func NewApp(cfg Config, log *zap.Logger) *App { return &App{ + cfg: cfg, log: log, } } @@ -23,12 +25,13 @@ func NewApp(log *zap.Logger) *App { func (a *App) Run(ctx context.Context) error { const op = "app.Run" - server := server.NewServer( + s := server.NewServer( + server.WithAddress(a.cfg.Address.Host, a.cfg.Address.Port), server.WithLogger(a.log), server.WithHandler(server.RegisterMethod, register.New().Handle), ) - err := server.Start(ctx) + err := s.Start(ctx) if err != nil { a.log.Error("Failed to start gRPC server", zap.Error(err)) return fmt.Errorf("%s: %w", op, err) diff --git a/internal/app/config.go b/internal/app/config.go new file mode 100644 index 0000000..24e8244 --- /dev/null +++ b/internal/app/config.go @@ -0,0 +1,18 @@ +package app + +type Config struct { + Env string `yaml:"env" env:"ENV" env-default:"dev" env-description:"Environment" json:"env"` + + Address struct { + Host string `yaml:"host" env:"ADDRESS_HOST" env-description:"Server address host" json:"host"` + Port int `yaml:"port" env:"ADDRESS_PORT" env-description:"Server address port" json:"port"` + } `yaml:"address" json:"address"` + + Database struct { + Host string `yaml:"host" env:"DB_HOST" env-description:"Database host" json:"host"` + Port string `yaml:"port" env:"DB_PORT" env-description:"Database port" json:"port"` + Name string `yaml:"name" env:"DB_NAME" env-description:"Database name" json:"name"` + User string `yaml:"user" env:"DB_USER" env-description:"Database user" json:"user"` + Password string `yaml:"password" env:"DB_PASSWORD" env-description:"Database password" json:"password"` + } `yaml:"database" json:"database"` +} diff --git a/internal/infrastructure/rpc/interceptor/logger.go b/internal/infrastructure/rpc/interceptor/logger.go index 5a6e59d..41e8e39 100644 --- a/internal/infrastructure/rpc/interceptor/logger.go +++ b/internal/infrastructure/rpc/interceptor/logger.go @@ -2,8 +2,8 @@ package interceptor import ( "context" - "go.uber.org/zap" + "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/status" ) diff --git a/internal/infrastructure/rpc/server/option.go b/internal/infrastructure/rpc/server/option.go index ac0510e..f33bc7e 100644 --- a/internal/infrastructure/rpc/server/option.go +++ b/internal/infrastructure/rpc/server/option.go @@ -6,6 +6,13 @@ import ( type Option func(*Server) +func WithAddress(host string, port int) Option { + return func(s *Server) { + s.host = host + s.port = port + } +} + func WithLogger(logger *zap.Logger) Option { return func(s *Server) { s.log = logger diff --git a/internal/infrastructure/rpc/server/server.go b/internal/infrastructure/rpc/server/server.go index 50331d6..5ffcb9f 100644 --- a/internal/infrastructure/rpc/server/server.go +++ b/internal/infrastructure/rpc/server/server.go @@ -16,6 +16,8 @@ import ( type Server struct { pb.UnimplementedKeeperServer + host string + port int handlers map[string]any log *zap.Logger } @@ -35,9 +37,12 @@ func NewServer(opts ...Option) *Server { func (s Server) Start(ctx context.Context) error { const op = "rpc.Start" - s.log.Info("Starting gRPC server") + s.log.Info("Starting gRPC server", + zap.String("host", s.host), + zap.Int("port", s.port), + ) - listen, err := net.Listen("tcp", ":8080") + listen, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.host, s.port)) if err != nil { return fmt.Errorf("%s: %w", op, err) } From 56ff8dda4be529cafebeb667be296761399e7c27 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Sat, 22 Feb 2025 15:08:51 +0300 Subject: [PATCH 08/94] auth: register --- .golangci.yml | 2 + cmd/migrator/main.go | 2 +- cmd/server/main.go | 4 +- go.mod | 8 +- go.sum | 4 + internal/app/app.go | 25 +++++- internal/app/config.go | 8 ++ internal/domain/user/user.go | 15 ++++ internal/infrastructure/auth/jwt/jwt.go | 89 +++++++++++++++++++ internal/infrastructure/db/pg/pg.go | 1 + internal/infrastructure/logger/logger.go | 25 ++++++ .../infrastructure/rpc/interceptor/logger.go | 13 ++- internal/infrastructure/store/user/model.go | 37 ++++++++ internal/infrastructure/store/user/store.go | 56 ++++++++++++ internal/infrastructure/validator/email.go | 19 ++++ internal/infrastructure/validator/password.go | 7 ++ internal/rpc/register/contract.go | 11 +++ internal/rpc/register/handler.go | 50 ++++++++--- internal/usecase/user/register/contract.go | 16 ++++ internal/usecase/user/register/model.go | 11 +++ internal/usecase/user/register/usecase.go | 80 +++++++++++++++++ 21 files changed, 459 insertions(+), 24 deletions(-) create mode 100644 internal/domain/user/user.go create mode 100644 internal/infrastructure/auth/jwt/jwt.go create mode 100644 internal/infrastructure/store/user/model.go create mode 100644 internal/infrastructure/store/user/store.go create mode 100644 internal/infrastructure/validator/email.go create mode 100644 internal/infrastructure/validator/password.go create mode 100644 internal/rpc/register/contract.go create mode 100644 internal/usecase/user/register/contract.go create mode 100644 internal/usecase/user/register/model.go create mode 100644 internal/usecase/user/register/usecase.go diff --git a/.golangci.yml b/.golangci.yml index 9cb2ac3..28cc558 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -16,6 +16,8 @@ linters: - paralleltest - tagliatelle - gochecknoglobals + - nonamedreturns + - wrapcheck linters-settings: gci: diff --git a/cmd/migrator/main.go b/cmd/migrator/main.go index 0c55244..0dedc8f 100644 --- a/cmd/migrator/main.go +++ b/cmd/migrator/main.go @@ -30,7 +30,7 @@ func main() { var configPath string - flag.StringVar(&configPath, "c", configPathDefault, "Configuration directory") + flag.StringVar(&configPath, "c", configPathDefault, "Path to config file") flag.Parse() var cfg Config diff --git a/cmd/server/main.go b/cmd/server/main.go index 77a64e3..6c0acb6 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -27,7 +27,7 @@ func main() { var configPath string - flag.StringVar(&configPath, "c", configPathDefault, "Configuration directory") + flag.StringVar(&configPath, "c", configPathDefault, "Path to config file") flag.Parse() var cfg app.Config @@ -35,7 +35,7 @@ func main() { panic(err) } - log := logger.Get("dev") + log := logger.Get(cfg.Env) defer func() { _ = log.Sync() }() diff --git a/go.mod b/go.mod index f6a0e33..0f257bb 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,17 @@ module github.com/bjlag/go-keeper go 1.23 require ( + github.com/golang-jwt/jwt/v4 v4.5.1 github.com/golang-migrate/migrate/v4 v4.18.2 + github.com/google/uuid v1.6.0 github.com/ilyakaznacheev/cleanenv v1.5.0 + github.com/jackc/pgx/v5 v5.7.2 github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.10.9 github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 + golang.org/x/crypto v0.33.0 + golang.org/x/sync v0.11.0 google.golang.org/grpc v1.64.1 google.golang.org/protobuf v1.34.2 ) @@ -21,15 +26,12 @@ require ( github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.7.2 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/joho/godotenv v1.5.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.33.0 // indirect golang.org/x/net v0.33.0 // indirect - golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect diff --git a/go.sum b/go.sum index 216527b..4ec7ca2 100644 --- a/go.sum +++ b/go.sum @@ -30,10 +30,14 @@ github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpv github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= +github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-migrate/migrate/v4 v4.18.2 h1:2VSCMz7x7mjyTXx3m2zPokOY82LTRgxK1yQYKo6wWQ8= github.com/golang-migrate/migrate/v4 v4.18.2/go.mod h1:2CM6tJvn2kqPXwnXO/d3rAQYiyoIm180VsO8PRX6Rpk= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/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= diff --git a/internal/app/app.go b/internal/app/app.go index ebb25fb..f45e035 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -6,8 +6,12 @@ import ( "go.uber.org/zap" + "github.com/bjlag/go-keeper/internal/infrastructure/auth/jwt" + "github.com/bjlag/go-keeper/internal/infrastructure/db/pg" "github.com/bjlag/go-keeper/internal/infrastructure/rpc/server" - "github.com/bjlag/go-keeper/internal/rpc/register" + "github.com/bjlag/go-keeper/internal/infrastructure/store/user" + rpcRegister "github.com/bjlag/go-keeper/internal/rpc/register" + "github.com/bjlag/go-keeper/internal/usecase/user/register" ) type App struct { @@ -25,13 +29,28 @@ func NewApp(cfg Config, log *zap.Logger) *App { func (a *App) Run(ctx context.Context) error { const op = "app.Run" + dbConf := a.cfg.Database + db, err := pg.New(pg.GetDSN(dbConf.Host, dbConf.Port, dbConf.Name, dbConf.User, dbConf.Password)).Connect() + if err != nil { + a.log.Error("Failed to get db connection", zap.Error(err)) + return fmt.Errorf("%s: %w", op, err) + } + defer func() { + _ = db.Close() + }() + + userStore := user.NewStore(db) + tokeGenerator := jwt.NewGenerator(a.cfg.Auth.SecretKey, a.cfg.Auth.AccessTokenExp, a.cfg.Auth.RefreshTokenExp) + + ucRegister := register.NewUsecase(userStore, tokeGenerator) + s := server.NewServer( server.WithAddress(a.cfg.Address.Host, a.cfg.Address.Port), server.WithLogger(a.log), - server.WithHandler(server.RegisterMethod, register.New().Handle), + server.WithHandler(server.RegisterMethod, rpcRegister.New(ucRegister).Handle), ) - err := s.Start(ctx) + err = s.Start(ctx) if err != nil { a.log.Error("Failed to start gRPC server", zap.Error(err)) return fmt.Errorf("%s: %w", op, err) diff --git a/internal/app/config.go b/internal/app/config.go index 24e8244..b3ab35e 100644 --- a/internal/app/config.go +++ b/internal/app/config.go @@ -1,5 +1,7 @@ package app +import "time" + type Config struct { Env string `yaml:"env" env:"ENV" env-default:"dev" env-description:"Environment" json:"env"` @@ -8,6 +10,12 @@ type Config struct { Port int `yaml:"port" env:"ADDRESS_PORT" env-description:"Server address port" json:"port"` } `yaml:"address" json:"address"` + Auth struct { + AccessTokenExp time.Duration `yaml:"accessTokenExp" env:"ACCESS_TOKEN_EXP" env-description:"Access token expiration" json:"access_token_exp"` + RefreshTokenExp time.Duration `yaml:"refreshTokenExp" env:"REFRESH_TOKEN_EXP" env-description:"Refresh token expiration" json:"refresh_token_exp"` + SecretKey string `yaml:"secretKey" env:"SECRET_KEY" env-description:"Secret key" json:"secret_key"` + } `yaml:"auth" json:"auth"` + Database struct { Host string `yaml:"host" env:"DB_HOST" env-description:"Database host" json:"host"` Port string `yaml:"port" env:"DB_PORT" env-description:"Database port" json:"port"` diff --git a/internal/domain/user/user.go b/internal/domain/user/user.go new file mode 100644 index 0000000..fb392a7 --- /dev/null +++ b/internal/domain/user/user.go @@ -0,0 +1,15 @@ +package user + +import ( + "time" + + "github.com/google/uuid" +) + +type User struct { + GUID uuid.UUID + Email string + PasswordHash string + CreatedAt time.Time + UpdatedAt time.Time +} diff --git a/internal/infrastructure/auth/jwt/jwt.go b/internal/infrastructure/auth/jwt/jwt.go new file mode 100644 index 0000000..28827ab --- /dev/null +++ b/internal/infrastructure/auth/jwt/jwt.go @@ -0,0 +1,89 @@ +package jwt + +import ( + "fmt" + "time" + + "github.com/golang-jwt/jwt/v4" +) + +type Claims struct { + jwt.RegisteredClaims +} + +type Generator struct { + secretKey string + accessTokenExp time.Duration + refreshTokenExp time.Duration +} + +func NewGenerator(secretKey string, accessTokenExp, refreshTokenExp time.Duration) *Generator { + return &Generator{ + secretKey: secretKey, + accessTokenExp: accessTokenExp, + refreshTokenExp: refreshTokenExp, + } +} + +func (g *Generator) GenerateTokens(uuid string) (accessToken string, refreshToken string, err error) { + const op = "GenerateTokens" + + var claim *Claims + + accessToken, claim, err = g.GenerateAccessToken(uuid) + if err != nil { + err = fmt.Errorf("%s: %w", op, err) + return + } + + refreshToken, err = g.GenerateRefreshToken(claim) + if err != nil { + err = fmt.Errorf("%s: %w", op, err) + } + + return +} + +func (g *Generator) GenerateAccessToken(uuid string) (string, *Claims, error) { + const op = "GenerateAccessToken" + + now := time.Now() + claim := &Claims{ + RegisteredClaims: jwt.RegisteredClaims{ + Issuer: uuid, + ExpiresAt: jwt.NewNumericDate(now.Add(g.accessTokenExp)), + Subject: "access_token", + IssuedAt: jwt.NewNumericDate(now), + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claim) + tokenString, err := token.SignedString([]byte(g.secretKey)) + if err != nil { + return "", nil, fmt.Errorf("%s: %w", op, err) + } + + return tokenString, claim, nil +} + +func (g *Generator) GenerateRefreshToken(cl *Claims) (string, error) { + const op = "GenerateRefreshToken" + + now := time.Now() + claim := &Claims{ + RegisteredClaims: jwt.RegisteredClaims{ + Issuer: cl.Issuer, + ExpiresAt: jwt.NewNumericDate(now.Add(g.refreshTokenExp)), + Subject: "refresh_token", + IssuedAt: jwt.NewNumericDate(now), + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claim) + tokenString, err := token.SignedString([]byte(g.secretKey)) + if err != nil { + return "", fmt.Errorf("%s: %w", op, err) + } + + return tokenString, nil +} diff --git a/internal/infrastructure/db/pg/pg.go b/internal/infrastructure/db/pg/pg.go index 3164536..aeb8b6b 100644 --- a/internal/infrastructure/db/pg/pg.go +++ b/internal/infrastructure/db/pg/pg.go @@ -4,6 +4,7 @@ import ( "fmt" "time" + _ "github.com/jackc/pgx/v5/stdlib" // pgx driver "github.com/jmoiron/sqlx" _ "github.com/lib/pq" // postgres driver ) diff --git a/internal/infrastructure/logger/logger.go b/internal/infrastructure/logger/logger.go index e2f1424..2f015ce 100644 --- a/internal/infrastructure/logger/logger.go +++ b/internal/infrastructure/logger/logger.go @@ -1,6 +1,7 @@ package logger import ( + "context" "os" "runtime" "sync" @@ -9,6 +10,10 @@ import ( "go.uber.org/zap/zapcore" ) +type ctxKeyLogger int + +const IDLoggerKey ctxKeyLogger = 0 + var ( once sync.Once logger *zap.Logger @@ -36,3 +41,23 @@ func Get(env string) *zap.Logger { return logger } + +func FromCtx(ctx context.Context) *zap.Logger { + if l, ok := ctx.Value(IDLoggerKey).(*zap.Logger); ok { + return l + } else if l := logger; l != nil { + return l + } + + return zap.NewNop() +} + +func WithCtx(ctx context.Context, l *zap.Logger) context.Context { + if lp, ok := ctx.Value(IDLoggerKey).(*zap.Logger); ok { + if lp == l { + return ctx + } + } + + return context.WithValue(ctx, IDLoggerKey, l) +} diff --git a/internal/infrastructure/rpc/interceptor/logger.go b/internal/infrastructure/rpc/interceptor/logger.go index 41e8e39..0c2aa4d 100644 --- a/internal/infrastructure/rpc/interceptor/logger.go +++ b/internal/infrastructure/rpc/interceptor/logger.go @@ -6,15 +6,20 @@ import ( "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/status" + + "github.com/bjlag/go-keeper/internal/infrastructure/logger" ) func LoggerServerInterceptor(log *zap.Logger) grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - resp, err := handler(ctx, req) + hLog := log + hLog = hLog. + With(zap.String("method", info.FullMethod)). + With(zap.Any("request", req)) + + resp, err := handler(logger.WithCtx(ctx, hLog), req) - log.Info("Got RPC request", - zap.String("method", info.FullMethod), - zap.Any("request", req), + hLog.Info("Got RPC request", zap.Any("response", resp), zap.Error(err), zap.String("code", status.Code(err).String()), diff --git a/internal/infrastructure/store/user/model.go b/internal/infrastructure/store/user/model.go new file mode 100644 index 0000000..19c7cd3 --- /dev/null +++ b/internal/infrastructure/store/user/model.go @@ -0,0 +1,37 @@ +package user + +import ( + "time" + + "github.com/google/uuid" + + model "github.com/bjlag/go-keeper/internal/domain/user" +) + +type row struct { + GUID uuid.UUID `db:"guid"` + Email string `db:"email"` + PasswordHash string `db:"password_hash"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` +} + +func convertToRow(m *model.User) row { + return row{ + GUID: m.GUID, + Email: m.Email, + PasswordHash: m.PasswordHash, + CreatedAt: m.CreatedAt, + UpdatedAt: m.UpdatedAt, + } +} + +func (r row) convertToModel() *model.User { + return &model.User{ + GUID: r.GUID, + Email: r.Email, + PasswordHash: r.PasswordHash, + CreatedAt: r.CreatedAt, + UpdatedAt: r.UpdatedAt, + } +} diff --git a/internal/infrastructure/store/user/store.go b/internal/infrastructure/store/user/store.go new file mode 100644 index 0000000..12e6bd2 --- /dev/null +++ b/internal/infrastructure/store/user/store.go @@ -0,0 +1,56 @@ +package user + +import ( + "context" + "database/sql" + "errors" + "fmt" + + "github.com/jmoiron/sqlx" + + model "github.com/bjlag/go-keeper/internal/domain/user" +) + +var ErrNotFound = errors.New("user not found") + +type Store struct { + db *sqlx.DB +} + +func NewStore(db *sqlx.DB) *Store { + return &Store{ + db: db, + } +} + +func (s Store) GetByEmail(ctx context.Context, email string) (*model.User, error) { + const op = "store.user.GetByEmail" + + query := ` + SELECT guid, email, password_hash, created_at, updated_at + FROM users + WHERE email = $1 + ` + + var user row + if err := s.db.GetContext(ctx, &user, query, email); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, ErrNotFound + } + return nil, fmt.Errorf("%s: %w", op, err) + } + + return user.convertToModel(), nil +} + +func (s Store) Add(ctx context.Context, user *model.User) error { + const op = "store.user.Add" + + query := `INSERT INTO users (guid, email, password_hash) VALUES (:guid, :email, :password_hash)` + _, err := s.db.NamedExecContext(ctx, query, convertToRow(user)) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + return nil +} diff --git a/internal/infrastructure/validator/email.go b/internal/infrastructure/validator/email.go new file mode 100644 index 0000000..e645f67 --- /dev/null +++ b/internal/infrastructure/validator/email.go @@ -0,0 +1,19 @@ +package validator + +import ( + "regexp" + "sync" +) + +var ( + once sync.Once + regexEmailPattern *regexp.Regexp +) + +func ValidateEmail(email string) bool { + once.Do(func() { + regexEmailPattern = regexp.MustCompile("^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$") + }) + + return regexEmailPattern.MatchString(email) +} diff --git a/internal/infrastructure/validator/password.go b/internal/infrastructure/validator/password.go new file mode 100644 index 0000000..bc30a08 --- /dev/null +++ b/internal/infrastructure/validator/password.go @@ -0,0 +1,7 @@ +package validator + +const minPasswordLength = 8 + +func ValidatePassword(password string) bool { + return !(len(password) < minPasswordLength) +} diff --git a/internal/rpc/register/contract.go b/internal/rpc/register/contract.go new file mode 100644 index 0000000..ceb0e8d --- /dev/null +++ b/internal/rpc/register/contract.go @@ -0,0 +1,11 @@ +package register + +import ( + "context" + + "github.com/bjlag/go-keeper/internal/usecase/user/register" +) + +type usecase interface { + Do(ctx context.Context, data register.Data) (*register.Result, error) +} diff --git a/internal/rpc/register/handler.go b/internal/rpc/register/handler.go index c0dfaa8..796853b 100644 --- a/internal/rpc/register/handler.go +++ b/internal/rpc/register/handler.go @@ -2,26 +2,54 @@ package register import ( "context" + "errors" + + "go.uber.org/zap" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" pb "github.com/bjlag/go-keeper/internal/generated/rpc" + "github.com/bjlag/go-keeper/internal/infrastructure/logger" + "github.com/bjlag/go-keeper/internal/infrastructure/validator" + "github.com/bjlag/go-keeper/internal/usecase/user/register" ) -type Handler struct{} +type Handler struct { + usecase usecase +} -func New() *Handler { - return &Handler{} +func New(usecase usecase) *Handler { + return &Handler{ + usecase: usecase, + } } func (h *Handler) Handle(ctx context.Context, in *pb.RegisterIn) (*pb.RegisterOut, error) { - // todo проверить есть ли пользователь с указанным емейлом - // todo проверить политику паролей - // todo захешировать пароль - // todo добавить пользователя в базу - // todo выпустить токены - // todo вернуть токены + log := logger.FromCtx(ctx) + + if !validator.ValidateEmail(in.GetEmail()) { + return nil, status.Error(codes.InvalidArgument, "email is invalid") + } + + if validator.ValidatePassword(in.GetPassword()) { + return nil, status.Error(codes.InvalidArgument, "password is invalid (min. length 8 characters)") + } + + result, err := h.usecase.Do(ctx, register.Data{ + Email: in.GetEmail(), + Password: in.GetPassword(), + }) + if err != nil { + if errors.Is(err, register.ErrUserAlreadyExists) { + return nil, status.Error(codes.AlreadyExists, "user with this email already exists") + } + + log.Error("failed to register user", zap.Error(err)) + return nil, status.Error(codes.Internal, "internal server error") + } return &pb.RegisterOut{ - AccessToken: "xxxx", - RefreshToken: "yyyy", + AccessToken: result.AccessToken, + RefreshToken: result.RefreshToken, }, nil } diff --git a/internal/usecase/user/register/contract.go b/internal/usecase/user/register/contract.go new file mode 100644 index 0000000..51e93de --- /dev/null +++ b/internal/usecase/user/register/contract.go @@ -0,0 +1,16 @@ +package register + +import ( + "context" + + model "github.com/bjlag/go-keeper/internal/domain/user" +) + +type userStore interface { + GetByEmail(ctx context.Context, email string) (*model.User, error) + Add(ctx context.Context, user *model.User) error +} + +type tokenGenerator interface { + GenerateTokens(guid string) (accessToken, refreshToken string, err error) +} diff --git a/internal/usecase/user/register/model.go b/internal/usecase/user/register/model.go new file mode 100644 index 0000000..6dca93a --- /dev/null +++ b/internal/usecase/user/register/model.go @@ -0,0 +1,11 @@ +package register + +type Data struct { + Email string + Password string +} + +type Result struct { + AccessToken string + RefreshToken string +} diff --git a/internal/usecase/user/register/usecase.go b/internal/usecase/user/register/usecase.go new file mode 100644 index 0000000..63436e4 --- /dev/null +++ b/internal/usecase/user/register/usecase.go @@ -0,0 +1,80 @@ +package register + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/google/uuid" + "golang.org/x/crypto/bcrypt" + + model "github.com/bjlag/go-keeper/internal/domain/user" + storeUser "github.com/bjlag/go-keeper/internal/infrastructure/store/user" +) + +var ErrUserAlreadyExists = errors.New("user already exists") + +type Usecase struct { + userStore userStore + tokenGenerator tokenGenerator +} + +func NewUsecase(userStore userStore, tokenGenerator tokenGenerator) *Usecase { + return &Usecase{ + userStore: userStore, + tokenGenerator: tokenGenerator, + } +} + +func (u Usecase) Do(ctx context.Context, data Data) (*Result, error) { + const op = "usecase.register.Do" + + exist, err := u.isUserExists(ctx, data.Email) + if err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + if exist { + return nil, ErrUserAlreadyExists + } + + passwordHash, err := bcrypt.GenerateFromPassword([]byte(data.Password), bcrypt.MinCost) + if err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + + user := &model.User{ + GUID: uuid.New(), + Email: data.Email, + PasswordHash: string(passwordHash), + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + err = u.userStore.Add(ctx, user) + if err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + + accessToken, refreshToken, err := u.tokenGenerator.GenerateTokens(user.GUID.String()) + if err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + + return &Result{ + AccessToken: accessToken, + RefreshToken: refreshToken, + }, nil +} + +func (u Usecase) isUserExists(ctx context.Context, email string) (bool, error) { + m, err := u.userStore.GetByEmail(ctx, email) + if err != nil { + if errors.Is(err, storeUser.ErrNotFound) { + return false, nil + } + return false, fmt.Errorf("failed to get user by email: %w", err) + } + + return m != nil, nil +} From 1e9567a48d94a5b2ea118791d382ab9d6ac341de Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Sun, 23 Feb 2025 00:11:36 +0300 Subject: [PATCH 09/94] auth: login --- internal/app/app.go | 5 + internal/generated/rpc/keeper.pb.go | 144 +++++++++++++++++-- internal/generated/rpc/keeper_grpc.pb.go | 38 +++++ internal/infrastructure/rpc/server/method.go | 30 +++- internal/rpc/login/contract.go | 11 ++ internal/rpc/login/handler.go | 54 +++++++ internal/usecase/user/login/contract.go | 15 ++ internal/usecase/user/login/model.go | 11 ++ internal/usecase/user/login/usecase.go | 56 ++++++++ proto/keeper.proto | 15 +- 10 files changed, 362 insertions(+), 17 deletions(-) create mode 100644 internal/rpc/login/contract.go create mode 100644 internal/rpc/login/handler.go create mode 100644 internal/usecase/user/login/contract.go create mode 100644 internal/usecase/user/login/model.go create mode 100644 internal/usecase/user/login/usecase.go diff --git a/internal/app/app.go b/internal/app/app.go index f45e035..196fbd6 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -10,7 +10,9 @@ import ( "github.com/bjlag/go-keeper/internal/infrastructure/db/pg" "github.com/bjlag/go-keeper/internal/infrastructure/rpc/server" "github.com/bjlag/go-keeper/internal/infrastructure/store/user" + rpcLogin "github.com/bjlag/go-keeper/internal/rpc/login" rpcRegister "github.com/bjlag/go-keeper/internal/rpc/register" + "github.com/bjlag/go-keeper/internal/usecase/user/login" "github.com/bjlag/go-keeper/internal/usecase/user/register" ) @@ -43,11 +45,14 @@ func (a *App) Run(ctx context.Context) error { tokeGenerator := jwt.NewGenerator(a.cfg.Auth.SecretKey, a.cfg.Auth.AccessTokenExp, a.cfg.Auth.RefreshTokenExp) ucRegister := register.NewUsecase(userStore, tokeGenerator) + ucLogin := login.NewUsecase(userStore, tokeGenerator) s := server.NewServer( server.WithAddress(a.cfg.Address.Host, a.cfg.Address.Port), server.WithLogger(a.log), + server.WithHandler(server.RegisterMethod, rpcRegister.New(ucRegister).Handle), + server.WithHandler(server.LoginMethod, rpcLogin.New(ucLogin).Handle), ) err = s.Start(ctx) diff --git a/internal/generated/rpc/keeper.pb.go b/internal/generated/rpc/keeper.pb.go index 4903adf..c0d2995 100644 --- a/internal/generated/rpc/keeper.pb.go +++ b/internal/generated/rpc/keeper.pb.go @@ -125,6 +125,110 @@ func (x *RegisterOut) GetRefreshToken() string { return "" } +type LoginIn struct { + state protoimpl.MessageState `protogen:"open.v1"` + Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` + Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LoginIn) Reset() { + *x = LoginIn{} + mi := &file_proto_keeper_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LoginIn) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LoginIn) ProtoMessage() {} + +func (x *LoginIn) ProtoReflect() protoreflect.Message { + mi := &file_proto_keeper_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LoginIn.ProtoReflect.Descriptor instead. +func (*LoginIn) Descriptor() ([]byte, []int) { + return file_proto_keeper_proto_rawDescGZIP(), []int{2} +} + +func (x *LoginIn) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *LoginIn) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +type LoginOut struct { + state protoimpl.MessageState `protogen:"open.v1"` + AccessToken string `protobuf:"bytes,1,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` + RefreshToken string `protobuf:"bytes,2,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LoginOut) Reset() { + *x = LoginOut{} + mi := &file_proto_keeper_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LoginOut) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LoginOut) ProtoMessage() {} + +func (x *LoginOut) ProtoReflect() protoreflect.Message { + mi := &file_proto_keeper_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LoginOut.ProtoReflect.Descriptor instead. +func (*LoginOut) Descriptor() ([]byte, []int) { + return file_proto_keeper_proto_rawDescGZIP(), []int{3} +} + +func (x *LoginOut) GetAccessToken() string { + if x != nil { + return x.AccessToken + } + return "" +} + +func (x *LoginOut) GetRefreshToken() string { + if x != nil { + return x.RefreshToken + } + return "" +} + var File_proto_keeper_proto protoreflect.FileDescriptor var file_proto_keeper_proto_rawDesc = string([]byte{ @@ -139,13 +243,25 @@ var file_proto_keeper_proto_rawDesc = string([]byte{ 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x32, 0x3d, 0x0a, 0x06, 0x4b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x12, 0x33, 0x0a, - 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x12, 0x2e, 0x6b, 0x65, 0x65, 0x70, - 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x1a, 0x13, 0x2e, - 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, - 0x75, 0x74, 0x42, 0x18, 0x5a, 0x16, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, - 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x6b, 0x65, 0x6e, 0x22, 0x3b, 0x0a, 0x07, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x12, 0x14, + 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, + 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, + 0x22, 0x52, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x4f, 0x75, 0x74, 0x12, 0x21, 0x0a, 0x0c, + 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, + 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x32, 0x69, 0x0a, 0x06, 0x4b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x12, 0x33, + 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x12, 0x2e, 0x6b, 0x65, 0x65, + 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x1a, 0x13, + 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, + 0x4f, 0x75, 0x74, 0x12, 0x2a, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x0f, 0x2e, 0x6b, + 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x1a, 0x10, 0x2e, + 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x4f, 0x75, 0x74, 0x42, + 0x18, 0x5a, 0x16, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x65, + 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, }) var ( @@ -160,16 +276,20 @@ func file_proto_keeper_proto_rawDescGZIP() []byte { return file_proto_keeper_proto_rawDescData } -var file_proto_keeper_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_proto_keeper_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_proto_keeper_proto_goTypes = []any{ (*RegisterIn)(nil), // 0: keeper.RegisterIn (*RegisterOut)(nil), // 1: keeper.RegisterOut + (*LoginIn)(nil), // 2: keeper.LoginIn + (*LoginOut)(nil), // 3: keeper.LoginOut } var file_proto_keeper_proto_depIdxs = []int32{ 0, // 0: keeper.Keeper.Register:input_type -> keeper.RegisterIn - 1, // 1: keeper.Keeper.Register:output_type -> keeper.RegisterOut - 1, // [1:2] is the sub-list for method output_type - 0, // [0:1] is the sub-list for method input_type + 2, // 1: keeper.Keeper.Login:input_type -> keeper.LoginIn + 1, // 2: keeper.Keeper.Register:output_type -> keeper.RegisterOut + 3, // 3: keeper.Keeper.Login:output_type -> keeper.LoginOut + 2, // [2:4] is the sub-list for method output_type + 0, // [0:2] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name @@ -186,7 +306,7 @@ func file_proto_keeper_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_keeper_proto_rawDesc), len(file_proto_keeper_proto_rawDesc)), NumEnums: 0, - NumMessages: 2, + NumMessages: 4, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/generated/rpc/keeper_grpc.pb.go b/internal/generated/rpc/keeper_grpc.pb.go index 9c89436..403014b 100644 --- a/internal/generated/rpc/keeper_grpc.pb.go +++ b/internal/generated/rpc/keeper_grpc.pb.go @@ -20,6 +20,7 @@ const _ = grpc.SupportPackageIsVersion9 const ( Keeper_Register_FullMethodName = "/keeper.Keeper/Register" + Keeper_Login_FullMethodName = "/keeper.Keeper/Login" ) // KeeperClient is the client API for Keeper service. @@ -27,6 +28,7 @@ const ( // 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 KeeperClient interface { Register(ctx context.Context, in *RegisterIn, opts ...grpc.CallOption) (*RegisterOut, error) + Login(ctx context.Context, in *LoginIn, opts ...grpc.CallOption) (*LoginOut, error) } type keeperClient struct { @@ -47,11 +49,22 @@ func (c *keeperClient) Register(ctx context.Context, in *RegisterIn, opts ...grp return out, nil } +func (c *keeperClient) Login(ctx context.Context, in *LoginIn, opts ...grpc.CallOption) (*LoginOut, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(LoginOut) + err := c.cc.Invoke(ctx, Keeper_Login_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // KeeperServer is the server API for Keeper service. // All implementations should embed UnimplementedKeeperServer // for forward compatibility. type KeeperServer interface { Register(context.Context, *RegisterIn) (*RegisterOut, error) + Login(context.Context, *LoginIn) (*LoginOut, error) } // UnimplementedKeeperServer should be embedded to have @@ -64,6 +77,9 @@ type UnimplementedKeeperServer struct{} func (UnimplementedKeeperServer) Register(context.Context, *RegisterIn) (*RegisterOut, error) { return nil, status.Errorf(codes.Unimplemented, "method Register not implemented") } +func (UnimplementedKeeperServer) Login(context.Context, *LoginIn) (*LoginOut, error) { + return nil, status.Errorf(codes.Unimplemented, "method Login not implemented") +} func (UnimplementedKeeperServer) testEmbeddedByValue() {} // UnsafeKeeperServer may be embedded to opt out of forward compatibility for this service. @@ -102,6 +118,24 @@ func _Keeper_Register_Handler(srv interface{}, ctx context.Context, dec func(int return interceptor(ctx, in, info, handler) } +func _Keeper_Login_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(LoginIn) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(KeeperServer).Login(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Keeper_Login_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(KeeperServer).Login(ctx, req.(*LoginIn)) + } + return interceptor(ctx, in, info, handler) +} + // Keeper_ServiceDesc is the grpc.ServiceDesc for Keeper service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -113,6 +147,10 @@ var Keeper_ServiceDesc = grpc.ServiceDesc{ MethodName: "Register", Handler: _Keeper_Register_Handler, }, + { + MethodName: "Login", + Handler: _Keeper_Login_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "proto/keeper.proto", diff --git a/internal/infrastructure/rpc/server/method.go b/internal/infrastructure/rpc/server/method.go index 8cf83f0..0f5ad98 100644 --- a/internal/infrastructure/rpc/server/method.go +++ b/internal/infrastructure/rpc/server/method.go @@ -11,12 +11,13 @@ import ( const ( RegisterMethod = "Register" + LoginMethod = "Login" ) func (s Server) Register(ctx context.Context, in *pb.RegisterIn) (*pb.RegisterOut, error) { - handler, ok := s.handlers[RegisterMethod] - if !ok { - return nil, status.Errorf(codes.NotFound, "handler for %s methos not found", RegisterMethod) + handler, err := s.getHandler(RegisterMethod) + if err != nil { + return nil, err } h, ok := handler.(func(context.Context, *pb.RegisterIn) (*pb.RegisterOut, error)) @@ -26,3 +27,26 @@ func (s Server) Register(ctx context.Context, in *pb.RegisterIn) (*pb.RegisterOu return h(ctx, in) } + +func (s Server) Login(ctx context.Context, in *pb.LoginIn) (*pb.LoginOut, error) { + handler, err := s.getHandler(LoginMethod) + if err != nil { + return nil, err + } + + h, ok := handler.(func(context.Context, *pb.LoginIn) (*pb.LoginOut, error)) + if !ok { + return nil, status.Errorf(codes.Internal, "handler for %s method not found", LoginMethod) + } + + return h(ctx, in) +} + +func (s Server) getHandler(name string) (any, error) { + handler, ok := s.handlers[name] + if !ok { + return nil, status.Errorf(codes.NotFound, "handler for %s methos not found", name) + } + + return handler, nil +} diff --git a/internal/rpc/login/contract.go b/internal/rpc/login/contract.go new file mode 100644 index 0000000..9b207a0 --- /dev/null +++ b/internal/rpc/login/contract.go @@ -0,0 +1,11 @@ +package login + +import ( + "context" + + "github.com/bjlag/go-keeper/internal/usecase/user/login" +) + +type usecase interface { + Do(ctx context.Context, data login.Data) (*login.Result, error) +} diff --git a/internal/rpc/login/handler.go b/internal/rpc/login/handler.go new file mode 100644 index 0000000..819f3ae --- /dev/null +++ b/internal/rpc/login/handler.go @@ -0,0 +1,54 @@ +package login + +import ( + "context" + "errors" + + "go.uber.org/zap" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + pb "github.com/bjlag/go-keeper/internal/generated/rpc" + "github.com/bjlag/go-keeper/internal/infrastructure/logger" + "github.com/bjlag/go-keeper/internal/infrastructure/validator" + "github.com/bjlag/go-keeper/internal/usecase/user/login" +) + +type Handler struct { + usecase usecase +} + +func New(usecase usecase) *Handler { + return &Handler{ + usecase: usecase, + } +} + +func (h *Handler) Handle(ctx context.Context, in *pb.LoginIn) (*pb.LoginOut, error) { + log := logger.FromCtx(ctx) + + if !validator.ValidateEmail(in.GetEmail()) { + return nil, status.Error(codes.InvalidArgument, "email is invalid") + } + + result, err := h.usecase.Do(ctx, login.Data{ + Email: in.GetEmail(), + Password: in.GetPassword(), + }) + if err != nil { + switch { + case errors.Is(err, login.ErrUserNotFound): + return nil, status.Error(codes.Unauthenticated, "password incorrect") + case errors.Is(err, login.ErrPasswordIncorrect): + return nil, status.Error(codes.Unauthenticated, "password incorrect") + default: + log.Error("Failed to login user", zap.Error(err)) + return nil, status.Error(codes.Internal, "internal error") + } + } + + return &pb.LoginOut{ + AccessToken: result.AccessToken, + RefreshToken: result.RefreshToken, + }, nil +} diff --git a/internal/usecase/user/login/contract.go b/internal/usecase/user/login/contract.go new file mode 100644 index 0000000..e674949 --- /dev/null +++ b/internal/usecase/user/login/contract.go @@ -0,0 +1,15 @@ +package login + +import ( + "context" + + model "github.com/bjlag/go-keeper/internal/domain/user" +) + +type userStore interface { + GetByEmail(ctx context.Context, email string) (*model.User, error) +} + +type tokenGenerator interface { + GenerateTokens(guid string) (accessToken, refreshToken string, err error) +} diff --git a/internal/usecase/user/login/model.go b/internal/usecase/user/login/model.go new file mode 100644 index 0000000..17dac34 --- /dev/null +++ b/internal/usecase/user/login/model.go @@ -0,0 +1,11 @@ +package login + +type Data struct { + Email string + Password string +} + +type Result struct { + AccessToken string + RefreshToken string +} diff --git a/internal/usecase/user/login/usecase.go b/internal/usecase/user/login/usecase.go new file mode 100644 index 0000000..02097d7 --- /dev/null +++ b/internal/usecase/user/login/usecase.go @@ -0,0 +1,56 @@ +package login + +import ( + "context" + "errors" + "fmt" + + "golang.org/x/crypto/bcrypt" + + storeUser "github.com/bjlag/go-keeper/internal/infrastructure/store/user" +) + +var ( + ErrUserNotFound = errors.New("user not found") + ErrPasswordIncorrect = errors.New("password incorrect") +) + +type Usecase struct { + userStore userStore + tokenGenerator tokenGenerator +} + +func NewUsecase(userStore userStore, tokenGenerator tokenGenerator) *Usecase { + return &Usecase{ + userStore: userStore, + tokenGenerator: tokenGenerator, + } +} + +func (u Usecase) Do(ctx context.Context, data Data) (*Result, error) { + const op = "usecase.login.Do" + + user, err := u.userStore.GetByEmail(ctx, data.Email) + if err != nil { + if errors.Is(err, storeUser.ErrNotFound) { + return nil, ErrUserNotFound + } + + return nil, fmt.Errorf("%s: %w", op, err) + } + + err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(data.Password)) + if err != nil { + return nil, ErrPasswordIncorrect + } + + accessToken, refreshToken, err := u.tokenGenerator.GenerateTokens(user.GUID.String()) + if err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + + return &Result{ + AccessToken: accessToken, + RefreshToken: refreshToken, + }, nil +} diff --git a/proto/keeper.proto b/proto/keeper.proto index 192ee49..2f64c1c 100644 --- a/proto/keeper.proto +++ b/proto/keeper.proto @@ -6,6 +6,7 @@ option go_package = "internal/generated/rpc"; service Keeper { rpc Register(RegisterIn) returns (RegisterOut); + rpc Login(LoginIn) returns (LoginOut); } message RegisterIn { @@ -14,6 +15,16 @@ message RegisterIn { } message RegisterOut { - string access_token = 1; - string refresh_token = 2; + string access_token = 1; + string refresh_token = 2; } + +message LoginIn { + string email = 1; + string password = 2; +} + +message LoginOut { + string access_token = 1; + string refresh_token = 2; +} \ No newline at end of file From 2491be9ade535c4ad6e7cb7f8057b71c1bbf4328 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Sun, 23 Feb 2025 16:03:09 +0300 Subject: [PATCH 10/94] auth: refresh tokens --- .golangci.yml | 11 ++ internal/app/app.go | 4 + internal/generated/rpc/keeper.pb.go | 153 +++++++++++++++--- internal/generated/rpc/keeper_grpc.pb.go | 42 ++++- internal/infrastructure/auth/jwt/jwt.go | 30 ++++ internal/infrastructure/rpc/server/method.go | 19 ++- internal/infrastructure/store/user/store.go | 20 +++ internal/rpc/refresh_tokens/contract.go | 11 ++ internal/rpc/refresh_tokens/handler.go | 49 ++++++ .../usecase/user/refresh_tokens/contract.go | 16 ++ internal/usecase/user/refresh_tokens/model.go | 10 ++ .../usecase/user/refresh_tokens/usecase.go | 58 +++++++ proto/keeper.proto | 10 ++ 13 files changed, 409 insertions(+), 24 deletions(-) create mode 100644 internal/rpc/refresh_tokens/contract.go create mode 100644 internal/rpc/refresh_tokens/handler.go create mode 100644 internal/usecase/user/refresh_tokens/contract.go create mode 100644 internal/usecase/user/refresh_tokens/model.go create mode 100644 internal/usecase/user/refresh_tokens/usecase.go diff --git a/.golangci.yml b/.golangci.yml index 28cc558..60cbab5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -20,6 +20,17 @@ linters: - wrapcheck linters-settings: + revive: + rules: + - name: var-naming + arguments: + - ["ID"] + - ["VM"] + - - skipPackageNameChecks: true + + stylecheck: + checks: ["all", "-ST1003"] + gci: sections: - standard # Standard lib diff --git a/internal/app/app.go b/internal/app/app.go index 196fbd6..44bc087 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -11,8 +11,10 @@ import ( "github.com/bjlag/go-keeper/internal/infrastructure/rpc/server" "github.com/bjlag/go-keeper/internal/infrastructure/store/user" rpcLogin "github.com/bjlag/go-keeper/internal/rpc/login" + rpcRefreshTokens "github.com/bjlag/go-keeper/internal/rpc/refresh_tokens" rpcRegister "github.com/bjlag/go-keeper/internal/rpc/register" "github.com/bjlag/go-keeper/internal/usecase/user/login" + rt "github.com/bjlag/go-keeper/internal/usecase/user/refresh_tokens" "github.com/bjlag/go-keeper/internal/usecase/user/register" ) @@ -46,6 +48,7 @@ func (a *App) Run(ctx context.Context) error { ucRegister := register.NewUsecase(userStore, tokeGenerator) ucLogin := login.NewUsecase(userStore, tokeGenerator) + ucRefreshTokens := rt.NewUsecase(userStore, tokeGenerator) s := server.NewServer( server.WithAddress(a.cfg.Address.Host, a.cfg.Address.Port), @@ -53,6 +56,7 @@ func (a *App) Run(ctx context.Context) error { server.WithHandler(server.RegisterMethod, rpcRegister.New(ucRegister).Handle), server.WithHandler(server.LoginMethod, rpcLogin.New(ucLogin).Handle), + server.WithHandler(server.RefreshTokensMethod, rpcRefreshTokens.New(ucRefreshTokens).Handle), ) err = s.Start(ctx) diff --git a/internal/generated/rpc/keeper.pb.go b/internal/generated/rpc/keeper.pb.go index c0d2995..b23ff8d 100644 --- a/internal/generated/rpc/keeper.pb.go +++ b/internal/generated/rpc/keeper.pb.go @@ -229,6 +229,102 @@ func (x *LoginOut) GetRefreshToken() string { return "" } +type RefreshTokensIn struct { + state protoimpl.MessageState `protogen:"open.v1"` + RefreshToken string `protobuf:"bytes,1,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RefreshTokensIn) Reset() { + *x = RefreshTokensIn{} + mi := &file_proto_keeper_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RefreshTokensIn) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RefreshTokensIn) ProtoMessage() {} + +func (x *RefreshTokensIn) ProtoReflect() protoreflect.Message { + mi := &file_proto_keeper_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RefreshTokensIn.ProtoReflect.Descriptor instead. +func (*RefreshTokensIn) Descriptor() ([]byte, []int) { + return file_proto_keeper_proto_rawDescGZIP(), []int{4} +} + +func (x *RefreshTokensIn) GetRefreshToken() string { + if x != nil { + return x.RefreshToken + } + return "" +} + +type RefreshTokensOut struct { + state protoimpl.MessageState `protogen:"open.v1"` + AccessToken string `protobuf:"bytes,1,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` + RefreshToken string `protobuf:"bytes,2,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RefreshTokensOut) Reset() { + *x = RefreshTokensOut{} + mi := &file_proto_keeper_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RefreshTokensOut) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RefreshTokensOut) ProtoMessage() {} + +func (x *RefreshTokensOut) ProtoReflect() protoreflect.Message { + mi := &file_proto_keeper_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RefreshTokensOut.ProtoReflect.Descriptor instead. +func (*RefreshTokensOut) Descriptor() ([]byte, []int) { + return file_proto_keeper_proto_rawDescGZIP(), []int{5} +} + +func (x *RefreshTokensOut) GetAccessToken() string { + if x != nil { + return x.AccessToken + } + return "" +} + +func (x *RefreshTokensOut) GetRefreshToken() string { + if x != nil { + return x.RefreshToken + } + return "" +} + var File_proto_keeper_proto protoreflect.FileDescriptor var file_proto_keeper_proto_rawDesc = string([]byte{ @@ -252,16 +348,29 @@ var file_proto_keeper_proto_rawDesc = string([]byte{ 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x32, 0x69, 0x0a, 0x06, 0x4b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x12, 0x33, - 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x12, 0x2e, 0x6b, 0x65, 0x65, - 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x1a, 0x13, - 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, - 0x4f, 0x75, 0x74, 0x12, 0x2a, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x0f, 0x2e, 0x6b, - 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x1a, 0x10, 0x2e, - 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x4f, 0x75, 0x74, 0x42, - 0x18, 0x5a, 0x16, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x65, - 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x36, 0x0a, 0x0f, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x49, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, + 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x5a, 0x0a, 0x10, + 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x4f, 0x75, 0x74, + 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, + 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x32, 0xad, 0x01, 0x0a, 0x06, 0x4b, 0x65, 0x65, + 0x70, 0x65, 0x72, 0x12, 0x33, 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, + 0x12, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, + 0x72, 0x49, 0x6e, 0x1a, 0x13, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x12, 0x2a, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, + 0x6e, 0x12, 0x0f, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, + 0x49, 0x6e, 0x1a, 0x10, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, + 0x6e, 0x4f, 0x75, 0x74, 0x12, 0x42, 0x0a, 0x0d, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x17, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, + 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x49, 0x6e, 0x1a, 0x18, + 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x4f, 0x75, 0x74, 0x42, 0x18, 0x5a, 0x16, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x72, + 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -276,20 +385,24 @@ func file_proto_keeper_proto_rawDescGZIP() []byte { return file_proto_keeper_proto_rawDescData } -var file_proto_keeper_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_proto_keeper_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_proto_keeper_proto_goTypes = []any{ - (*RegisterIn)(nil), // 0: keeper.RegisterIn - (*RegisterOut)(nil), // 1: keeper.RegisterOut - (*LoginIn)(nil), // 2: keeper.LoginIn - (*LoginOut)(nil), // 3: keeper.LoginOut + (*RegisterIn)(nil), // 0: keeper.RegisterIn + (*RegisterOut)(nil), // 1: keeper.RegisterOut + (*LoginIn)(nil), // 2: keeper.LoginIn + (*LoginOut)(nil), // 3: keeper.LoginOut + (*RefreshTokensIn)(nil), // 4: keeper.RefreshTokensIn + (*RefreshTokensOut)(nil), // 5: keeper.RefreshTokensOut } var file_proto_keeper_proto_depIdxs = []int32{ 0, // 0: keeper.Keeper.Register:input_type -> keeper.RegisterIn 2, // 1: keeper.Keeper.Login:input_type -> keeper.LoginIn - 1, // 2: keeper.Keeper.Register:output_type -> keeper.RegisterOut - 3, // 3: keeper.Keeper.Login:output_type -> keeper.LoginOut - 2, // [2:4] is the sub-list for method output_type - 0, // [0:2] is the sub-list for method input_type + 4, // 2: keeper.Keeper.RefreshTokens:input_type -> keeper.RefreshTokensIn + 1, // 3: keeper.Keeper.Register:output_type -> keeper.RegisterOut + 3, // 4: keeper.Keeper.Login:output_type -> keeper.LoginOut + 5, // 5: keeper.Keeper.RefreshTokens:output_type -> keeper.RefreshTokensOut + 3, // [3:6] is the sub-list for method output_type + 0, // [0:3] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name @@ -306,7 +419,7 @@ func file_proto_keeper_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_keeper_proto_rawDesc), len(file_proto_keeper_proto_rawDesc)), NumEnums: 0, - NumMessages: 4, + NumMessages: 6, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/generated/rpc/keeper_grpc.pb.go b/internal/generated/rpc/keeper_grpc.pb.go index 403014b..8a556f1 100644 --- a/internal/generated/rpc/keeper_grpc.pb.go +++ b/internal/generated/rpc/keeper_grpc.pb.go @@ -19,8 +19,9 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - Keeper_Register_FullMethodName = "/keeper.Keeper/Register" - Keeper_Login_FullMethodName = "/keeper.Keeper/Login" + Keeper_Register_FullMethodName = "/keeper.Keeper/Register" + Keeper_Login_FullMethodName = "/keeper.Keeper/Login" + Keeper_RefreshTokens_FullMethodName = "/keeper.Keeper/RefreshTokens" ) // KeeperClient is the client API for Keeper service. @@ -29,6 +30,7 @@ const ( type KeeperClient interface { Register(ctx context.Context, in *RegisterIn, opts ...grpc.CallOption) (*RegisterOut, error) Login(ctx context.Context, in *LoginIn, opts ...grpc.CallOption) (*LoginOut, error) + RefreshTokens(ctx context.Context, in *RefreshTokensIn, opts ...grpc.CallOption) (*RefreshTokensOut, error) } type keeperClient struct { @@ -59,12 +61,23 @@ func (c *keeperClient) Login(ctx context.Context, in *LoginIn, opts ...grpc.Call return out, nil } +func (c *keeperClient) RefreshTokens(ctx context.Context, in *RefreshTokensIn, opts ...grpc.CallOption) (*RefreshTokensOut, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RefreshTokensOut) + err := c.cc.Invoke(ctx, Keeper_RefreshTokens_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // KeeperServer is the server API for Keeper service. // All implementations should embed UnimplementedKeeperServer // for forward compatibility. type KeeperServer interface { Register(context.Context, *RegisterIn) (*RegisterOut, error) Login(context.Context, *LoginIn) (*LoginOut, error) + RefreshTokens(context.Context, *RefreshTokensIn) (*RefreshTokensOut, error) } // UnimplementedKeeperServer should be embedded to have @@ -80,6 +93,9 @@ func (UnimplementedKeeperServer) Register(context.Context, *RegisterIn) (*Regist func (UnimplementedKeeperServer) Login(context.Context, *LoginIn) (*LoginOut, error) { return nil, status.Errorf(codes.Unimplemented, "method Login not implemented") } +func (UnimplementedKeeperServer) RefreshTokens(context.Context, *RefreshTokensIn) (*RefreshTokensOut, error) { + return nil, status.Errorf(codes.Unimplemented, "method RefreshTokens not implemented") +} func (UnimplementedKeeperServer) testEmbeddedByValue() {} // UnsafeKeeperServer may be embedded to opt out of forward compatibility for this service. @@ -136,6 +152,24 @@ func _Keeper_Login_Handler(srv interface{}, ctx context.Context, dec func(interf return interceptor(ctx, in, info, handler) } +func _Keeper_RefreshTokens_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RefreshTokensIn) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(KeeperServer).RefreshTokens(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Keeper_RefreshTokens_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(KeeperServer).RefreshTokens(ctx, req.(*RefreshTokensIn)) + } + return interceptor(ctx, in, info, handler) +} + // Keeper_ServiceDesc is the grpc.ServiceDesc for Keeper service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -151,6 +185,10 @@ var Keeper_ServiceDesc = grpc.ServiceDesc{ MethodName: "Login", Handler: _Keeper_Login_Handler, }, + { + MethodName: "RefreshTokens", + Handler: _Keeper_RefreshTokens_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "proto/keeper.proto", diff --git a/internal/infrastructure/auth/jwt/jwt.go b/internal/infrastructure/auth/jwt/jwt.go index 28827ab..afc5691 100644 --- a/internal/infrastructure/auth/jwt/jwt.go +++ b/internal/infrastructure/auth/jwt/jwt.go @@ -1,12 +1,18 @@ package jwt import ( + "errors" "fmt" "time" "github.com/golang-jwt/jwt/v4" ) +var ( + ErrInvalidToken = errors.New("invalid token") + ErrUnexpectedSigningMethod = errors.New("unexpected signing method") +) + type Claims struct { jwt.RegisteredClaims } @@ -25,6 +31,30 @@ func NewGenerator(secretKey string, accessTokenExp, refreshTokenExp time.Duratio } } +func (g *Generator) GetUserGUID(tokenString string) (string, error) { + c := &Claims{} + + token, err := jwt.ParseWithClaims(tokenString, c, func(t *jwt.Token) (interface{}, error) { + if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("%w: %v", ErrUnexpectedSigningMethod, t.Header["alg"]) + } + return []byte(g.secretKey), nil + }) + if err != nil { + var e *jwt.ValidationError + if errors.As(err, &e) { + return "", ErrInvalidToken + } + return "", err + } + + if !token.Valid || c.Issuer == "" { + return "", ErrInvalidToken + } + + return c.Issuer, nil +} + func (g *Generator) GenerateTokens(uuid string) (accessToken string, refreshToken string, err error) { const op = "GenerateTokens" diff --git a/internal/infrastructure/rpc/server/method.go b/internal/infrastructure/rpc/server/method.go index 0f5ad98..8dc85ab 100644 --- a/internal/infrastructure/rpc/server/method.go +++ b/internal/infrastructure/rpc/server/method.go @@ -10,8 +10,9 @@ import ( ) const ( - RegisterMethod = "Register" - LoginMethod = "Login" + RegisterMethod = "Register" + LoginMethod = "Login" + RefreshTokensMethod = "RefreshTokens" ) func (s Server) Register(ctx context.Context, in *pb.RegisterIn) (*pb.RegisterOut, error) { @@ -42,6 +43,20 @@ func (s Server) Login(ctx context.Context, in *pb.LoginIn) (*pb.LoginOut, error) return h(ctx, in) } +func (s Server) RefreshTokens(ctx context.Context, in *pb.RefreshTokensIn) (*pb.RefreshTokensOut, error) { + handler, err := s.getHandler(RefreshTokensMethod) + if err != nil { + return nil, err + } + + h, ok := handler.(func(context.Context, *pb.RefreshTokensIn) (*pb.RefreshTokensOut, error)) + if !ok { + return nil, status.Errorf(codes.Internal, "handler for %s method not found", RefreshTokensMethod) + } + + return h(ctx, in) +} + func (s Server) getHandler(name string) (any, error) { handler, ok := s.handlers[name] if !ok { diff --git a/internal/infrastructure/store/user/store.go b/internal/infrastructure/store/user/store.go index 12e6bd2..e2ddb2b 100644 --- a/internal/infrastructure/store/user/store.go +++ b/internal/infrastructure/store/user/store.go @@ -23,6 +23,26 @@ func NewStore(db *sqlx.DB) *Store { } } +func (s Store) GetByGUID(ctx context.Context, guid string) (*model.User, error) { + const op = "store.user.GetByGUID" + + query := ` + SELECT guid, email, password_hash, created_at, updated_at + FROM users + WHERE guid = $1 + ` + + var user row + if err := s.db.GetContext(ctx, &user, query, guid); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, ErrNotFound + } + return nil, fmt.Errorf("%s: %w", op, err) + } + + return user.convertToModel(), nil +} + func (s Store) GetByEmail(ctx context.Context, email string) (*model.User, error) { const op = "store.user.GetByEmail" diff --git a/internal/rpc/refresh_tokens/contract.go b/internal/rpc/refresh_tokens/contract.go new file mode 100644 index 0000000..0c0d56c --- /dev/null +++ b/internal/rpc/refresh_tokens/contract.go @@ -0,0 +1,11 @@ +package refresh_tokens + +import ( + "context" + + rt "github.com/bjlag/go-keeper/internal/usecase/user/refresh_tokens" +) + +type usecase interface { + Do(ctx context.Context, data rt.Data) (*rt.Result, error) +} diff --git a/internal/rpc/refresh_tokens/handler.go b/internal/rpc/refresh_tokens/handler.go new file mode 100644 index 0000000..3130ea8 --- /dev/null +++ b/internal/rpc/refresh_tokens/handler.go @@ -0,0 +1,49 @@ +package refresh_tokens + +import ( + "context" + "errors" + + "go.uber.org/zap" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + pb "github.com/bjlag/go-keeper/internal/generated/rpc" + "github.com/bjlag/go-keeper/internal/infrastructure/logger" + "github.com/bjlag/go-keeper/internal/usecase/user/refresh_tokens" +) + +const lenRefreshToken = 24 + +type Handler struct { + usecase usecase +} + +func New(usecase usecase) *Handler { + return &Handler{ + usecase: usecase, + } +} + +func (h *Handler) Handle(ctx context.Context, in *pb.RefreshTokensIn) (*pb.RefreshTokensOut, error) { + log := logger.FromCtx(ctx) + + if len(in.GetRefreshToken()) < lenRefreshToken { + return nil, status.Error(codes.InvalidArgument, "refresh token too short") + } + + result, err := h.usecase.Do(ctx, refresh_tokens.Data{RefreshToken: in.GetRefreshToken()}) + if err != nil { + if errors.Is(err, refresh_tokens.ErrInvalidRefreshToken) { + return nil, status.Error(codes.FailedPrecondition, "invalid refresh token") + } + + log.Error("Failed to refresh tokens", zap.Error(err)) + return nil, status.Error(codes.Internal, "internal server error") + } + + return &pb.RefreshTokensOut{ + AccessToken: result.AccessToken, + RefreshToken: result.RefreshToken, + }, nil +} diff --git a/internal/usecase/user/refresh_tokens/contract.go b/internal/usecase/user/refresh_tokens/contract.go new file mode 100644 index 0000000..cf2142e --- /dev/null +++ b/internal/usecase/user/refresh_tokens/contract.go @@ -0,0 +1,16 @@ +package refresh_tokens + +import ( + "context" + + model "github.com/bjlag/go-keeper/internal/domain/user" +) + +type userStore interface { + GetByGUID(ctx context.Context, guid string) (*model.User, error) +} + +type tokenGenerator interface { + GetUserGUID(tokenString string) (string, error) + GenerateTokens(guid string) (accessToken, refreshToken string, err error) +} diff --git a/internal/usecase/user/refresh_tokens/model.go b/internal/usecase/user/refresh_tokens/model.go new file mode 100644 index 0000000..bb45586 --- /dev/null +++ b/internal/usecase/user/refresh_tokens/model.go @@ -0,0 +1,10 @@ +package refresh_tokens + +type Data struct { + RefreshToken string +} + +type Result struct { + AccessToken string + RefreshToken string +} diff --git a/internal/usecase/user/refresh_tokens/usecase.go b/internal/usecase/user/refresh_tokens/usecase.go new file mode 100644 index 0000000..1c24a58 --- /dev/null +++ b/internal/usecase/user/refresh_tokens/usecase.go @@ -0,0 +1,58 @@ +package refresh_tokens + +import ( + "context" + "errors" + "fmt" + + "github.com/bjlag/go-keeper/internal/infrastructure/auth/jwt" + storeUser "github.com/bjlag/go-keeper/internal/infrastructure/store/user" +) + +var ( + ErrInvalidRefreshToken = errors.New("invalid refresh token") + ErrUserNotFound = errors.New("user not found") +) + +type Usecase struct { + userStore userStore + tokenGenerator tokenGenerator +} + +func NewUsecase(userStore userStore, tokenGenerator tokenGenerator) *Usecase { + return &Usecase{ + userStore: userStore, + tokenGenerator: tokenGenerator, + } +} + +func (u Usecase) Do(ctx context.Context, data Data) (*Result, error) { + const op = "usecase.refreshTokens.Do" + + guid, err := u.tokenGenerator.GetUserGUID(data.RefreshToken) + if err != nil { + if errors.Is(err, jwt.ErrInvalidToken) { + return nil, ErrInvalidRefreshToken + } + return nil, fmt.Errorf("%s: %w", op, err) + } + + user, err := u.userStore.GetByGUID(ctx, guid) + if err != nil { + if errors.Is(err, storeUser.ErrNotFound) { + return nil, ErrUserNotFound + } + + return nil, fmt.Errorf("%s: %w", op, err) + } + + accessToken, refreshToken, err := u.tokenGenerator.GenerateTokens(user.GUID.String()) + if err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + + return &Result{ + AccessToken: accessToken, + RefreshToken: refreshToken, + }, nil +} diff --git a/proto/keeper.proto b/proto/keeper.proto index 2f64c1c..128c764 100644 --- a/proto/keeper.proto +++ b/proto/keeper.proto @@ -7,6 +7,7 @@ option go_package = "internal/generated/rpc"; service Keeper { rpc Register(RegisterIn) returns (RegisterOut); rpc Login(LoginIn) returns (LoginOut); + rpc RefreshTokens(RefreshTokensIn) returns (RefreshTokensOut); } message RegisterIn { @@ -27,4 +28,13 @@ message LoginIn { message LoginOut { string access_token = 1; string refresh_token = 2; +} + +message RefreshTokensIn { + string refresh_token = 1; +} + +message RefreshTokensOut { + string access_token = 1; + string refresh_token = 2; } \ No newline at end of file From 57764377ab3cb9f99bd7cd101bb21227bc4913be Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Mon, 24 Feb 2025 19:03:20 +0300 Subject: [PATCH 11/94] client: move sever app --- cmd/server/main.go | 6 +++--- internal/app/{ => server}/app.go | 2 +- internal/app/{ => server}/config.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename internal/app/{ => server}/app.go (99%) rename internal/app/{ => server}/config.go (98%) diff --git a/cmd/server/main.go b/cmd/server/main.go index 6c0acb6..8b12026 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -10,7 +10,7 @@ import ( "github.com/ilyakaznacheev/cleanenv" "go.uber.org/zap" - "github.com/bjlag/go-keeper/internal/app" + "github.com/bjlag/go-keeper/internal/app/server" "github.com/bjlag/go-keeper/internal/infrastructure/logger" ) @@ -30,7 +30,7 @@ func main() { flag.StringVar(&configPath, "c", configPathDefault, "Path to config file") flag.Parse() - var cfg app.Config + var cfg server.Config if err := cleanenv.ReadConfig(configPath, &cfg); err != nil { panic(err) } @@ -45,7 +45,7 @@ func main() { ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) defer cancel() - err := app.NewApp(cfg, log).Run(ctx) + err := server.NewApp(cfg, log).Run(ctx) if err != nil { panic(err) } diff --git a/internal/app/app.go b/internal/app/server/app.go similarity index 99% rename from internal/app/app.go rename to internal/app/server/app.go index 44bc087..888db80 100644 --- a/internal/app/app.go +++ b/internal/app/server/app.go @@ -1,4 +1,4 @@ -package app +package server import ( "context" diff --git a/internal/app/config.go b/internal/app/server/config.go similarity index 98% rename from internal/app/config.go rename to internal/app/server/config.go index b3ab35e..c562dc4 100644 --- a/internal/app/config.go +++ b/internal/app/server/config.go @@ -1,4 +1,4 @@ -package app +package server import "time" From 47e7e59f8d9d2baf59e38d471d6349256c2afe8f Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Mon, 24 Feb 2025 20:11:59 +0300 Subject: [PATCH 12/94] client: login ui --- cmd/client/main.go | 48 +++++++ config/client.yaml.dist | 12 ++ config/server.yaml.dist | 5 + go.mod | 16 +++ go.sum | 37 +++++ internal/app/client/app.go | 20 +++ internal/app/client/config.go | 18 +++ internal/app/client/ui/command.go | 52 +++++++ internal/app/client/ui/forms/login/login.go | 146 ++++++++++++++++++++ internal/app/client/ui/key/key.go | 75 ++++++++++ internal/app/client/ui/model.go | 97 +++++++++++++ internal/app/client/ui/style/style.go | 39 ++++++ internal/app/client/ui/update.go | 59 ++++++++ internal/app/client/ui/view.go | 40 ++++++ internal/app/server/config.go | 4 +- 15 files changed, 666 insertions(+), 2 deletions(-) create mode 100644 config/client.yaml.dist create mode 100644 internal/app/client/app.go create mode 100644 internal/app/client/config.go create mode 100644 internal/app/client/ui/command.go create mode 100644 internal/app/client/ui/forms/login/login.go create mode 100644 internal/app/client/ui/key/key.go create mode 100644 internal/app/client/ui/model.go create mode 100644 internal/app/client/ui/style/style.go create mode 100644 internal/app/client/ui/update.go create mode 100644 internal/app/client/ui/view.go diff --git a/cmd/client/main.go b/cmd/client/main.go index da29a2c..27562f6 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -1,4 +1,52 @@ package main +import ( + "context" + "flag" + logNative "log" + "os/signal" + "syscall" + + "github.com/ilyakaznacheev/cleanenv" + "go.uber.org/zap" + + "github.com/bjlag/go-keeper/internal/app/client" + "github.com/bjlag/go-keeper/internal/app/server" + "github.com/bjlag/go-keeper/internal/infrastructure/logger" +) + +const ( + configPathDefault = "./config/client.yaml" +) + func main() { + defer func() { + if r := recover(); r != nil { + logNative.Fatalf("panic occurred: %v", r) + } + }() + + var configPath string + + flag.StringVar(&configPath, "c", configPathDefault, "Path to config file") + flag.Parse() + + var cfg server.Config + if err := cleanenv.ReadConfig(configPath, &cfg); err != nil { + panic(err) + } + + log := logger.Get(cfg.Env) + defer func() { + _ = log.Sync() + }() + + log.Debug("Config loaded", zap.Any("config", cfg)) + + ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) + defer cancel() + + if err := client.NewApp().Run(ctx); err != nil { + panic(err) + } } diff --git a/config/client.yaml.dist b/config/client.yaml.dist new file mode 100644 index 0000000..b88523e --- /dev/null +++ b/config/client.yaml.dist @@ -0,0 +1,12 @@ +env: dev + +server: + host: localhost + port: 8080 + +database: + host: localhost + port: 5444 + name: master + user: postgres + password: secret \ No newline at end of file diff --git a/config/server.yaml.dist b/config/server.yaml.dist index 9c21dd2..255972f 100644 --- a/config/server.yaml.dist +++ b/config/server.yaml.dist @@ -4,6 +4,11 @@ address: host: localhost port: 8080 +auth: + accessTokenExp: 5m + refreshTokenExp: 720h # 30 days + secretKey: secret + database: host: localhost port: 5444 diff --git a/go.mod b/go.mod index 0f257bb..3ae0234 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,9 @@ module github.com/bjlag/go-keeper go 1.23 require ( + github.com/charmbracelet/bubbles v0.20.0 + github.com/charmbracelet/bubbletea v1.3.3 + github.com/charmbracelet/lipgloss v1.0.0 github.com/golang-jwt/jwt/v4 v4.5.1 github.com/golang-migrate/migrate/v4 v4.18.2 github.com/google/uuid v1.6.0 @@ -20,7 +23,12 @@ require ( require ( github.com/BurntSushi/toml v1.4.0 // indirect + github.com/atotto/clipboard v0.1.4 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/charmbracelet/x/ansi v0.8.0 // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 // indirect @@ -28,7 +36,15 @@ require ( github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/joho/godotenv v1.5.1 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/termenv v0.16.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/net v0.33.0 // indirect diff --git a/go.sum b/go.sum index 4ec7ca2..4b9e15b 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,24 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg6 github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= +github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= +github.com/charmbracelet/bubbletea v1.3.3 h1:WpU6fCY0J2vDWM3zfS3vIDi/ULq3SYphZhkAGGvmEUY= +github.com/charmbracelet/bubbletea v1.3.3/go.mod h1:dtcUCyCGEX3g9tosuYiut3MXgY/Jsv9nKVdibKKRRXo= +github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg= +github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo= +github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE= +github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= 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= @@ -20,6 +36,8 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= @@ -65,6 +83,14 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= @@ -73,6 +99,12 @@ github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= +github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= @@ -81,6 +113,9 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -110,6 +145,8 @@ golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= diff --git a/internal/app/client/app.go b/internal/app/client/app.go new file mode 100644 index 0000000..4d19753 --- /dev/null +++ b/internal/app/client/app.go @@ -0,0 +1,20 @@ +package client + +import ( + "context" + + tea "github.com/charmbracelet/bubbletea" + + "github.com/bjlag/go-keeper/internal/app/client/ui" +) + +type App struct{} + +func NewApp() *App { + return &App{} +} + +func (a *App) Run(ctx context.Context) error { + _, err := tea.NewProgram(ui.InitModel(), tea.WithAltScreen()).Run() + return err +} diff --git a/internal/app/client/config.go b/internal/app/client/config.go new file mode 100644 index 0000000..374e865 --- /dev/null +++ b/internal/app/client/config.go @@ -0,0 +1,18 @@ +package client + +type Config struct { + Env string `yaml:"env" env:"ENV" env-default:"dev" env-description:"Environment" json:"env"` + + Server struct { + Host string `yaml:"host" env:"SERVER_HOST" env-description:"Server host" json:"host"` + Port int `yaml:"port" env:"SERVER_PORT" env-description:"Server port" json:"port"` + } `yaml:"address" json:"server"` + + Database struct { + Host string `yaml:"host" env:"DB_HOST" env-description:"Database host" json:"host"` + Port string `yaml:"port" env:"DB_PORT" env-description:"Database port" json:"port"` + Name string `yaml:"name" env:"DB_NAME" env-description:"Database name" json:"name"` + User string `yaml:"user" env:"DB_USER" env-description:"Database user" json:"user"` + Password string `yaml:"password" env:"DB_PASSWORD" env-description:"Database password" json:"password"` + } `yaml:"database" json:"database"` +} diff --git a/internal/app/client/ui/command.go b/internal/app/client/ui/command.go new file mode 100644 index 0000000..8796320 --- /dev/null +++ b/internal/app/client/ui/command.go @@ -0,0 +1,52 @@ +package ui + +import ( + "errors" + "github.com/bjlag/go-keeper/internal/app/client/ui/style" + + tea "github.com/charmbracelet/bubbletea" + + "github.com/bjlag/go-keeper/internal/infrastructure/validator" +) + +const ( + EmailInput = iota + PasswordInput +) + +type logonCmd struct { +} + +func submitLogin(m *Model) tea.Cmd { + return func() tea.Msg { + isValid := true + for i := range m.inputs { + switch i { + case EmailInput: + if !validator.ValidateEmail(m.inputs[i].Value()) { + m.inputs[i].PromptStyle = style.ErrorStyle + m.inputs[i].TextStyle = style.ErrorStyle + m.inputs[i].Err = errors.New("Неверный емейл") + isValid = false + } + case PasswordInput: + if m.inputs[i].Value() == "" { + m.inputs[i].PromptStyle = style.ErrorStyle + m.inputs[i].TextStyle = style.ErrorStyle + m.inputs[i].Err = errors.New("Не указан пароль") + isValid = false + } + } + } + + if !isValid { + return nil + } + + return logonCmd{} + } +} + +func (m logonCmd) Login() { + //fmt.Println("login") +} diff --git a/internal/app/client/ui/forms/login/login.go b/internal/app/client/ui/forms/login/login.go new file mode 100644 index 0000000..e56baa9 --- /dev/null +++ b/internal/app/client/ui/forms/login/login.go @@ -0,0 +1,146 @@ +package login + +import ( + "strings" + + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/textarea" + "github.com/charmbracelet/bubbles/textinput" + tea "github.com/charmbracelet/bubbletea" + + key2 "github.com/bjlag/go-keeper/internal/app/client/ui/key" + "github.com/bjlag/go-keeper/internal/app/client/ui/style" + "github.com/bjlag/go-keeper/internal/infrastructure/validator" +) + +type Form struct { + help help.Model + header string + email textinput.Model + password textinput.Model +} + +func NewLoginForm() *Form { + f := &Form{ + help: help.New(), + header: "Авторизация", + email: textinput.New(), + password: textinput.New(), + } + + f.help.ShowAll = true + + f.email.Cursor.Style = style.CursorStyle + f.email.PlaceholderStyle = style.BlurredStyle + f.email.CharLimit = 20 + f.email.Placeholder = "Email" + f.email.TextStyle = style.FocusedStyle + f.email.PromptStyle = style.FocusedStyle + f.email.Focus() + + f.password.Cursor.Style = style.CursorStyle + f.password.PlaceholderStyle = style.BlurredStyle + f.password.CharLimit = 60 + f.password.Placeholder = "Password" + f.password.EchoMode = textinput.EchoPassword + f.password.EchoCharacter = '•' + + return f +} + +func (f *Form) Init() tea.Cmd { + return nil +} + +func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch { + case key.Matches(msg, key2.Keys.Quit): + return f, tea.Quit + case key.Matches(msg, key2.Keys.Up, key2.Keys.Down): + if f.email.Focused() { + f.email.Blur() + f.password.Focus() + + f.email.PromptStyle = style.NoStyle + f.email.TextStyle = style.NoStyle + + f.password.PromptStyle = style.FocusedStyle + f.password.TextStyle = style.FocusedStyle + + return f, textarea.Blink + } + + if f.password.Focused() { + f.password.Blur() + f.email.Focus() + + f.password.PromptStyle = style.NoStyle + f.password.TextStyle = style.NoStyle + + f.email.PromptStyle = style.FocusedStyle + f.email.TextStyle = style.FocusedStyle + } + + return f, textarea.Blink + case key.Matches(msg, key2.Keys.Enter): + + if !validator.ValidateEmail(f.email.Value()) { + f.email.PromptStyle = style.ErrorStyle + f.email.TextStyle = style.ErrorStyle + } + + if f.password.Value() == "" { + f.password.PromptStyle = style.ErrorStyle + f.password.TextStyle = style.ErrorStyle + } + + return f, textarea.Blink + } + } + + return f, f.updateInputs(msg) +} + +func (f *Form) View() string { + var b strings.Builder + + b.WriteString(style.TitleStyle.Render(f.header)) + b.WriteRune('\n') + + b.WriteString(f.email.View()) + b.WriteRune('\n') + + if f.email.Err != nil { + b.WriteString(style.ErrorStyle. + MarginLeft(2). //nolint:mnd + Render(f.email.Err.Error())) + b.WriteRune('\n') + } + + b.WriteString(f.password.View()) + b.WriteRune('\n') + + if f.password.Err != nil { + b.WriteString(style.ErrorStyle. + MarginLeft(2). //nolint:mnd + Render(f.password.Err.Error())) + b.WriteRune('\n') + } + + b.WriteRune('\n') + b.WriteString(f.help.View(key2.Keys)) + + return b.String() +} + +func (f *Form) updateInputs(msg tea.Msg) tea.Cmd { + cmds := make([]tea.Cmd, 2) + + f.email, cmds[0] = f.email.Update(msg) + f.password, cmds[1] = f.password.Update(msg) + + return tea.Batch(cmds...) +} diff --git a/internal/app/client/ui/key/key.go b/internal/app/client/ui/key/key.go new file mode 100644 index 0000000..d89fc89 --- /dev/null +++ b/internal/app/client/ui/key/key.go @@ -0,0 +1,75 @@ +package key + +import "github.com/charmbracelet/bubbles/key" + +type Map struct { + New key.Binding + Edit key.Binding + Delete key.Binding + Up key.Binding + Down key.Binding + Right key.Binding + Left key.Binding + Enter key.Binding + Help key.Binding + Quit key.Binding + Back key.Binding +} + +func (k Map) ShortHelp() []key.Binding { + return []key.Binding{k.Help, k.Quit} +} + +func (k Map) FullHelp() [][]key.Binding { + return [][]key.Binding{ + {k.Up, k.Down, k.Enter}, + {k.Help, k.Quit}, + } +} + +var Keys = Map{ + New: key.NewBinding( + key.WithKeys("n"), + key.WithHelp("n", "new"), + ), + Edit: key.NewBinding( + key.WithKeys("e"), + key.WithHelp("e", "edit"), + ), + Delete: key.NewBinding( + key.WithKeys("d"), + key.WithHelp("d", "delete"), + ), + Up: key.NewBinding( + key.WithKeys("up"), + key.WithHelp("↑", "move up"), + ), + Down: key.NewBinding( + key.WithKeys("down"), + key.WithHelp("↓", "move down"), + ), + Right: key.NewBinding( + key.WithKeys("right", "l"), + key.WithHelp("→/l", "move right"), + ), + Left: key.NewBinding( + key.WithKeys("left", "h"), + key.WithHelp("←/l", "move left"), + ), + Enter: key.NewBinding( + key.WithKeys("enter"), + key.WithHelp("enter", "submit"), + ), + Help: key.NewBinding( + key.WithKeys("?"), + key.WithHelp("?", "toggle help"), + ), + Quit: key.NewBinding( + key.WithKeys("q", "ctrl+c"), + key.WithHelp("q/ctrl+c", "quit"), + ), + Back: key.NewBinding( + key.WithKeys("esc"), + key.WithHelp("esc", "back"), + ), +} diff --git a/internal/app/client/ui/model.go b/internal/app/client/ui/model.go new file mode 100644 index 0000000..cb411ef --- /dev/null +++ b/internal/app/client/ui/model.go @@ -0,0 +1,97 @@ +package ui + +import ( + "github.com/bjlag/go-keeper/internal/app/client/ui/forms/login" + key2 "github.com/bjlag/go-keeper/internal/app/client/ui/key" + "github.com/bjlag/go-keeper/internal/app/client/ui/style" + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/textinput" + tea "github.com/charmbracelet/bubbletea" +) + +const initCountInputs = 2 + +type state int + +const ( + loginState state = iota + listCategories + listPasswords + viewPassword +) + +type Model struct { + help help.Model + state state + inputs []textinput.Model + focusIndex int +} + +func InitModel() *Model { + m := &Model{ + help: help.New(), + state: loginState, + inputs: make([]textinput.Model, initCountInputs), + } + + for i := range m.inputs { + t := textinput.New() + t.Cursor.Style = style.CursorStyle + t.PlaceholderStyle = style.BlurredStyle + + switch i { + case 0: + t.CharLimit = 20 + t.Focus() + t.Placeholder = "Email" + t.TextStyle = style.FocusedStyle + t.PromptStyle = style.FocusedStyle + case 1: + t.CharLimit = 60 + t.Placeholder = "Password" + t.EchoMode = textinput.EchoPassword + t.EchoCharacter = '•' + } + + m.inputs[i] = t + } + + return m +} + +func (m *Model) Init() tea.Cmd { + return tea.Batch( + textinput.Blink, + func() tea.Msg { + return login.NewLoginForm() + }, + ) +} + +func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch { + case key.Matches(msg, key2.Keys.Quit): + return m, tea.Quit + } + case *login.Form: + return msg.Update(msg) + } + + switch m.state { + case loginState: + //return m.updateLogin(msg) + } + + return m, nil +} + +func (m *Model) View() string { + if m.state == loginState { + + } + + return "" +} diff --git a/internal/app/client/ui/style/style.go b/internal/app/client/ui/style/style.go new file mode 100644 index 0000000..411dba3 --- /dev/null +++ b/internal/app/client/ui/style/style.go @@ -0,0 +1,39 @@ +package style + +import ( + "fmt" + + "github.com/charmbracelet/lipgloss" +) + +var ( + TitleStyle = lipgloss.NewStyle(). + Bold(true). + BorderStyle(lipgloss.RoundedBorder()). + BorderBottom(true). + Foreground(lipgloss.Color("39")). + Margin(1, 0) + + NoStyle = lipgloss.NewStyle() + CursorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("252")) + FocusedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("39")) + BlurredStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("244")) + HelpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("240")) + + FocusedButton = FocusedStyle.Render("[ Submit ]") + BlurredButton = fmt.Sprintf("[ %s ]", BlurredStyle.Render("Submit")) + + SeparatorStyle = lipgloss.NewStyle(). + Margin(1, 0). + Foreground(lipgloss.Color("245")) + //BorderStyle(lipgloss.NormalBorder()).Margin(1, 0). + //BorderForeground(lipgloss.Color("63")). + //BorderBackground(lipgloss.Color("63")). + //BorderBottom(true) + + ErrorStyle = lipgloss.NewStyle(). + Foreground(lipgloss.Color("196")). + Bold(true). + MarginLeft(0). + PaddingLeft(0) +) diff --git a/internal/app/client/ui/update.go b/internal/app/client/ui/update.go new file mode 100644 index 0000000..65a523d --- /dev/null +++ b/internal/app/client/ui/update.go @@ -0,0 +1,59 @@ +package ui + +import ( + key2 "github.com/bjlag/go-keeper/internal/app/client/ui/key" + "github.com/bjlag/go-keeper/internal/app/client/ui/style" + "github.com/charmbracelet/bubbles/key" + tea "github.com/charmbracelet/bubbletea" +) + +func (m *Model) updateLogin(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch { + case key.Matches(msg, key2.Keys.Up, key2.Keys.Down): + s := msg.String() + + if s == "up" || s == "shift+tab" { + m.focusIndex-- + } else { + m.focusIndex++ + } + + if m.focusIndex > len(m.inputs) { + m.focusIndex = 0 + } else if m.focusIndex < 0 { + m.focusIndex = len(m.inputs) + } + + cmds := make([]tea.Cmd, len(m.inputs)) + for i := range m.inputs { + if i == m.focusIndex { + cmds[i] = m.inputs[i].Focus() + m.inputs[i].PromptStyle = style.FocusedStyle + m.inputs[i].TextStyle = style.FocusedStyle + continue + } + m.inputs[i].Blur() + m.inputs[i].PromptStyle = style.NoStyle + m.inputs[i].TextStyle = style.NoStyle + } + + return m, tea.Batch(cmds...) + + case key.Matches(msg, key2.Keys.Enter): + return m, submitLogin(m) + } + } + + return m, m.updateInputs(msg) +} + +func (m *Model) updateInputs(msg tea.Msg) tea.Cmd { + cmds := make([]tea.Cmd, len(m.inputs)) + for i := range m.inputs { + m.inputs[i], cmds[i] = m.inputs[i].Update(msg) + } + + return tea.Batch(cmds...) +} diff --git a/internal/app/client/ui/view.go b/internal/app/client/ui/view.go new file mode 100644 index 0000000..d0d3e15 --- /dev/null +++ b/internal/app/client/ui/view.go @@ -0,0 +1,40 @@ +package ui + +import ( + "github.com/bjlag/go-keeper/internal/app/client/ui/style" + "strings" +) + +func (m *Model) loginView() string { + var b strings.Builder + + b.WriteString(style.TitleStyle.Render("АВТОРИЗАЦИЯ")) + b.WriteRune('\n') + + help := "↑/↓: навигация • q: выход\n" + b.WriteString(style.HelpStyle.Render(help)) + b.WriteRune('\n') + + for i := range m.inputs { + b.WriteString(m.inputs[i].View()) + if m.inputs[i].Err != nil { + b.WriteRune('\n') + b.WriteString(style.ErrorStyle. + MarginLeft(2). //nolint:mnd + Render(m.inputs[i].Err.Error())) + } + b.WriteRune('\n') + } + + submitBtn := style.BlurredButton + if m.focusIndex == len(m.inputs) { + submitBtn = style.FocusedButton + } + + b.WriteRune('\n') + b.WriteString(submitBtn) + + //registerBtn := blurredButton + + return b.String() +} diff --git a/internal/app/server/config.go b/internal/app/server/config.go index c562dc4..ab2bd83 100644 --- a/internal/app/server/config.go +++ b/internal/app/server/config.go @@ -6,8 +6,8 @@ type Config struct { Env string `yaml:"env" env:"ENV" env-default:"dev" env-description:"Environment" json:"env"` Address struct { - Host string `yaml:"host" env:"ADDRESS_HOST" env-description:"Server address host" json:"host"` - Port int `yaml:"port" env:"ADDRESS_PORT" env-description:"Server address port" json:"port"` + Host string `yaml:"host" env:"ADDRESS_HOST" env-description:"Server host" json:"host"` + Port int `yaml:"port" env:"ADDRESS_PORT" env-description:"Server port" json:"port"` } `yaml:"address" json:"address"` Auth struct { From c1b0eb51add59255554ea8ac26fe7934157c5f68 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Thu, 27 Feb 2025 10:51:12 +0300 Subject: [PATCH 13/94] client: refactoring --- .golangci.yml | 11 +- internal/app/client/app.go | 4 +- internal/app/client/ui/command.go | 52 ---- internal/app/client/ui/forms/login/login.go | 146 ----------- internal/app/client/ui/key/key.go | 75 ------ internal/app/client/ui/model.go | 97 -------- internal/app/client/ui/style/style.go | 39 --- internal/app/client/ui/update.go | 59 ----- internal/app/client/ui/view.go | 40 --- internal/app/server/app.go | 6 +- internal/cli/common/error.go | 28 +++ internal/cli/common/key.go | 85 +++++++ internal/cli/common/style.go | 67 ++++++ internal/cli/element/button.go | 41 ++++ internal/cli/form/login/form.go | 227 ++++++++++++++++++ internal/cli/message/message.go | 6 + internal/cli/model.go | 67 ++++++ internal/rpc/login/contract.go | 2 +- internal/rpc/login/handler.go | 2 +- internal/rpc/refresh_tokens/contract.go | 2 +- internal/rpc/refresh_tokens/handler.go | 2 +- internal/rpc/register/contract.go | 2 +- internal/rpc/register/handler.go | 2 +- .../{ => server}/user/login/contract.go | 0 .../usecase/{ => server}/user/login/model.go | 0 .../{ => server}/user/login/usecase.go | 0 .../user/refresh_tokens/contract.go | 0 .../{ => server}/user/refresh_tokens/model.go | 0 .../user/refresh_tokens/usecase.go | 0 .../{ => server}/user/register/contract.go | 0 .../{ => server}/user/register/model.go | 0 .../{ => server}/user/register/usecase.go | 0 32 files changed, 542 insertions(+), 520 deletions(-) delete mode 100644 internal/app/client/ui/command.go delete mode 100644 internal/app/client/ui/forms/login/login.go delete mode 100644 internal/app/client/ui/key/key.go delete mode 100644 internal/app/client/ui/model.go delete mode 100644 internal/app/client/ui/style/style.go delete mode 100644 internal/app/client/ui/update.go delete mode 100644 internal/app/client/ui/view.go create mode 100644 internal/cli/common/error.go create mode 100644 internal/cli/common/key.go create mode 100644 internal/cli/common/style.go create mode 100644 internal/cli/element/button.go create mode 100644 internal/cli/form/login/form.go create mode 100644 internal/cli/message/message.go create mode 100644 internal/cli/model.go rename internal/usecase/{ => server}/user/login/contract.go (100%) rename internal/usecase/{ => server}/user/login/model.go (100%) rename internal/usecase/{ => server}/user/login/usecase.go (100%) rename internal/usecase/{ => server}/user/refresh_tokens/contract.go (100%) rename internal/usecase/{ => server}/user/refresh_tokens/model.go (100%) rename internal/usecase/{ => server}/user/refresh_tokens/usecase.go (100%) rename internal/usecase/{ => server}/user/register/contract.go (100%) rename internal/usecase/{ => server}/user/register/model.go (100%) rename internal/usecase/{ => server}/user/register/usecase.go (100%) diff --git a/.golangci.yml b/.golangci.yml index 60cbab5..5d4ab80 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -35,4 +35,13 @@ linters-settings: sections: - standard # Standard lib - default # External dependencies - - prefix(github.com/bjlag/go-keeper) # Internal packages \ No newline at end of file + - prefix(github.com/bjlag/go-keeper) # Internal packages + + ireturn: + allow: + - anon + - error + - empty + - stdlib + - github\.com\/charmbracelet\/bubbletea\.Model +# - github\.com\/charmbracelet\/bubbletea\.Cmd \ No newline at end of file diff --git a/internal/app/client/app.go b/internal/app/client/app.go index 4d19753..2e83260 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -5,7 +5,7 @@ import ( tea "github.com/charmbracelet/bubbletea" - "github.com/bjlag/go-keeper/internal/app/client/ui" + "github.com/bjlag/go-keeper/internal/cli" ) type App struct{} @@ -15,6 +15,6 @@ func NewApp() *App { } func (a *App) Run(ctx context.Context) error { - _, err := tea.NewProgram(ui.InitModel(), tea.WithAltScreen()).Run() + _, err := tea.NewProgram(cli.InitModel(), tea.WithAltScreen(), tea.WithContext(ctx)).Run() return err } diff --git a/internal/app/client/ui/command.go b/internal/app/client/ui/command.go deleted file mode 100644 index 8796320..0000000 --- a/internal/app/client/ui/command.go +++ /dev/null @@ -1,52 +0,0 @@ -package ui - -import ( - "errors" - "github.com/bjlag/go-keeper/internal/app/client/ui/style" - - tea "github.com/charmbracelet/bubbletea" - - "github.com/bjlag/go-keeper/internal/infrastructure/validator" -) - -const ( - EmailInput = iota - PasswordInput -) - -type logonCmd struct { -} - -func submitLogin(m *Model) tea.Cmd { - return func() tea.Msg { - isValid := true - for i := range m.inputs { - switch i { - case EmailInput: - if !validator.ValidateEmail(m.inputs[i].Value()) { - m.inputs[i].PromptStyle = style.ErrorStyle - m.inputs[i].TextStyle = style.ErrorStyle - m.inputs[i].Err = errors.New("Неверный емейл") - isValid = false - } - case PasswordInput: - if m.inputs[i].Value() == "" { - m.inputs[i].PromptStyle = style.ErrorStyle - m.inputs[i].TextStyle = style.ErrorStyle - m.inputs[i].Err = errors.New("Не указан пароль") - isValid = false - } - } - } - - if !isValid { - return nil - } - - return logonCmd{} - } -} - -func (m logonCmd) Login() { - //fmt.Println("login") -} diff --git a/internal/app/client/ui/forms/login/login.go b/internal/app/client/ui/forms/login/login.go deleted file mode 100644 index e56baa9..0000000 --- a/internal/app/client/ui/forms/login/login.go +++ /dev/null @@ -1,146 +0,0 @@ -package login - -import ( - "strings" - - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/textarea" - "github.com/charmbracelet/bubbles/textinput" - tea "github.com/charmbracelet/bubbletea" - - key2 "github.com/bjlag/go-keeper/internal/app/client/ui/key" - "github.com/bjlag/go-keeper/internal/app/client/ui/style" - "github.com/bjlag/go-keeper/internal/infrastructure/validator" -) - -type Form struct { - help help.Model - header string - email textinput.Model - password textinput.Model -} - -func NewLoginForm() *Form { - f := &Form{ - help: help.New(), - header: "Авторизация", - email: textinput.New(), - password: textinput.New(), - } - - f.help.ShowAll = true - - f.email.Cursor.Style = style.CursorStyle - f.email.PlaceholderStyle = style.BlurredStyle - f.email.CharLimit = 20 - f.email.Placeholder = "Email" - f.email.TextStyle = style.FocusedStyle - f.email.PromptStyle = style.FocusedStyle - f.email.Focus() - - f.password.Cursor.Style = style.CursorStyle - f.password.PlaceholderStyle = style.BlurredStyle - f.password.CharLimit = 60 - f.password.Placeholder = "Password" - f.password.EchoMode = textinput.EchoPassword - f.password.EchoCharacter = '•' - - return f -} - -func (f *Form) Init() tea.Cmd { - return nil -} - -func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg := msg.(type) { - case tea.KeyMsg: - switch { - case key.Matches(msg, key2.Keys.Quit): - return f, tea.Quit - case key.Matches(msg, key2.Keys.Up, key2.Keys.Down): - if f.email.Focused() { - f.email.Blur() - f.password.Focus() - - f.email.PromptStyle = style.NoStyle - f.email.TextStyle = style.NoStyle - - f.password.PromptStyle = style.FocusedStyle - f.password.TextStyle = style.FocusedStyle - - return f, textarea.Blink - } - - if f.password.Focused() { - f.password.Blur() - f.email.Focus() - - f.password.PromptStyle = style.NoStyle - f.password.TextStyle = style.NoStyle - - f.email.PromptStyle = style.FocusedStyle - f.email.TextStyle = style.FocusedStyle - } - - return f, textarea.Blink - case key.Matches(msg, key2.Keys.Enter): - - if !validator.ValidateEmail(f.email.Value()) { - f.email.PromptStyle = style.ErrorStyle - f.email.TextStyle = style.ErrorStyle - } - - if f.password.Value() == "" { - f.password.PromptStyle = style.ErrorStyle - f.password.TextStyle = style.ErrorStyle - } - - return f, textarea.Blink - } - } - - return f, f.updateInputs(msg) -} - -func (f *Form) View() string { - var b strings.Builder - - b.WriteString(style.TitleStyle.Render(f.header)) - b.WriteRune('\n') - - b.WriteString(f.email.View()) - b.WriteRune('\n') - - if f.email.Err != nil { - b.WriteString(style.ErrorStyle. - MarginLeft(2). //nolint:mnd - Render(f.email.Err.Error())) - b.WriteRune('\n') - } - - b.WriteString(f.password.View()) - b.WriteRune('\n') - - if f.password.Err != nil { - b.WriteString(style.ErrorStyle. - MarginLeft(2). //nolint:mnd - Render(f.password.Err.Error())) - b.WriteRune('\n') - } - - b.WriteRune('\n') - b.WriteString(f.help.View(key2.Keys)) - - return b.String() -} - -func (f *Form) updateInputs(msg tea.Msg) tea.Cmd { - cmds := make([]tea.Cmd, 2) - - f.email, cmds[0] = f.email.Update(msg) - f.password, cmds[1] = f.password.Update(msg) - - return tea.Batch(cmds...) -} diff --git a/internal/app/client/ui/key/key.go b/internal/app/client/ui/key/key.go deleted file mode 100644 index d89fc89..0000000 --- a/internal/app/client/ui/key/key.go +++ /dev/null @@ -1,75 +0,0 @@ -package key - -import "github.com/charmbracelet/bubbles/key" - -type Map struct { - New key.Binding - Edit key.Binding - Delete key.Binding - Up key.Binding - Down key.Binding - Right key.Binding - Left key.Binding - Enter key.Binding - Help key.Binding - Quit key.Binding - Back key.Binding -} - -func (k Map) ShortHelp() []key.Binding { - return []key.Binding{k.Help, k.Quit} -} - -func (k Map) FullHelp() [][]key.Binding { - return [][]key.Binding{ - {k.Up, k.Down, k.Enter}, - {k.Help, k.Quit}, - } -} - -var Keys = Map{ - New: key.NewBinding( - key.WithKeys("n"), - key.WithHelp("n", "new"), - ), - Edit: key.NewBinding( - key.WithKeys("e"), - key.WithHelp("e", "edit"), - ), - Delete: key.NewBinding( - key.WithKeys("d"), - key.WithHelp("d", "delete"), - ), - Up: key.NewBinding( - key.WithKeys("up"), - key.WithHelp("↑", "move up"), - ), - Down: key.NewBinding( - key.WithKeys("down"), - key.WithHelp("↓", "move down"), - ), - Right: key.NewBinding( - key.WithKeys("right", "l"), - key.WithHelp("→/l", "move right"), - ), - Left: key.NewBinding( - key.WithKeys("left", "h"), - key.WithHelp("←/l", "move left"), - ), - Enter: key.NewBinding( - key.WithKeys("enter"), - key.WithHelp("enter", "submit"), - ), - Help: key.NewBinding( - key.WithKeys("?"), - key.WithHelp("?", "toggle help"), - ), - Quit: key.NewBinding( - key.WithKeys("q", "ctrl+c"), - key.WithHelp("q/ctrl+c", "quit"), - ), - Back: key.NewBinding( - key.WithKeys("esc"), - key.WithHelp("esc", "back"), - ), -} diff --git a/internal/app/client/ui/model.go b/internal/app/client/ui/model.go deleted file mode 100644 index cb411ef..0000000 --- a/internal/app/client/ui/model.go +++ /dev/null @@ -1,97 +0,0 @@ -package ui - -import ( - "github.com/bjlag/go-keeper/internal/app/client/ui/forms/login" - key2 "github.com/bjlag/go-keeper/internal/app/client/ui/key" - "github.com/bjlag/go-keeper/internal/app/client/ui/style" - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/textinput" - tea "github.com/charmbracelet/bubbletea" -) - -const initCountInputs = 2 - -type state int - -const ( - loginState state = iota - listCategories - listPasswords - viewPassword -) - -type Model struct { - help help.Model - state state - inputs []textinput.Model - focusIndex int -} - -func InitModel() *Model { - m := &Model{ - help: help.New(), - state: loginState, - inputs: make([]textinput.Model, initCountInputs), - } - - for i := range m.inputs { - t := textinput.New() - t.Cursor.Style = style.CursorStyle - t.PlaceholderStyle = style.BlurredStyle - - switch i { - case 0: - t.CharLimit = 20 - t.Focus() - t.Placeholder = "Email" - t.TextStyle = style.FocusedStyle - t.PromptStyle = style.FocusedStyle - case 1: - t.CharLimit = 60 - t.Placeholder = "Password" - t.EchoMode = textinput.EchoPassword - t.EchoCharacter = '•' - } - - m.inputs[i] = t - } - - return m -} - -func (m *Model) Init() tea.Cmd { - return tea.Batch( - textinput.Blink, - func() tea.Msg { - return login.NewLoginForm() - }, - ) -} - -func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg := msg.(type) { - case tea.KeyMsg: - switch { - case key.Matches(msg, key2.Keys.Quit): - return m, tea.Quit - } - case *login.Form: - return msg.Update(msg) - } - - switch m.state { - case loginState: - //return m.updateLogin(msg) - } - - return m, nil -} - -func (m *Model) View() string { - if m.state == loginState { - - } - - return "" -} diff --git a/internal/app/client/ui/style/style.go b/internal/app/client/ui/style/style.go deleted file mode 100644 index 411dba3..0000000 --- a/internal/app/client/ui/style/style.go +++ /dev/null @@ -1,39 +0,0 @@ -package style - -import ( - "fmt" - - "github.com/charmbracelet/lipgloss" -) - -var ( - TitleStyle = lipgloss.NewStyle(). - Bold(true). - BorderStyle(lipgloss.RoundedBorder()). - BorderBottom(true). - Foreground(lipgloss.Color("39")). - Margin(1, 0) - - NoStyle = lipgloss.NewStyle() - CursorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("252")) - FocusedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("39")) - BlurredStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("244")) - HelpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("240")) - - FocusedButton = FocusedStyle.Render("[ Submit ]") - BlurredButton = fmt.Sprintf("[ %s ]", BlurredStyle.Render("Submit")) - - SeparatorStyle = lipgloss.NewStyle(). - Margin(1, 0). - Foreground(lipgloss.Color("245")) - //BorderStyle(lipgloss.NormalBorder()).Margin(1, 0). - //BorderForeground(lipgloss.Color("63")). - //BorderBackground(lipgloss.Color("63")). - //BorderBottom(true) - - ErrorStyle = lipgloss.NewStyle(). - Foreground(lipgloss.Color("196")). - Bold(true). - MarginLeft(0). - PaddingLeft(0) -) diff --git a/internal/app/client/ui/update.go b/internal/app/client/ui/update.go deleted file mode 100644 index 65a523d..0000000 --- a/internal/app/client/ui/update.go +++ /dev/null @@ -1,59 +0,0 @@ -package ui - -import ( - key2 "github.com/bjlag/go-keeper/internal/app/client/ui/key" - "github.com/bjlag/go-keeper/internal/app/client/ui/style" - "github.com/charmbracelet/bubbles/key" - tea "github.com/charmbracelet/bubbletea" -) - -func (m *Model) updateLogin(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg := msg.(type) { - case tea.KeyMsg: - switch { - case key.Matches(msg, key2.Keys.Up, key2.Keys.Down): - s := msg.String() - - if s == "up" || s == "shift+tab" { - m.focusIndex-- - } else { - m.focusIndex++ - } - - if m.focusIndex > len(m.inputs) { - m.focusIndex = 0 - } else if m.focusIndex < 0 { - m.focusIndex = len(m.inputs) - } - - cmds := make([]tea.Cmd, len(m.inputs)) - for i := range m.inputs { - if i == m.focusIndex { - cmds[i] = m.inputs[i].Focus() - m.inputs[i].PromptStyle = style.FocusedStyle - m.inputs[i].TextStyle = style.FocusedStyle - continue - } - m.inputs[i].Blur() - m.inputs[i].PromptStyle = style.NoStyle - m.inputs[i].TextStyle = style.NoStyle - } - - return m, tea.Batch(cmds...) - - case key.Matches(msg, key2.Keys.Enter): - return m, submitLogin(m) - } - } - - return m, m.updateInputs(msg) -} - -func (m *Model) updateInputs(msg tea.Msg) tea.Cmd { - cmds := make([]tea.Cmd, len(m.inputs)) - for i := range m.inputs { - m.inputs[i], cmds[i] = m.inputs[i].Update(msg) - } - - return tea.Batch(cmds...) -} diff --git a/internal/app/client/ui/view.go b/internal/app/client/ui/view.go deleted file mode 100644 index d0d3e15..0000000 --- a/internal/app/client/ui/view.go +++ /dev/null @@ -1,40 +0,0 @@ -package ui - -import ( - "github.com/bjlag/go-keeper/internal/app/client/ui/style" - "strings" -) - -func (m *Model) loginView() string { - var b strings.Builder - - b.WriteString(style.TitleStyle.Render("АВТОРИЗАЦИЯ")) - b.WriteRune('\n') - - help := "↑/↓: навигация • q: выход\n" - b.WriteString(style.HelpStyle.Render(help)) - b.WriteRune('\n') - - for i := range m.inputs { - b.WriteString(m.inputs[i].View()) - if m.inputs[i].Err != nil { - b.WriteRune('\n') - b.WriteString(style.ErrorStyle. - MarginLeft(2). //nolint:mnd - Render(m.inputs[i].Err.Error())) - } - b.WriteRune('\n') - } - - submitBtn := style.BlurredButton - if m.focusIndex == len(m.inputs) { - submitBtn = style.FocusedButton - } - - b.WriteRune('\n') - b.WriteString(submitBtn) - - //registerBtn := blurredButton - - return b.String() -} diff --git a/internal/app/server/app.go b/internal/app/server/app.go index 888db80..bb4e105 100644 --- a/internal/app/server/app.go +++ b/internal/app/server/app.go @@ -13,9 +13,9 @@ import ( rpcLogin "github.com/bjlag/go-keeper/internal/rpc/login" rpcRefreshTokens "github.com/bjlag/go-keeper/internal/rpc/refresh_tokens" rpcRegister "github.com/bjlag/go-keeper/internal/rpc/register" - "github.com/bjlag/go-keeper/internal/usecase/user/login" - rt "github.com/bjlag/go-keeper/internal/usecase/user/refresh_tokens" - "github.com/bjlag/go-keeper/internal/usecase/user/register" + "github.com/bjlag/go-keeper/internal/usecase/server/user/login" + rt "github.com/bjlag/go-keeper/internal/usecase/server/user/refresh_tokens" + "github.com/bjlag/go-keeper/internal/usecase/server/user/register" ) type App struct { diff --git a/internal/cli/common/error.go b/internal/cli/common/error.go new file mode 100644 index 0000000..859c9da --- /dev/null +++ b/internal/cli/common/error.go @@ -0,0 +1,28 @@ +package common + +import ( + "errors" + "strings" +) + +var ErrInvalidElement = errors.New("invalid element") + +type ValidateError struct { + errors []string +} + +func NewValidateError() *ValidateError { + return &ValidateError{} +} + +func (e *ValidateError) AddError(msg string) { + e.errors = append(e.errors, msg) +} + +func (e *ValidateError) HasErrors() bool { + return len(e.errors) > 0 +} + +func (e *ValidateError) Error() string { + return strings.Join(e.errors, "\n") +} diff --git a/internal/cli/common/key.go b/internal/cli/common/key.go new file mode 100644 index 0000000..410f1ea --- /dev/null +++ b/internal/cli/common/key.go @@ -0,0 +1,85 @@ +package common + +import "github.com/charmbracelet/bubbles/key" + +type Map struct { + New key.Binding + Edit key.Binding + Delete key.Binding + Up key.Binding + Down key.Binding + Right key.Binding + Left key.Binding + Enter key.Binding + Help key.Binding + Quit key.Binding + Back key.Binding + Tab key.Binding + Navigation key.Binding +} + +func (k Map) ShortHelp() []key.Binding { + return []key.Binding{k.Navigation, k.Enter, k.Quit} +} + +func (k Map) FullHelp() [][]key.Binding { + return [][]key.Binding{ + {k.Navigation, k.Enter}, + {k.Help, k.Quit}, + } +} + +var Keys = Map{ + New: key.NewBinding( + key.WithKeys("n"), + key.WithHelp("n", "добавить"), + ), + Edit: key.NewBinding( + key.WithKeys("e"), + key.WithHelp("e", "редактировать"), + ), + Delete: key.NewBinding( + key.WithKeys("d"), + key.WithHelp("d", "удалить"), + ), + Navigation: key.NewBinding( + key.WithKeys("up", "down", "tab"), + key.WithHelp("↑/↓/tab", "navigation"), + ), + Up: key.NewBinding( + key.WithKeys("up"), + key.WithHelp("↑", "наверх"), + ), + Down: key.NewBinding( + key.WithKeys("down"), + key.WithHelp("↓", "вниз"), + ), + Tab: key.NewBinding( + key.WithKeys("tab"), + key.WithHelp("tab", "табуляция"), + ), + Right: key.NewBinding( + key.WithKeys("right", "l"), + key.WithHelp("→/l", "направо"), + ), + Left: key.NewBinding( + key.WithKeys("left", "h"), + key.WithHelp("←/l", "налево"), + ), + Enter: key.NewBinding( + key.WithKeys("enter"), + key.WithHelp("enter", "выполнить"), + ), + Help: key.NewBinding( + key.WithKeys("?"), + key.WithHelp("?", "help"), + ), + Quit: key.NewBinding( + key.WithKeys("ctrl+c"), + key.WithHelp("ctrl+c", "выход"), + ), + Back: key.NewBinding( + key.WithKeys("esc"), + key.WithHelp("esc", "назад"), + ), +} diff --git a/internal/cli/common/style.go b/internal/cli/common/style.go new file mode 100644 index 0000000..a8ad18d --- /dev/null +++ b/internal/cli/common/style.go @@ -0,0 +1,67 @@ +package common + +import ( + "github.com/bjlag/go-keeper/internal/cli/element" + "github.com/charmbracelet/bubbles/textinput" + "github.com/charmbracelet/lipgloss" +) + +var ( + TitleStyle = lipgloss.NewStyle(). + Bold(true). + BorderStyle(lipgloss.RoundedBorder()). + BorderBottom(true). + Foreground(lipgloss.Color("39")). + Margin(1, 0) + + NoStyle = lipgloss.NewStyle() + TextStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("252")) + CursorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("252")) + FocusedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("39")) + BlurredStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("244")) + ErrorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("196")) + + BlockStyle = TextStyle.Margin(1) + ErrorBlockStyle = lipgloss.NewStyle(). + BorderStyle(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("245")). + Padding(0, 1). + MarginTop(1). + Foreground(lipgloss.Color("196")) +) + +func SetNoStyle(input textinput.Model) textinput.Model { + input.PromptStyle = NoStyle + input.TextStyle = NoStyle + return input +} + +func SetFocusStyle(input textinput.Model) textinput.Model { + input.PromptStyle = FocusedStyle + input.TextStyle = FocusedStyle + return input +} + +func SetErrorStyle(input textinput.Model) textinput.Model { + input.PromptStyle = ErrorStyle + return input +} + +func CreateDefaultTextInput(placeholder string, limit int) textinput.Model { + m := textinput.New() + + m.Cursor.Style = CursorStyle + m.PlaceholderStyle = BlurredStyle + m.CharLimit = limit + m.Placeholder = placeholder + + return m +} + +func CreateDefaultButton(text string) element.Button { + b := element.NewButton(text) + b.FocusedStyle = FocusedStyle + b.BlurredStyle = BlurredStyle + + return b +} diff --git a/internal/cli/element/button.go b/internal/cli/element/button.go new file mode 100644 index 0000000..78d1433 --- /dev/null +++ b/internal/cli/element/button.go @@ -0,0 +1,41 @@ +package element + +import ( + "fmt" + + "github.com/charmbracelet/lipgloss" +) + +type Button struct { + text string + focus bool + FocusedStyle lipgloss.Style + BlurredStyle lipgloss.Style +} + +func NewButton(text string) Button { + return Button{ + text: text, + FocusedStyle: lipgloss.NewStyle(), + BlurredStyle: lipgloss.NewStyle(), + } +} + +func (b *Button) String() string { + if b.focus { + return fmt.Sprintf("[ %s ]", b.FocusedStyle.Render(b.text)) + } + return fmt.Sprintf("[ %s ]", b.BlurredStyle.Render(b.text)) +} + +func (b *Button) Focus() { + b.focus = true +} + +func (b *Button) Blur() { + b.focus = false +} + +func (b *Button) Focused() bool { + return b.focus +} diff --git a/internal/cli/form/login/form.go b/internal/cli/form/login/form.go new file mode 100644 index 0000000..285145b --- /dev/null +++ b/internal/cli/form/login/form.go @@ -0,0 +1,227 @@ +package login + +import ( + "errors" + "strings" + + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/textinput" + tea "github.com/charmbracelet/bubbletea" + + "github.com/bjlag/go-keeper/internal/cli/common" + "github.com/bjlag/go-keeper/internal/cli/element" + "github.com/bjlag/go-keeper/internal/cli/message" + "github.com/bjlag/go-keeper/internal/infrastructure/validator" +) + +const ( + posEmail int = iota + posPassword + posSubmitBtn + posRegisterBtn + posCloseBtn + + emailCharLimit = 20 + passwordCharLimit = 20 +) + +type Form struct { + main tea.Model + help help.Model + header string + elements []interface{} + pos int + err error +} + +func NewForm(main tea.Model) *Form { + f := &Form{ + main: main, + help: help.New(), + header: "Авторизация", + elements: []interface{}{ + posEmail: common.CreateDefaultTextInput("Email", emailCharLimit), + posPassword: common.CreateDefaultTextInput("Password", passwordCharLimit), + posSubmitBtn: common.CreateDefaultButton("Вход"), + posRegisterBtn: common.CreateDefaultButton("Регистрация"), + posCloseBtn: common.CreateDefaultButton("Закрыть"), + }, + } + + for i := range f.elements { + switch e := f.elements[i].(type) { + case textinput.Model: + if i == posEmail { + e.TextStyle = common.FocusedStyle + e.PromptStyle = common.FocusedStyle + e.Focus() + + f.elements[i] = e + continue + } + + e.EchoMode = textinput.EchoPassword + e.EchoCharacter = '•' + + f.elements[i] = e + } + } + + return f +} + +func (f *Form) Init() tea.Cmd { + return nil +} + +func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch { + case key.Matches(msg, common.Keys.Quit): + return f, tea.Quit + case key.Matches(msg, common.Keys.Navigation): + if key.Matches(msg, common.Keys.Down, common.Keys.Tab) { + f.pos++ + } else { + f.pos-- + } + + if f.pos > len(f.elements)-1 { + f.pos = 0 + } else if f.pos < 0 { + f.pos = len(f.elements) - 1 + } + + for i := range f.elements { + switch e := f.elements[i].(type) { + case textinput.Model: + if i == f.pos { + e.Focus() + f.elements[i] = common.SetFocusStyle(e) + continue + } + + e.Blur() + f.elements[i] = common.SetNoStyle(e) + case element.Button: + if i == f.pos { + e.Focus() + f.elements[i] = e + continue + } + e.Blur() + f.elements[i] = e + } + } + + return f, nil + case key.Matches(msg, common.Keys.Enter): + f.err = nil + + switch { + case f.pos == posSubmitBtn || f.pos == posEmail || f.pos == posPassword: + return f.submit() + case f.pos == posRegisterBtn: + case f.pos == posCloseBtn: + return f, tea.Quit + } + + return f, nil + } + } + + return f, f.updateInputs(msg) +} + +func (f *Form) View() string { + var b strings.Builder + + b.WriteString(common.TitleStyle.Render(f.header)) + b.WriteRune('\n') + + for i := range f.elements { + if e, ok := f.elements[i].(textinput.Model); ok { + b.WriteString(e.View()) + b.WriteRune('\n') + } + } + + b.WriteRune('\n') + + for i := range f.elements { + if e, ok := f.elements[i].(element.Button); ok { + b.WriteString(e.String()) + b.WriteRune('\n') + } + } + + var errValidate *common.ValidateError + + // выводим ошибки валидации + if f.err != nil && errors.As(f.err, &errValidate) { + b.WriteString(common.ErrorBlockStyle.Render(f.err.Error())) + b.WriteRune('\n') + } + + b.WriteRune('\n') + b.WriteString(f.help.View(common.Keys)) + + // выводим прочие ошибки + if f.err != nil && !errors.As(f.err, &errValidate) { + b.WriteRune('\n') + b.WriteString(common.ErrorBlockStyle.Render(f.err.Error())) + } + + return b.String() +} + +func (f *Form) submit() (tea.Model, tea.Cmd) { + errValidate := common.NewValidateError() + + email, ok := f.elements[posEmail].(textinput.Model) + if !ok { + f.err = common.ErrInvalidElement + return f, nil + } + password, ok := f.elements[posPassword].(textinput.Model) + if !ok { + f.err = common.ErrInvalidElement + return f, nil + } + + if !validator.ValidateEmail(email.Value()) { + f.elements[posEmail] = common.SetErrorStyle(email) + errValidate.AddError("Неверно заполнен email") + } + + if password.Value() == "" { + f.elements[posPassword] = common.SetErrorStyle(password) + errValidate.AddError("Не заполнен пароль") + } + + if errValidate.HasErrors() { + f.err = errValidate + return f, nil + } + + // TODO: выполняем авторизацию + + return f.main.Update(message.LoginSuccessMessage{ + AccessToken: "xxxx", + RefreshToken: "yyyy", + }) +} + +func (f *Form) updateInputs(msg tea.Msg) tea.Cmd { + cmds := make([]tea.Cmd, len(f.elements)) + + for i := range f.elements { + if m, ok := f.elements[i].(textinput.Model); ok { + f.elements[i], cmds[i] = m.Update(msg) + } + } + + return tea.Batch(cmds...) +} diff --git a/internal/cli/message/message.go b/internal/cli/message/message.go new file mode 100644 index 0000000..aed3744 --- /dev/null +++ b/internal/cli/message/message.go @@ -0,0 +1,6 @@ +package message + +type LoginSuccessMessage struct { + AccessToken string + RefreshToken string +} diff --git a/internal/cli/model.go b/internal/cli/model.go new file mode 100644 index 0000000..8da6f01 --- /dev/null +++ b/internal/cli/model.go @@ -0,0 +1,67 @@ +package cli + +import ( + "strings" + + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" + tea "github.com/charmbracelet/bubbletea" + + "github.com/bjlag/go-keeper/internal/cli/common" + "github.com/bjlag/go-keeper/internal/cli/form/login" + "github.com/bjlag/go-keeper/internal/cli/message" +) + +type state int + +type MainModel struct { + help help.Model + header string + state state + accessToken string + refreshToken string +} + +func InitModel() *MainModel { + return &MainModel{ + help: help.New(), + header: "Go Keeper", + } +} + +func (m *MainModel) Init() tea.Cmd { + return tea.Batch( + func() tea.Msg { + return login.NewForm(m) + }, + ) +} + +func (m *MainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch { + case key.Matches(msg, common.Keys.Quit): + return m, tea.Quit + } + case *login.Form: + return msg.Update(msg) + case message.LoginSuccessMessage: + m.accessToken = msg.AccessToken + m.refreshToken = msg.RefreshToken + } + + return m, nil +} + +func (m *MainModel) View() string { + var b strings.Builder + + b.WriteString(common.TitleStyle.Render(m.header)) + b.WriteRune('\n') + + b.WriteString(common.TextStyle.Render("Access token:", m.accessToken, "\n\n")) + b.WriteString(common.TextStyle.Render("Refresh token:", m.accessToken, "\n\n")) + + return b.String() +} diff --git a/internal/rpc/login/contract.go b/internal/rpc/login/contract.go index 9b207a0..11af932 100644 --- a/internal/rpc/login/contract.go +++ b/internal/rpc/login/contract.go @@ -3,7 +3,7 @@ package login import ( "context" - "github.com/bjlag/go-keeper/internal/usecase/user/login" + "github.com/bjlag/go-keeper/internal/usecase/server/user/login" ) type usecase interface { diff --git a/internal/rpc/login/handler.go b/internal/rpc/login/handler.go index 819f3ae..dec416d 100644 --- a/internal/rpc/login/handler.go +++ b/internal/rpc/login/handler.go @@ -11,7 +11,7 @@ import ( pb "github.com/bjlag/go-keeper/internal/generated/rpc" "github.com/bjlag/go-keeper/internal/infrastructure/logger" "github.com/bjlag/go-keeper/internal/infrastructure/validator" - "github.com/bjlag/go-keeper/internal/usecase/user/login" + "github.com/bjlag/go-keeper/internal/usecase/server/user/login" ) type Handler struct { diff --git a/internal/rpc/refresh_tokens/contract.go b/internal/rpc/refresh_tokens/contract.go index 0c0d56c..ca372ea 100644 --- a/internal/rpc/refresh_tokens/contract.go +++ b/internal/rpc/refresh_tokens/contract.go @@ -3,7 +3,7 @@ package refresh_tokens import ( "context" - rt "github.com/bjlag/go-keeper/internal/usecase/user/refresh_tokens" + rt "github.com/bjlag/go-keeper/internal/usecase/server/user/refresh_tokens" ) type usecase interface { diff --git a/internal/rpc/refresh_tokens/handler.go b/internal/rpc/refresh_tokens/handler.go index 3130ea8..8c199e3 100644 --- a/internal/rpc/refresh_tokens/handler.go +++ b/internal/rpc/refresh_tokens/handler.go @@ -10,7 +10,7 @@ import ( pb "github.com/bjlag/go-keeper/internal/generated/rpc" "github.com/bjlag/go-keeper/internal/infrastructure/logger" - "github.com/bjlag/go-keeper/internal/usecase/user/refresh_tokens" + "github.com/bjlag/go-keeper/internal/usecase/server/user/refresh_tokens" ) const lenRefreshToken = 24 diff --git a/internal/rpc/register/contract.go b/internal/rpc/register/contract.go index ceb0e8d..0fc6b5e 100644 --- a/internal/rpc/register/contract.go +++ b/internal/rpc/register/contract.go @@ -3,7 +3,7 @@ package register import ( "context" - "github.com/bjlag/go-keeper/internal/usecase/user/register" + "github.com/bjlag/go-keeper/internal/usecase/server/user/register" ) type usecase interface { diff --git a/internal/rpc/register/handler.go b/internal/rpc/register/handler.go index 796853b..e9ea823 100644 --- a/internal/rpc/register/handler.go +++ b/internal/rpc/register/handler.go @@ -11,7 +11,7 @@ import ( pb "github.com/bjlag/go-keeper/internal/generated/rpc" "github.com/bjlag/go-keeper/internal/infrastructure/logger" "github.com/bjlag/go-keeper/internal/infrastructure/validator" - "github.com/bjlag/go-keeper/internal/usecase/user/register" + "github.com/bjlag/go-keeper/internal/usecase/server/user/register" ) type Handler struct { diff --git a/internal/usecase/user/login/contract.go b/internal/usecase/server/user/login/contract.go similarity index 100% rename from internal/usecase/user/login/contract.go rename to internal/usecase/server/user/login/contract.go diff --git a/internal/usecase/user/login/model.go b/internal/usecase/server/user/login/model.go similarity index 100% rename from internal/usecase/user/login/model.go rename to internal/usecase/server/user/login/model.go diff --git a/internal/usecase/user/login/usecase.go b/internal/usecase/server/user/login/usecase.go similarity index 100% rename from internal/usecase/user/login/usecase.go rename to internal/usecase/server/user/login/usecase.go diff --git a/internal/usecase/user/refresh_tokens/contract.go b/internal/usecase/server/user/refresh_tokens/contract.go similarity index 100% rename from internal/usecase/user/refresh_tokens/contract.go rename to internal/usecase/server/user/refresh_tokens/contract.go diff --git a/internal/usecase/user/refresh_tokens/model.go b/internal/usecase/server/user/refresh_tokens/model.go similarity index 100% rename from internal/usecase/user/refresh_tokens/model.go rename to internal/usecase/server/user/refresh_tokens/model.go diff --git a/internal/usecase/user/refresh_tokens/usecase.go b/internal/usecase/server/user/refresh_tokens/usecase.go similarity index 100% rename from internal/usecase/user/refresh_tokens/usecase.go rename to internal/usecase/server/user/refresh_tokens/usecase.go diff --git a/internal/usecase/user/register/contract.go b/internal/usecase/server/user/register/contract.go similarity index 100% rename from internal/usecase/user/register/contract.go rename to internal/usecase/server/user/register/contract.go diff --git a/internal/usecase/user/register/model.go b/internal/usecase/server/user/register/model.go similarity index 100% rename from internal/usecase/user/register/model.go rename to internal/usecase/server/user/register/model.go diff --git a/internal/usecase/user/register/usecase.go b/internal/usecase/server/user/register/usecase.go similarity index 100% rename from internal/usecase/user/register/usecase.go rename to internal/usecase/server/user/register/usecase.go From 236cc3f9b55b60150bc8d3e4d58b50a7301b5d97 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Thu, 27 Feb 2025 19:27:08 +0300 Subject: [PATCH 14/94] client: registration form --- internal/cli/form/login/form.go | 2 + internal/cli/form/register/form.go | 226 +++++++++++++++++++++++++++++ internal/cli/message/message.go | 5 + internal/cli/model.go | 11 +- 4 files changed, 239 insertions(+), 5 deletions(-) create mode 100644 internal/cli/form/register/form.go diff --git a/internal/cli/form/login/form.go b/internal/cli/form/login/form.go index 285145b..f7053ee 100644 --- a/internal/cli/form/login/form.go +++ b/internal/cli/form/login/form.go @@ -11,6 +11,7 @@ import ( "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/element" + "github.com/bjlag/go-keeper/internal/cli/form/register" "github.com/bjlag/go-keeper/internal/cli/message" "github.com/bjlag/go-keeper/internal/infrastructure/validator" ) @@ -124,6 +125,7 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case f.pos == posSubmitBtn || f.pos == posEmail || f.pos == posPassword: return f.submit() case f.pos == posRegisterBtn: + return register.NewForm(f.main, f).Update(tea.ClearScreen) case f.pos == posCloseBtn: return f, tea.Quit } diff --git a/internal/cli/form/register/form.go b/internal/cli/form/register/form.go new file mode 100644 index 0000000..271d34f --- /dev/null +++ b/internal/cli/form/register/form.go @@ -0,0 +1,226 @@ +package register + +import ( + "errors" + "strings" + + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/textinput" + tea "github.com/charmbracelet/bubbletea" + + "github.com/bjlag/go-keeper/internal/cli/common" + "github.com/bjlag/go-keeper/internal/cli/element" + "github.com/bjlag/go-keeper/internal/cli/message" + "github.com/bjlag/go-keeper/internal/infrastructure/validator" +) + +const ( + posEmail int = iota + posPassword + posSubmitBtn + posBackBtn + + emailCharLimit = 20 + passwordCharLimit = 20 +) + +type Form struct { + main tea.Model + prevModel tea.Model + help help.Model + header string + elements []interface{} + pos int + err error +} + +func NewForm(main, prevModel tea.Model) *Form { + f := &Form{ + main: main, + prevModel: prevModel, + help: help.New(), + header: "Регистрация", + elements: []interface{}{ + posEmail: common.CreateDefaultTextInput("Email", emailCharLimit), + posPassword: common.CreateDefaultTextInput("Password", passwordCharLimit), + posSubmitBtn: common.CreateDefaultButton("Регистрация"), + posBackBtn: common.CreateDefaultButton("Назад"), + }, + } + + for i := range f.elements { + switch e := f.elements[i].(type) { + case textinput.Model: + if i == posEmail { + e.TextStyle = common.FocusedStyle + e.PromptStyle = common.FocusedStyle + e.Focus() + + f.elements[i] = e + continue + } + + e.EchoMode = textinput.EchoPassword + e.EchoCharacter = '•' + + f.elements[i] = e + } + } + + return f +} + +func (f *Form) Init() tea.Cmd { + return nil +} + +func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch { + case key.Matches(msg, common.Keys.Quit): + return f, tea.Quit + case key.Matches(msg, common.Keys.Navigation): + if key.Matches(msg, common.Keys.Down, common.Keys.Tab) { + f.pos++ + } else { + f.pos-- + } + + if f.pos > len(f.elements)-1 { + f.pos = 0 + } else if f.pos < 0 { + f.pos = len(f.elements) - 1 + } + + for i := range f.elements { + switch e := f.elements[i].(type) { + case textinput.Model: + if i == f.pos { + e.Focus() + f.elements[i] = common.SetFocusStyle(e) + continue + } + + e.Blur() + f.elements[i] = common.SetNoStyle(e) + case element.Button: + if i == f.pos { + e.Focus() + f.elements[i] = e + continue + } + e.Blur() + f.elements[i] = e + } + } + + return f, nil + case key.Matches(msg, common.Keys.Enter): + f.err = nil + + switch { + case f.pos == posSubmitBtn || f.pos == posEmail || f.pos == posPassword: + return f.submit() + case f.pos == posBackBtn: + return f.prevModel.Update(tea.ClearScreen) + } + + return f, nil + } + } + + return f, f.updateInputs(msg) +} + +func (f *Form) View() string { + var b strings.Builder + + b.WriteString(common.TitleStyle.Render(f.header)) + b.WriteRune('\n') + + for i := range f.elements { + if e, ok := f.elements[i].(textinput.Model); ok { + b.WriteString(e.View()) + b.WriteRune('\n') + } + } + + b.WriteRune('\n') + + for i := range f.elements { + if e, ok := f.elements[i].(element.Button); ok { + b.WriteString(e.String()) + b.WriteRune('\n') + } + } + + var errValidate *common.ValidateError + + // выводим ошибки валидации + if f.err != nil && errors.As(f.err, &errValidate) { + b.WriteString(common.ErrorBlockStyle.Render(f.err.Error())) + b.WriteRune('\n') + } + + b.WriteRune('\n') + b.WriteString(f.help.View(common.Keys)) + + // выводим прочие ошибки + if f.err != nil && !errors.As(f.err, &errValidate) { + b.WriteRune('\n') + b.WriteString(common.ErrorBlockStyle.Render(f.err.Error())) + } + + return b.String() +} + +func (f *Form) submit() (tea.Model, tea.Cmd) { + errValidate := common.NewValidateError() + + email, ok := f.elements[posEmail].(textinput.Model) + if !ok { + f.err = common.ErrInvalidElement + return f, nil + } + password, ok := f.elements[posPassword].(textinput.Model) + if !ok { + f.err = common.ErrInvalidElement + return f, nil + } + + if !validator.ValidateEmail(email.Value()) { + f.elements[posEmail] = common.SetErrorStyle(email) + errValidate.AddError("Неправильный email") + } + + if !validator.ValidatePassword(password.Value()) { + f.elements[posPassword] = common.SetErrorStyle(password) + errValidate.AddError("Недостаточно сложный пароль") + } + + if errValidate.HasErrors() { + f.err = errValidate + return f, nil + } + + // TODO: выполняем регистрацию + + return f.main.Update(message.RegisterSuccessMessage{ + AccessToken: "xxxx", + RefreshToken: "yyyy", + }) +} + +func (f *Form) updateInputs(msg tea.Msg) tea.Cmd { + cmds := make([]tea.Cmd, len(f.elements)) + + for i := range f.elements { + if m, ok := f.elements[i].(textinput.Model); ok { + f.elements[i], cmds[i] = m.Update(msg) + } + } + + return tea.Batch(cmds...) +} diff --git a/internal/cli/message/message.go b/internal/cli/message/message.go index aed3744..e865f58 100644 --- a/internal/cli/message/message.go +++ b/internal/cli/message/message.go @@ -4,3 +4,8 @@ type LoginSuccessMessage struct { AccessToken string RefreshToken string } + +type RegisterSuccessMessage struct { + AccessToken string + RefreshToken string +} diff --git a/internal/cli/model.go b/internal/cli/model.go index 8da6f01..693d517 100644 --- a/internal/cli/model.go +++ b/internal/cli/model.go @@ -12,12 +12,10 @@ import ( "github.com/bjlag/go-keeper/internal/cli/message" ) -type state int - type MainModel struct { - help help.Model - header string - state state + help help.Model + header string + accessToken string refreshToken string } @@ -49,6 +47,9 @@ func (m *MainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case message.LoginSuccessMessage: m.accessToken = msg.AccessToken m.refreshToken = msg.RefreshToken + case message.RegisterSuccessMessage: + m.accessToken = msg.AccessToken + m.refreshToken = msg.RefreshToken } return m, nil From fbba804fc9e90ba18673393ec3cbc1a15253fca2 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Thu, 27 Feb 2025 19:41:50 +0300 Subject: [PATCH 15/94] client: refactoring RPC server --- internal/app/server/app.go | 2 +- internal/infrastructure/rpc/server/method.go | 8 ++++---- internal/infrastructure/rpc/server/option.go | 8 ++++---- internal/infrastructure/rpc/server/server.go | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/internal/app/server/app.go b/internal/app/server/app.go index bb4e105..f7000ef 100644 --- a/internal/app/server/app.go +++ b/internal/app/server/app.go @@ -50,7 +50,7 @@ func (a *App) Run(ctx context.Context) error { ucLogin := login.NewUsecase(userStore, tokeGenerator) ucRefreshTokens := rt.NewUsecase(userStore, tokeGenerator) - s := server.NewServer( + s := server.NewRPCServer( server.WithAddress(a.cfg.Address.Host, a.cfg.Address.Port), server.WithLogger(a.log), diff --git a/internal/infrastructure/rpc/server/method.go b/internal/infrastructure/rpc/server/method.go index 8dc85ab..7d65d25 100644 --- a/internal/infrastructure/rpc/server/method.go +++ b/internal/infrastructure/rpc/server/method.go @@ -15,7 +15,7 @@ const ( RefreshTokensMethod = "RefreshTokens" ) -func (s Server) Register(ctx context.Context, in *pb.RegisterIn) (*pb.RegisterOut, error) { +func (s RPCServer) Register(ctx context.Context, in *pb.RegisterIn) (*pb.RegisterOut, error) { handler, err := s.getHandler(RegisterMethod) if err != nil { return nil, err @@ -29,7 +29,7 @@ func (s Server) Register(ctx context.Context, in *pb.RegisterIn) (*pb.RegisterOu return h(ctx, in) } -func (s Server) Login(ctx context.Context, in *pb.LoginIn) (*pb.LoginOut, error) { +func (s RPCServer) Login(ctx context.Context, in *pb.LoginIn) (*pb.LoginOut, error) { handler, err := s.getHandler(LoginMethod) if err != nil { return nil, err @@ -43,7 +43,7 @@ func (s Server) Login(ctx context.Context, in *pb.LoginIn) (*pb.LoginOut, error) return h(ctx, in) } -func (s Server) RefreshTokens(ctx context.Context, in *pb.RefreshTokensIn) (*pb.RefreshTokensOut, error) { +func (s RPCServer) RefreshTokens(ctx context.Context, in *pb.RefreshTokensIn) (*pb.RefreshTokensOut, error) { handler, err := s.getHandler(RefreshTokensMethod) if err != nil { return nil, err @@ -57,7 +57,7 @@ func (s Server) RefreshTokens(ctx context.Context, in *pb.RefreshTokensIn) (*pb. return h(ctx, in) } -func (s Server) getHandler(name string) (any, error) { +func (s RPCServer) getHandler(name string) (any, error) { handler, ok := s.handlers[name] if !ok { return nil, status.Errorf(codes.NotFound, "handler for %s methos not found", name) diff --git a/internal/infrastructure/rpc/server/option.go b/internal/infrastructure/rpc/server/option.go index f33bc7e..96701fd 100644 --- a/internal/infrastructure/rpc/server/option.go +++ b/internal/infrastructure/rpc/server/option.go @@ -4,23 +4,23 @@ import ( "go.uber.org/zap" ) -type Option func(*Server) +type Option func(*RPCServer) func WithAddress(host string, port int) Option { - return func(s *Server) { + return func(s *RPCServer) { s.host = host s.port = port } } func WithLogger(logger *zap.Logger) Option { - return func(s *Server) { + return func(s *RPCServer) { s.log = logger } } func WithHandler(method string, handler any) Option { - return func(s *Server) { + return func(s *RPCServer) { s.handlers[method] = handler } } diff --git a/internal/infrastructure/rpc/server/server.go b/internal/infrastructure/rpc/server/server.go index 5ffcb9f..692da9f 100644 --- a/internal/infrastructure/rpc/server/server.go +++ b/internal/infrastructure/rpc/server/server.go @@ -13,7 +13,7 @@ import ( "github.com/bjlag/go-keeper/internal/infrastructure/rpc/interceptor" ) -type Server struct { +type RPCServer struct { pb.UnimplementedKeeperServer host string @@ -22,8 +22,8 @@ type Server struct { log *zap.Logger } -func NewServer(opts ...Option) *Server { - s := &Server{ +func NewRPCServer(opts ...Option) *RPCServer { + s := &RPCServer{ handlers: make(map[string]any), } @@ -34,7 +34,7 @@ func NewServer(opts ...Option) *Server { return s } -func (s Server) Start(ctx context.Context) error { +func (s RPCServer) Start(ctx context.Context) error { const op = "rpc.Start" s.log.Info("Starting gRPC server", From d61aa4e0b2fc3d3eb96fae3294d4eca917501309 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Thu, 27 Feb 2025 19:56:40 +0300 Subject: [PATCH 16/94] client: rpc client --- cmd/client/main.go | 5 +-- internal/app/client/app.go | 27 ++++++++++++-- internal/cli/model.go | 6 ++- internal/infrastructure/rpc/client/client.go | 39 ++++++++++++++++++++ internal/infrastructure/rpc/client/login.go | 34 +++++++++++++++++ 5 files changed, 103 insertions(+), 8 deletions(-) create mode 100644 internal/infrastructure/rpc/client/client.go create mode 100644 internal/infrastructure/rpc/client/login.go diff --git a/cmd/client/main.go b/cmd/client/main.go index 27562f6..3f6af36 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -11,7 +11,6 @@ import ( "go.uber.org/zap" "github.com/bjlag/go-keeper/internal/app/client" - "github.com/bjlag/go-keeper/internal/app/server" "github.com/bjlag/go-keeper/internal/infrastructure/logger" ) @@ -31,7 +30,7 @@ func main() { flag.StringVar(&configPath, "c", configPathDefault, "Path to config file") flag.Parse() - var cfg server.Config + var cfg client.Config if err := cleanenv.ReadConfig(configPath, &cfg); err != nil { panic(err) } @@ -46,7 +45,7 @@ func main() { ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) defer cancel() - if err := client.NewApp().Run(ctx); err != nil { + if err := client.NewApp(cfg, log).Run(ctx); err != nil { panic(err) } } diff --git a/internal/app/client/app.go b/internal/app/client/app.go index 2e83260..71dec8b 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -2,19 +2,38 @@ package client import ( "context" + "fmt" tea "github.com/charmbracelet/bubbletea" + "go.uber.org/zap" "github.com/bjlag/go-keeper/internal/cli" + rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" ) -type App struct{} +type App struct { + cfg Config + log *zap.Logger +} -func NewApp() *App { - return &App{} +func NewApp(cfg Config, log *zap.Logger) *App { + return &App{ + cfg: cfg, + log: log, + } } func (a *App) Run(ctx context.Context) error { - _, err := tea.NewProgram(cli.InitModel(), tea.WithAltScreen(), tea.WithContext(ctx)).Run() + const op = "app.Run" + + rpcClient, err := rpc.NewRPCClient(a.cfg.Server.Host, a.cfg.Server.Port) + if err != nil { + return fmt.Errorf("%s:%w", op, err) + } + defer func() { + _ = rpcClient.Close() + }() + + _, err = tea.NewProgram(cli.InitModel(rpcClient), tea.WithAltScreen(), tea.WithContext(ctx)).Run() return err } diff --git a/internal/cli/model.go b/internal/cli/model.go index 693d517..dd8cb36 100644 --- a/internal/cli/model.go +++ b/internal/cli/model.go @@ -10,20 +10,24 @@ import ( "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/form/login" "github.com/bjlag/go-keeper/internal/cli/message" + "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" ) type MainModel struct { help help.Model header string + client *client.RPCClient + accessToken string refreshToken string } -func InitModel() *MainModel { +func InitModel(client *client.RPCClient) *MainModel { return &MainModel{ help: help.New(), header: "Go Keeper", + client: client, } } diff --git a/internal/infrastructure/rpc/client/client.go b/internal/infrastructure/rpc/client/client.go new file mode 100644 index 0000000..1c55a72 --- /dev/null +++ b/internal/infrastructure/rpc/client/client.go @@ -0,0 +1,39 @@ +package client + +import ( + "fmt" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + + "github.com/bjlag/go-keeper/internal/generated/rpc" +) + +type RPCClient struct { + conn *grpc.ClientConn + client rpc.KeeperClient +} + +func NewRPCClient(serverHost string, serverPort int) (*RPCClient, error) { + conn, err := grpc.NewClient( + fmt.Sprintf("%s:%d", serverHost, serverPort), + grpc.WithTransportCredentials(insecure.NewCredentials()), + //grpc.WithChainUnaryInterceptor( + // interceptor.LoggerClientInterceptor(log), + // interceptor.RealIPClientInterceptor, + // interceptor.SignatureClientInterceptor(sign), + //), + ) + if err != nil { + return nil, err + } + + return &RPCClient{ + conn: conn, + client: rpc.NewKeeperClient(conn), + }, nil +} + +func (c RPCClient) Close() error { + return c.conn.Close() +} diff --git a/internal/infrastructure/rpc/client/login.go b/internal/infrastructure/rpc/client/login.go new file mode 100644 index 0000000..ea626ec --- /dev/null +++ b/internal/infrastructure/rpc/client/login.go @@ -0,0 +1,34 @@ +package client + +import ( + "context" + + "github.com/bjlag/go-keeper/internal/generated/rpc" +) + +type LoginIn struct { + Email string + password string +} + +type LoginOut struct { + AccessToken string + RefreshToken string +} + +func (c RPCClient) Login(ctx context.Context, in LoginIn) (*LoginOut, error) { + rpcIn := &rpc.LoginIn{ + Email: in.Email, + Password: in.password, + } + + out, err := c.client.Login(ctx, rpcIn) + if err != nil { + return nil, err + } + + return &LoginOut{ + AccessToken: out.AccessToken, + RefreshToken: out.RefreshToken, + }, nil +} From 472dd145e6d5e83ac74fd5418f1348f117ec1f87 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Fri, 28 Feb 2025 17:46:11 +0300 Subject: [PATCH 17/94] client: login --- .golangci.yml | 2 + internal/app/client/app.go | 19 +++++- internal/app/client/config.go | 2 +- internal/cli/common/error.go | 14 +++++ internal/cli/common/style.go | 3 +- internal/cli/form/login/form.go | 63 ++++++++++++++----- internal/cli/form/register/form.go | 35 +++++------ internal/cli/message/message.go | 4 ++ internal/cli/model.go | 30 ++++++--- internal/cli/option.go | 22 +++++++ internal/infrastructure/rpc/client/client.go | 12 ++-- internal/infrastructure/rpc/client/login.go | 8 +-- .../infrastructure/rpc/interceptor/logger.go | 15 +++++ internal/usecase/client/login/contract.go | 11 ++++ internal/usecase/client/login/model.go | 11 ++++ internal/usecase/client/login/usecase.go | 35 +++++++++++ 16 files changed, 227 insertions(+), 59 deletions(-) create mode 100644 internal/cli/option.go create mode 100644 internal/usecase/client/login/contract.go create mode 100644 internal/usecase/client/login/model.go create mode 100644 internal/usecase/client/login/usecase.go diff --git a/.golangci.yml b/.golangci.yml index 5d4ab80..933155c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -18,6 +18,8 @@ linters: - gochecknoglobals - nonamedreturns - wrapcheck + - gocognit + - cyclop linters-settings: revive: diff --git a/internal/app/client/app.go b/internal/app/client/app.go index 71dec8b..280cf6b 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -8,7 +8,10 @@ import ( "go.uber.org/zap" "github.com/bjlag/go-keeper/internal/cli" + formLogin "github.com/bjlag/go-keeper/internal/cli/form/login" + formRegister "github.com/bjlag/go-keeper/internal/cli/form/register" rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" + "github.com/bjlag/go-keeper/internal/usecase/client/login" ) type App struct { @@ -26,14 +29,26 @@ func NewApp(cfg Config, log *zap.Logger) *App { func (a *App) Run(ctx context.Context) error { const op = "app.Run" - rpcClient, err := rpc.NewRPCClient(a.cfg.Server.Host, a.cfg.Server.Port) + rpcClient, err := rpc.NewRPCClient(a.cfg.Server.Host, a.cfg.Server.Port, a.log) if err != nil { + a.log.Error("failed to create rpc client", zap.Error(err)) return fmt.Errorf("%s:%w", op, err) } defer func() { _ = rpcClient.Close() }() - _, err = tea.NewProgram(cli.InitModel(rpcClient), tea.WithAltScreen(), tea.WithContext(ctx)).Run() + ucLogin := login.NewUsecase(rpcClient) + + model := cli.InitModel( + cli.WithLoginForm(formLogin.NewForm(ucLogin)), + cli.WithRegisterForm(formRegister.NewForm()), + ) + + _, err = tea.NewProgram(model, tea.WithAltScreen(), tea.WithContext(ctx)).Run() + if err != nil { + a.log.Error("failed to run cli program", zap.Error(err)) + } + return err } diff --git a/internal/app/client/config.go b/internal/app/client/config.go index 374e865..73a48d0 100644 --- a/internal/app/client/config.go +++ b/internal/app/client/config.go @@ -6,7 +6,7 @@ type Config struct { Server struct { Host string `yaml:"host" env:"SERVER_HOST" env-description:"Server host" json:"host"` Port int `yaml:"port" env:"SERVER_PORT" env-description:"Server port" json:"port"` - } `yaml:"address" json:"server"` + } `yaml:"server" json:"server"` Database struct { Host string `yaml:"host" env:"DB_HOST" env-description:"Database host" json:"host"` diff --git a/internal/cli/common/error.go b/internal/cli/common/error.go index 859c9da..80ed7a1 100644 --- a/internal/cli/common/error.go +++ b/internal/cli/common/error.go @@ -26,3 +26,17 @@ func (e *ValidateError) HasErrors() bool { func (e *ValidateError) Error() string { return strings.Join(e.errors, "\n") } + +type FormError struct { + text string +} + +func NewFormError(text string) *FormError { + return &FormError{ + text: text, + } +} + +func (e *FormError) Error() string { + return e.text +} diff --git a/internal/cli/common/style.go b/internal/cli/common/style.go index a8ad18d..d846041 100644 --- a/internal/cli/common/style.go +++ b/internal/cli/common/style.go @@ -1,9 +1,10 @@ package common import ( - "github.com/bjlag/go-keeper/internal/cli/element" "github.com/charmbracelet/bubbles/textinput" "github.com/charmbracelet/lipgloss" + + "github.com/bjlag/go-keeper/internal/cli/element" ) var ( diff --git a/internal/cli/form/login/form.go b/internal/cli/form/login/form.go index f7053ee..83d1381 100644 --- a/internal/cli/form/login/form.go +++ b/internal/cli/form/login/form.go @@ -1,19 +1,23 @@ package login import ( + "context" "errors" "strings" + "time" "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/element" - "github.com/bjlag/go-keeper/internal/cli/form/register" "github.com/bjlag/go-keeper/internal/cli/message" "github.com/bjlag/go-keeper/internal/infrastructure/validator" + "github.com/bjlag/go-keeper/internal/usecase/client/login" ) const ( @@ -27,6 +31,8 @@ const ( passwordCharLimit = 20 ) +var errPasswordInvalid = common.NewFormError("Неверный email или пароль") + type Form struct { main tea.Model help help.Model @@ -34,11 +40,12 @@ type Form struct { elements []interface{} pos int err error + + usecase *login.Usecase } -func NewForm(main tea.Model) *Form { +func NewForm(usecase *login.Usecase) *Form { f := &Form{ - main: main, help: help.New(), header: "Авторизация", elements: []interface{}{ @@ -48,11 +55,12 @@ func NewForm(main tea.Model) *Form { posRegisterBtn: common.CreateDefaultButton("Регистрация"), posCloseBtn: common.CreateDefaultButton("Закрыть"), }, + + usecase: usecase, } for i := range f.elements { - switch e := f.elements[i].(type) { - case textinput.Model: + if e, ok := f.elements[i].(textinput.Model); ok { if i == posEmail { e.TextStyle = common.FocusedStyle e.PromptStyle = common.FocusedStyle @@ -61,10 +69,8 @@ func NewForm(main tea.Model) *Form { f.elements[i] = e continue } - e.EchoMode = textinput.EchoPassword e.EchoCharacter = '•' - f.elements[i] = e } } @@ -72,13 +78,16 @@ func NewForm(main tea.Model) *Form { return f } +func (f *Form) SetMainModel(m tea.Model) { + f.main = m +} + func (f *Form) Init() tea.Cmd { return nil } func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg := msg.(type) { - case tea.KeyMsg: + if msg, ok := msg.(tea.KeyMsg); ok { switch { case key.Matches(msg, common.Keys.Quit): return f, tea.Quit @@ -125,7 +134,7 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case f.pos == posSubmitBtn || f.pos == posEmail || f.pos == posPassword: return f.submit() case f.pos == posRegisterBtn: - return register.NewForm(f.main, f).Update(tea.ClearScreen) + return f.main.Update(message.OpenRegisterFormMessage{}) case f.pos == posCloseBtn: return f, tea.Quit } @@ -159,10 +168,13 @@ func (f *Form) View() string { } } - var errValidate *common.ValidateError + var ( + errValidate *common.ValidateError + errForm *common.FormError + ) // выводим ошибки валидации - if f.err != nil && errors.As(f.err, &errValidate) { + if f.err != nil && (errors.As(f.err, &errValidate) || errors.As(f.err, &errForm)) { b.WriteString(common.ErrorBlockStyle.Render(f.err.Error())) b.WriteRune('\n') } @@ -171,7 +183,7 @@ func (f *Form) View() string { b.WriteString(f.help.View(common.Keys)) // выводим прочие ошибки - if f.err != nil && !errors.As(f.err, &errValidate) { + if f.err != nil && !(errors.As(f.err, &errValidate) || errors.As(f.err, &errForm)) { b.WriteRune('\n') b.WriteString(common.ErrorBlockStyle.Render(f.err.Error())) } @@ -208,11 +220,30 @@ func (f *Form) submit() (tea.Model, tea.Cmd) { return f, nil } - // TODO: выполняем авторизацию + ctx, cancel := context.WithTimeout(context.Background(), 300*time.Millisecond) + defer cancel() + + result, err := f.usecase.Do(ctx, login.Data{ + Email: email.Value(), + Password: password.Value(), + }) + if err != nil { + if s, ok := status.FromError(err); ok { + if s.Code() == codes.Unauthenticated { + f.err = errPasswordInvalid + return f, nil + } + f.err = common.NewFormError(s.Message()) + return f, nil + } + + f.err = err + return f, nil + } return f.main.Update(message.LoginSuccessMessage{ - AccessToken: "xxxx", - RefreshToken: "yyyy", + AccessToken: result.AccessToken, + RefreshToken: result.RefreshToken, }) } diff --git a/internal/cli/form/register/form.go b/internal/cli/form/register/form.go index 271d34f..1407b6f 100644 --- a/internal/cli/form/register/form.go +++ b/internal/cli/form/register/form.go @@ -26,21 +26,18 @@ const ( ) type Form struct { - main tea.Model - prevModel tea.Model - help help.Model - header string - elements []interface{} - pos int - err error + main tea.Model + help help.Model + header string + elements []interface{} + pos int + err error } -func NewForm(main, prevModel tea.Model) *Form { +func NewForm() *Form { f := &Form{ - main: main, - prevModel: prevModel, - help: help.New(), - header: "Регистрация", + help: help.New(), + header: "Регистрация", elements: []interface{}{ posEmail: common.CreateDefaultTextInput("Email", emailCharLimit), posPassword: common.CreateDefaultTextInput("Password", passwordCharLimit), @@ -50,8 +47,7 @@ func NewForm(main, prevModel tea.Model) *Form { } for i := range f.elements { - switch e := f.elements[i].(type) { - case textinput.Model: + if e, ok := f.elements[i].(textinput.Model); ok { if i == posEmail { e.TextStyle = common.FocusedStyle e.PromptStyle = common.FocusedStyle @@ -60,10 +56,8 @@ func NewForm(main, prevModel tea.Model) *Form { f.elements[i] = e continue } - e.EchoMode = textinput.EchoPassword e.EchoCharacter = '•' - f.elements[i] = e } } @@ -71,13 +65,16 @@ func NewForm(main, prevModel tea.Model) *Form { return f } +func (f *Form) SetMainModel(m tea.Model) { + f.main = m +} + func (f *Form) Init() tea.Cmd { return nil } func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg := msg.(type) { - case tea.KeyMsg: + if msg, ok := msg.(tea.KeyMsg); ok { switch { case key.Matches(msg, common.Keys.Quit): return f, tea.Quit @@ -124,7 +121,7 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case f.pos == posSubmitBtn || f.pos == posEmail || f.pos == posPassword: return f.submit() case f.pos == posBackBtn: - return f.prevModel.Update(tea.ClearScreen) + return f.main.Update(message.OpenLoginFormMessage{}) } return f, nil diff --git a/internal/cli/message/message.go b/internal/cli/message/message.go index e865f58..9a5f612 100644 --- a/internal/cli/message/message.go +++ b/internal/cli/message/message.go @@ -9,3 +9,7 @@ type RegisterSuccessMessage struct { AccessToken string RefreshToken string } + +type OpenLoginFormMessage struct{} + +type OpenRegisterFormMessage struct{} diff --git a/internal/cli/model.go b/internal/cli/model.go index dd8cb36..815fa86 100644 --- a/internal/cli/model.go +++ b/internal/cli/model.go @@ -9,32 +9,38 @@ import ( "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/form/login" + "github.com/bjlag/go-keeper/internal/cli/form/register" "github.com/bjlag/go-keeper/internal/cli/message" - "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" ) type MainModel struct { help help.Model header string - client *client.RPCClient + loginForm *login.Form + registerForm *register.Form accessToken string refreshToken string } -func InitModel(client *client.RPCClient) *MainModel { - return &MainModel{ +func InitModel(opts ...Option) *MainModel { + m := &MainModel{ help: help.New(), header: "Go Keeper", - client: client, } + + for _, opt := range opts { + opt(m) + } + + return m } func (m *MainModel) Init() tea.Cmd { return tea.Batch( func() tea.Msg { - return login.NewForm(m) + return message.OpenLoginFormMessage{} }, ) } @@ -46,8 +52,10 @@ func (m *MainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, common.Keys.Quit): return m, tea.Quit } - case *login.Form: - return msg.Update(msg) + case message.OpenLoginFormMessage: + return m.loginForm.Update(tea.ClearScreen()) + case message.OpenRegisterFormMessage: + return m.registerForm.Update(tea.ClearScreen()) case message.LoginSuccessMessage: m.accessToken = msg.AccessToken m.refreshToken = msg.RefreshToken @@ -65,8 +73,10 @@ func (m *MainModel) View() string { b.WriteString(common.TitleStyle.Render(m.header)) b.WriteRune('\n') - b.WriteString(common.TextStyle.Render("Access token:", m.accessToken, "\n\n")) - b.WriteString(common.TextStyle.Render("Refresh token:", m.accessToken, "\n\n")) + b.WriteString(common.TextStyle.Render("Access token:", m.accessToken)) + b.WriteRune('\n') + b.WriteString(common.TextStyle.Render("Refresh token:", m.accessToken)) + b.WriteRune('\n') return b.String() } diff --git a/internal/cli/option.go b/internal/cli/option.go new file mode 100644 index 0000000..e42b6c6 --- /dev/null +++ b/internal/cli/option.go @@ -0,0 +1,22 @@ +package cli + +import ( + "github.com/bjlag/go-keeper/internal/cli/form/login" + "github.com/bjlag/go-keeper/internal/cli/form/register" +) + +type Option func(*MainModel) + +func WithLoginForm(form *login.Form) Option { + return func(m *MainModel) { + form.SetMainModel(m) + m.loginForm = form + } +} + +func WithRegisterForm(form *register.Form) Option { + return func(m *MainModel) { + form.SetMainModel(m) + m.registerForm = form + } +} diff --git a/internal/infrastructure/rpc/client/client.go b/internal/infrastructure/rpc/client/client.go index 1c55a72..e389452 100644 --- a/internal/infrastructure/rpc/client/client.go +++ b/internal/infrastructure/rpc/client/client.go @@ -3,10 +3,12 @@ package client import ( "fmt" + "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "github.com/bjlag/go-keeper/internal/generated/rpc" + "github.com/bjlag/go-keeper/internal/infrastructure/rpc/interceptor" ) type RPCClient struct { @@ -14,15 +16,13 @@ type RPCClient struct { client rpc.KeeperClient } -func NewRPCClient(serverHost string, serverPort int) (*RPCClient, error) { +func NewRPCClient(serverHost string, serverPort int, log *zap.Logger) (*RPCClient, error) { conn, err := grpc.NewClient( fmt.Sprintf("%s:%d", serverHost, serverPort), grpc.WithTransportCredentials(insecure.NewCredentials()), - //grpc.WithChainUnaryInterceptor( - // interceptor.LoggerClientInterceptor(log), - // interceptor.RealIPClientInterceptor, - // interceptor.SignatureClientInterceptor(sign), - //), + grpc.WithChainUnaryInterceptor( + interceptor.LoggerClientInterceptor(log), + ), ) if err != nil { return nil, err diff --git a/internal/infrastructure/rpc/client/login.go b/internal/infrastructure/rpc/client/login.go index ea626ec..0699cc9 100644 --- a/internal/infrastructure/rpc/client/login.go +++ b/internal/infrastructure/rpc/client/login.go @@ -8,7 +8,7 @@ import ( type LoginIn struct { Email string - password string + Password string } type LoginOut struct { @@ -19,7 +19,7 @@ type LoginOut struct { func (c RPCClient) Login(ctx context.Context, in LoginIn) (*LoginOut, error) { rpcIn := &rpc.LoginIn{ Email: in.Email, - Password: in.password, + Password: in.Password, } out, err := c.client.Login(ctx, rpcIn) @@ -28,7 +28,7 @@ func (c RPCClient) Login(ctx context.Context, in LoginIn) (*LoginOut, error) { } return &LoginOut{ - AccessToken: out.AccessToken, - RefreshToken: out.RefreshToken, + AccessToken: out.GetAccessToken(), + RefreshToken: out.GetRefreshToken(), }, nil } diff --git a/internal/infrastructure/rpc/interceptor/logger.go b/internal/infrastructure/rpc/interceptor/logger.go index 0c2aa4d..755313f 100644 --- a/internal/infrastructure/rpc/interceptor/logger.go +++ b/internal/infrastructure/rpc/interceptor/logger.go @@ -28,3 +28,18 @@ func LoggerServerInterceptor(log *zap.Logger) grpc.UnaryServerInterceptor { return resp, err } } + +func LoggerClientInterceptor(log *zap.Logger) grpc.UnaryClientInterceptor { + return func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + err := invoker(ctx, method, req, reply, cc, opts...) + + hLog := log + hLog.Info("Send RPC request", + zap.String("method", method), + zap.Any("request", req), + zap.String("code", status.Code(err).String()), + ) + + return err + } +} diff --git a/internal/usecase/client/login/contract.go b/internal/usecase/client/login/contract.go new file mode 100644 index 0000000..1fff280 --- /dev/null +++ b/internal/usecase/client/login/contract.go @@ -0,0 +1,11 @@ +package login + +import ( + "context" + + rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" +) + +type client interface { + Login(ctx context.Context, in rpc.LoginIn) (*rpc.LoginOut, error) +} diff --git a/internal/usecase/client/login/model.go b/internal/usecase/client/login/model.go new file mode 100644 index 0000000..17dac34 --- /dev/null +++ b/internal/usecase/client/login/model.go @@ -0,0 +1,11 @@ +package login + +type Data struct { + Email string + Password string +} + +type Result struct { + AccessToken string + RefreshToken string +} diff --git a/internal/usecase/client/login/usecase.go b/internal/usecase/client/login/usecase.go new file mode 100644 index 0000000..3d06e45 --- /dev/null +++ b/internal/usecase/client/login/usecase.go @@ -0,0 +1,35 @@ +package login + +import ( + "context" + "fmt" + + rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" +) + +type Usecase struct { + client client +} + +func NewUsecase(client client) *Usecase { + return &Usecase{ + client: client, + } +} + +func (u *Usecase) Do(ctx context.Context, data Data) (*Result, error) { + const op = "usecase.login.Do" + + out, err := u.client.Login(ctx, rpc.LoginIn{ + Email: data.Email, + Password: data.Password, + }) + if err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + + return &Result{ + AccessToken: out.AccessToken, + RefreshToken: out.RefreshToken, + }, nil +} From 2acd9a9950a36800afa683ca7f1fbd193abe9611 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Fri, 28 Feb 2025 19:10:38 +0300 Subject: [PATCH 18/94] client: registration --- internal/app/client/app.go | 4 +- internal/cli/form/login/form.go | 6 +-- internal/cli/form/register/form.go | 42 +++++++++++++++---- .../infrastructure/rpc/client/register.go | 34 +++++++++++++++ internal/rpc/register/handler.go | 2 +- internal/usecase/client/register/contract.go | 11 +++++ internal/usecase/client/register/model.go | 11 +++++ internal/usecase/client/register/usecase.go | 35 ++++++++++++++++ 8 files changed, 131 insertions(+), 14 deletions(-) create mode 100644 internal/infrastructure/rpc/client/register.go create mode 100644 internal/usecase/client/register/contract.go create mode 100644 internal/usecase/client/register/model.go create mode 100644 internal/usecase/client/register/usecase.go diff --git a/internal/app/client/app.go b/internal/app/client/app.go index 280cf6b..4f0db11 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -12,6 +12,7 @@ import ( formRegister "github.com/bjlag/go-keeper/internal/cli/form/register" rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" "github.com/bjlag/go-keeper/internal/usecase/client/login" + "github.com/bjlag/go-keeper/internal/usecase/client/register" ) type App struct { @@ -39,10 +40,11 @@ func (a *App) Run(ctx context.Context) error { }() ucLogin := login.NewUsecase(rpcClient) + ucRegister := register.NewUsecase(rpcClient) model := cli.InitModel( cli.WithLoginForm(formLogin.NewForm(ucLogin)), - cli.WithRegisterForm(formRegister.NewForm()), + cli.WithRegisterForm(formRegister.NewForm(ucRegister)), ) _, err = tea.NewProgram(model, tea.WithAltScreen(), tea.WithContext(ctx)).Run() diff --git a/internal/cli/form/login/form.go b/internal/cli/form/login/form.go index 83d1381..4351d56 100644 --- a/internal/cli/form/login/form.go +++ b/internal/cli/form/login/form.go @@ -4,7 +4,6 @@ import ( "context" "errors" "strings" - "time" "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" @@ -220,10 +219,7 @@ func (f *Form) submit() (tea.Model, tea.Cmd) { return f, nil } - ctx, cancel := context.WithTimeout(context.Background(), 300*time.Millisecond) - defer cancel() - - result, err := f.usecase.Do(ctx, login.Data{ + result, err := f.usecase.Do(context.TODO(), login.Data{ Email: email.Value(), Password: password.Value(), }) diff --git a/internal/cli/form/register/form.go b/internal/cli/form/register/form.go index 1407b6f..0e5bca3 100644 --- a/internal/cli/form/register/form.go +++ b/internal/cli/form/register/form.go @@ -1,6 +1,7 @@ package register import ( + "context" "errors" "strings" @@ -8,11 +9,14 @@ import ( "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/element" "github.com/bjlag/go-keeper/internal/cli/message" "github.com/bjlag/go-keeper/internal/infrastructure/validator" + "github.com/bjlag/go-keeper/internal/usecase/client/register" ) const ( @@ -25,6 +29,8 @@ const ( passwordCharLimit = 20 ) +var errUserAlreadyRegistered = common.NewFormError("Пользователь уже зарегистрирован") + type Form struct { main tea.Model help help.Model @@ -32,9 +38,11 @@ type Form struct { elements []interface{} pos int err error + + usecase *register.Usecase } -func NewForm() *Form { +func NewForm(usecase *register.Usecase) *Form { f := &Form{ help: help.New(), header: "Регистрация", @@ -44,6 +52,7 @@ func NewForm() *Form { posSubmitBtn: common.CreateDefaultButton("Регистрация"), posBackBtn: common.CreateDefaultButton("Назад"), }, + usecase: usecase, } for i := range f.elements { @@ -153,10 +162,13 @@ func (f *Form) View() string { } } - var errValidate *common.ValidateError + var ( + errValidate *common.ValidateError + errForm *common.FormError + ) // выводим ошибки валидации - if f.err != nil && errors.As(f.err, &errValidate) { + if f.err != nil && (errors.As(f.err, &errValidate) || errors.As(f.err, &errForm)) { b.WriteString(common.ErrorBlockStyle.Render(f.err.Error())) b.WriteRune('\n') } @@ -165,7 +177,7 @@ func (f *Form) View() string { b.WriteString(f.help.View(common.Keys)) // выводим прочие ошибки - if f.err != nil && !errors.As(f.err, &errValidate) { + if f.err != nil && !(errors.As(f.err, &errValidate) || errors.As(f.err, &errForm)) { b.WriteRune('\n') b.WriteString(common.ErrorBlockStyle.Render(f.err.Error())) } @@ -202,11 +214,27 @@ func (f *Form) submit() (tea.Model, tea.Cmd) { return f, nil } - // TODO: выполняем регистрацию + result, err := f.usecase.Do(context.TODO(), register.Data{ + Email: email.Value(), + Password: password.Value(), + }) + if err != nil { + if s, ok := status.FromError(err); ok { + if s.Code() == codes.AlreadyExists { + f.err = errUserAlreadyRegistered + return f, nil + } + f.err = common.NewFormError(s.Message()) + return f, nil + } + + f.err = err + return f, nil + } return f.main.Update(message.RegisterSuccessMessage{ - AccessToken: "xxxx", - RefreshToken: "yyyy", + AccessToken: result.AccessToken, + RefreshToken: result.RefreshToken, }) } diff --git a/internal/infrastructure/rpc/client/register.go b/internal/infrastructure/rpc/client/register.go new file mode 100644 index 0000000..f14211f --- /dev/null +++ b/internal/infrastructure/rpc/client/register.go @@ -0,0 +1,34 @@ +package client + +import ( + "context" + + "github.com/bjlag/go-keeper/internal/generated/rpc" +) + +type RegisterIn struct { + Email string + Password string +} + +type RegisterOut struct { + AccessToken string + RefreshToken string +} + +func (c RPCClient) Register(ctx context.Context, in RegisterIn) (*RegisterOut, error) { + rpcIn := &rpc.RegisterIn{ + Email: in.Email, + Password: in.Password, + } + + out, err := c.client.Register(ctx, rpcIn) + if err != nil { + return nil, err + } + + return &RegisterOut{ + AccessToken: out.GetAccessToken(), + RefreshToken: out.GetRefreshToken(), + }, nil +} diff --git a/internal/rpc/register/handler.go b/internal/rpc/register/handler.go index e9ea823..9292fab 100644 --- a/internal/rpc/register/handler.go +++ b/internal/rpc/register/handler.go @@ -31,7 +31,7 @@ func (h *Handler) Handle(ctx context.Context, in *pb.RegisterIn) (*pb.RegisterOu return nil, status.Error(codes.InvalidArgument, "email is invalid") } - if validator.ValidatePassword(in.GetPassword()) { + if !validator.ValidatePassword(in.GetPassword()) { return nil, status.Error(codes.InvalidArgument, "password is invalid (min. length 8 characters)") } diff --git a/internal/usecase/client/register/contract.go b/internal/usecase/client/register/contract.go new file mode 100644 index 0000000..5d300f5 --- /dev/null +++ b/internal/usecase/client/register/contract.go @@ -0,0 +1,11 @@ +package register + +import ( + "context" + + rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" +) + +type client interface { + Register(ctx context.Context, in rpc.RegisterIn) (*rpc.RegisterOut, error) +} diff --git a/internal/usecase/client/register/model.go b/internal/usecase/client/register/model.go new file mode 100644 index 0000000..6dca93a --- /dev/null +++ b/internal/usecase/client/register/model.go @@ -0,0 +1,11 @@ +package register + +type Data struct { + Email string + Password string +} + +type Result struct { + AccessToken string + RefreshToken string +} diff --git a/internal/usecase/client/register/usecase.go b/internal/usecase/client/register/usecase.go new file mode 100644 index 0000000..02f0e6d --- /dev/null +++ b/internal/usecase/client/register/usecase.go @@ -0,0 +1,35 @@ +package register + +import ( + "context" + "fmt" + + rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" +) + +type Usecase struct { + client client +} + +func NewUsecase(client client) *Usecase { + return &Usecase{ + client: client, + } +} + +func (u *Usecase) Do(ctx context.Context, data Data) (*Result, error) { + const op = "usecase.register.Do" + + out, err := u.client.Register(ctx, rpc.RegisterIn{ + Email: data.Email, + Password: data.Password, + }) + if err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + + return &Result{ + AccessToken: out.AccessToken, + RefreshToken: out.RefreshToken, + }, nil +} From 5caee4f10b377cec49d28144d18edc0b5e4eef47 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Fri, 28 Feb 2025 19:18:04 +0300 Subject: [PATCH 19/94] client: rpc refresh tokens --- internal/infrastructure/rpc/client/login.go | 5 ++- .../rpc/client/refresh_tokens.go | 35 +++++++++++++++++++ .../infrastructure/rpc/client/register.go | 5 ++- internal/infrastructure/rpc/server/server.go | 2 +- 4 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 internal/infrastructure/rpc/client/refresh_tokens.go diff --git a/internal/infrastructure/rpc/client/login.go b/internal/infrastructure/rpc/client/login.go index 0699cc9..1baa650 100644 --- a/internal/infrastructure/rpc/client/login.go +++ b/internal/infrastructure/rpc/client/login.go @@ -2,6 +2,7 @@ package client import ( "context" + "fmt" "github.com/bjlag/go-keeper/internal/generated/rpc" ) @@ -17,6 +18,8 @@ type LoginOut struct { } func (c RPCClient) Login(ctx context.Context, in LoginIn) (*LoginOut, error) { + const op = "client.rpc.Login" + rpcIn := &rpc.LoginIn{ Email: in.Email, Password: in.Password, @@ -24,7 +27,7 @@ func (c RPCClient) Login(ctx context.Context, in LoginIn) (*LoginOut, error) { out, err := c.client.Login(ctx, rpcIn) if err != nil { - return nil, err + return nil, fmt.Errorf("%s: %w", op, err) } return &LoginOut{ diff --git a/internal/infrastructure/rpc/client/refresh_tokens.go b/internal/infrastructure/rpc/client/refresh_tokens.go new file mode 100644 index 0000000..59e682b --- /dev/null +++ b/internal/infrastructure/rpc/client/refresh_tokens.go @@ -0,0 +1,35 @@ +package client + +import ( + "context" + "fmt" + + "github.com/bjlag/go-keeper/internal/generated/rpc" +) + +type RefreshTokensIn struct { + RefreshToken string +} + +type RefreshTokensOut struct { + AccessToken string + RefreshToken string +} + +func (c RPCClient) RefreshTokens(ctx context.Context, in RefreshTokensIn) (*RefreshTokensOut, error) { + const op = "client.rpc.RefreshTokens" + + rpcIn := &rpc.RefreshTokensIn{ + RefreshToken: in.RefreshToken, + } + + out, err := c.client.RefreshTokens(ctx, rpcIn) + if err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + + return &RefreshTokensOut{ + AccessToken: out.GetAccessToken(), + RefreshToken: out.GetRefreshToken(), + }, nil +} diff --git a/internal/infrastructure/rpc/client/register.go b/internal/infrastructure/rpc/client/register.go index f14211f..fbf956a 100644 --- a/internal/infrastructure/rpc/client/register.go +++ b/internal/infrastructure/rpc/client/register.go @@ -2,6 +2,7 @@ package client import ( "context" + "fmt" "github.com/bjlag/go-keeper/internal/generated/rpc" ) @@ -17,6 +18,8 @@ type RegisterOut struct { } func (c RPCClient) Register(ctx context.Context, in RegisterIn) (*RegisterOut, error) { + const op = "client.rpc.Register" + rpcIn := &rpc.RegisterIn{ Email: in.Email, Password: in.Password, @@ -24,7 +27,7 @@ func (c RPCClient) Register(ctx context.Context, in RegisterIn) (*RegisterOut, e out, err := c.client.Register(ctx, rpcIn) if err != nil { - return nil, err + return nil, fmt.Errorf("%s: %w", op, err) } return &RegisterOut{ diff --git a/internal/infrastructure/rpc/server/server.go b/internal/infrastructure/rpc/server/server.go index 692da9f..c941a99 100644 --- a/internal/infrastructure/rpc/server/server.go +++ b/internal/infrastructure/rpc/server/server.go @@ -35,7 +35,7 @@ func NewRPCServer(opts ...Option) *RPCServer { } func (s RPCServer) Start(ctx context.Context) error { - const op = "rpc.Start" + const op = "server.rpc.Start" s.log.Info("Starting gRPC server", zap.String("host", s.host), From 4c95a9ada3ec14ce39ab93740b8b155a44b8f1a9 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Fri, 28 Feb 2025 19:48:37 +0300 Subject: [PATCH 20/94] client: list of pass categories --- go.mod | 1 + go.sum | 6 ++- internal/cli/element/list.go | 46 +++++++++++++++++++++++ internal/cli/{common => element}/style.go | 30 +++++++++++---- internal/cli/form/login/form.go | 39 +++++++++++-------- internal/cli/form/register/form.go | 37 +++++++++++------- internal/cli/model.go | 35 ++++++++++++++--- 7 files changed, 150 insertions(+), 44 deletions(-) create mode 100644 internal/cli/element/list.go rename internal/cli/{common => element}/style.go (63%) diff --git a/go.mod b/go.mod index 3ae0234..9f4a4c9 100644 --- a/go.mod +++ b/go.mod @@ -45,6 +45,7 @@ require ( github.com/muesli/termenv v0.16.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/sahilm/fuzzy v0.1.1 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/net v0.33.0 // indirect diff --git a/go.sum b/go.sum index 4b9e15b..a6327d5 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,6 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg6 github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= -github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= @@ -81,6 +79,8 @@ github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= @@ -118,6 +118,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= +github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/internal/cli/element/list.go b/internal/cli/element/list.go new file mode 100644 index 0000000..12975d9 --- /dev/null +++ b/internal/cli/element/list.go @@ -0,0 +1,46 @@ +package element + +import ( + "fmt" + "io" + "strings" + + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" +) + +type Item string + +func (i Item) FilterValue() string { return "" } + +type ItemDelegate struct{} + +func (d ItemDelegate) Height() int { + return 1 +} + +func (d ItemDelegate) Spacing() int { + return 0 +} + +func (d ItemDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { + return nil +} + +func (d ItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { + i, ok := listItem.(Item) + if !ok { + return + } + + str := fmt.Sprintf("%d. %s", index+1, i) + + fn := ListItemStyle.Render + if index == m.Index() { + fn = func(s ...string) string { + return SelectedListItemStyle.Render("> " + strings.Join(s, " ")) + } + } + + _, _ = fmt.Fprint(w, fn(str)) +} diff --git a/internal/cli/common/style.go b/internal/cli/element/style.go similarity index 63% rename from internal/cli/common/style.go rename to internal/cli/element/style.go index d846041..cbd98f3 100644 --- a/internal/cli/common/style.go +++ b/internal/cli/element/style.go @@ -1,10 +1,9 @@ -package common +package element import ( + "github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/bubbles/textinput" "github.com/charmbracelet/lipgloss" - - "github.com/bjlag/go-keeper/internal/cli/element" ) var ( @@ -16,13 +15,17 @@ var ( Margin(1, 0) NoStyle = lipgloss.NewStyle() - TextStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("252")) CursorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("252")) FocusedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("39")) BlurredStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("244")) ErrorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("196")) - BlockStyle = TextStyle.Margin(1) + ListTitleStyle = lipgloss.NewStyle().MarginLeft(2) + ListItemStyle = lipgloss.NewStyle().PaddingLeft(4) + SelectedListItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170")) + ListPaginationStyle = list.DefaultStyles().PaginationStyle.PaddingLeft(4) + ListHelpStyle = list.DefaultStyles().HelpStyle.PaddingLeft(4).PaddingBottom(1) + ErrorBlockStyle = lipgloss.NewStyle(). BorderStyle(lipgloss.NormalBorder()). BorderForeground(lipgloss.Color("245")). @@ -59,10 +62,23 @@ func CreateDefaultTextInput(placeholder string, limit int) textinput.Model { return m } -func CreateDefaultButton(text string) element.Button { - b := element.NewButton(text) +func CreateDefaultButton(text string) Button { + b := NewButton(text) b.FocusedStyle = FocusedStyle b.BlurredStyle = BlurredStyle return b } + +func CreateDefaultList(title string, with, height int, items ...list.Item) list.Model { + l := list.New(items, ItemDelegate{}, with, height) + + l.Title = title + l.SetShowStatusBar(false) + l.SetFilteringEnabled(false) + l.Styles.Title = ListTitleStyle + l.Styles.PaginationStyle = ListPaginationStyle + l.Styles.HelpStyle = ListHelpStyle + + return l +} diff --git a/internal/cli/form/login/form.go b/internal/cli/form/login/form.go index 4351d56..ab448b0 100644 --- a/internal/cli/form/login/form.go +++ b/internal/cli/form/login/form.go @@ -48,11 +48,11 @@ func NewForm(usecase *login.Usecase) *Form { help: help.New(), header: "Авторизация", elements: []interface{}{ - posEmail: common.CreateDefaultTextInput("Email", emailCharLimit), - posPassword: common.CreateDefaultTextInput("Password", passwordCharLimit), - posSubmitBtn: common.CreateDefaultButton("Вход"), - posRegisterBtn: common.CreateDefaultButton("Регистрация"), - posCloseBtn: common.CreateDefaultButton("Закрыть"), + posEmail: element.CreateDefaultTextInput("Email", emailCharLimit), + posPassword: element.CreateDefaultTextInput("Password", passwordCharLimit), + posSubmitBtn: element.CreateDefaultButton("Вход"), + posRegisterBtn: element.CreateDefaultButton("Регистрация"), + posCloseBtn: element.CreateDefaultButton("Закрыть"), }, usecase: usecase, @@ -61,8 +61,8 @@ func NewForm(usecase *login.Usecase) *Form { for i := range f.elements { if e, ok := f.elements[i].(textinput.Model); ok { if i == posEmail { - e.TextStyle = common.FocusedStyle - e.PromptStyle = common.FocusedStyle + e.TextStyle = element.FocusedStyle + e.PromptStyle = element.FocusedStyle e.Focus() f.elements[i] = e @@ -86,7 +86,16 @@ func (f *Form) Init() tea.Cmd { } func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - if msg, ok := msg.(tea.KeyMsg); ok { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + for i := range f.elements { + switch e := f.elements[i].(type) { + case textinput.Model: + e.Width = msg.Width + } + } + return f, nil + case tea.KeyMsg: switch { case key.Matches(msg, common.Keys.Quit): return f, tea.Quit @@ -108,12 +117,12 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case textinput.Model: if i == f.pos { e.Focus() - f.elements[i] = common.SetFocusStyle(e) + f.elements[i] = element.SetFocusStyle(e) continue } e.Blur() - f.elements[i] = common.SetNoStyle(e) + f.elements[i] = element.SetNoStyle(e) case element.Button: if i == f.pos { e.Focus() @@ -148,7 +157,7 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (f *Form) View() string { var b strings.Builder - b.WriteString(common.TitleStyle.Render(f.header)) + b.WriteString(element.TitleStyle.Render(f.header)) b.WriteRune('\n') for i := range f.elements { @@ -174,7 +183,7 @@ func (f *Form) View() string { // выводим ошибки валидации if f.err != nil && (errors.As(f.err, &errValidate) || errors.As(f.err, &errForm)) { - b.WriteString(common.ErrorBlockStyle.Render(f.err.Error())) + b.WriteString(element.ErrorBlockStyle.Render(f.err.Error())) b.WriteRune('\n') } @@ -184,7 +193,7 @@ func (f *Form) View() string { // выводим прочие ошибки if f.err != nil && !(errors.As(f.err, &errValidate) || errors.As(f.err, &errForm)) { b.WriteRune('\n') - b.WriteString(common.ErrorBlockStyle.Render(f.err.Error())) + b.WriteString(element.ErrorBlockStyle.Render(f.err.Error())) } return b.String() @@ -205,12 +214,12 @@ func (f *Form) submit() (tea.Model, tea.Cmd) { } if !validator.ValidateEmail(email.Value()) { - f.elements[posEmail] = common.SetErrorStyle(email) + f.elements[posEmail] = element.SetErrorStyle(email) errValidate.AddError("Неверно заполнен email") } if password.Value() == "" { - f.elements[posPassword] = common.SetErrorStyle(password) + f.elements[posPassword] = element.SetErrorStyle(password) errValidate.AddError("Не заполнен пароль") } diff --git a/internal/cli/form/register/form.go b/internal/cli/form/register/form.go index 0e5bca3..f34491e 100644 --- a/internal/cli/form/register/form.go +++ b/internal/cli/form/register/form.go @@ -47,10 +47,10 @@ func NewForm(usecase *register.Usecase) *Form { help: help.New(), header: "Регистрация", elements: []interface{}{ - posEmail: common.CreateDefaultTextInput("Email", emailCharLimit), - posPassword: common.CreateDefaultTextInput("Password", passwordCharLimit), - posSubmitBtn: common.CreateDefaultButton("Регистрация"), - posBackBtn: common.CreateDefaultButton("Назад"), + posEmail: element.CreateDefaultTextInput("Email", emailCharLimit), + posPassword: element.CreateDefaultTextInput("Password", passwordCharLimit), + posSubmitBtn: element.CreateDefaultButton("Регистрация"), + posBackBtn: element.CreateDefaultButton("Назад"), }, usecase: usecase, } @@ -58,8 +58,8 @@ func NewForm(usecase *register.Usecase) *Form { for i := range f.elements { if e, ok := f.elements[i].(textinput.Model); ok { if i == posEmail { - e.TextStyle = common.FocusedStyle - e.PromptStyle = common.FocusedStyle + e.TextStyle = element.FocusedStyle + e.PromptStyle = element.FocusedStyle e.Focus() f.elements[i] = e @@ -83,7 +83,16 @@ func (f *Form) Init() tea.Cmd { } func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - if msg, ok := msg.(tea.KeyMsg); ok { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + for i := range f.elements { + switch e := f.elements[i].(type) { + case textinput.Model: + e.Width = msg.Width + } + } + return f, nil + case tea.KeyMsg: switch { case key.Matches(msg, common.Keys.Quit): return f, tea.Quit @@ -105,12 +114,12 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case textinput.Model: if i == f.pos { e.Focus() - f.elements[i] = common.SetFocusStyle(e) + f.elements[i] = element.SetFocusStyle(e) continue } e.Blur() - f.elements[i] = common.SetNoStyle(e) + f.elements[i] = element.SetNoStyle(e) case element.Button: if i == f.pos { e.Focus() @@ -143,7 +152,7 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (f *Form) View() string { var b strings.Builder - b.WriteString(common.TitleStyle.Render(f.header)) + b.WriteString(element.TitleStyle.Render(f.header)) b.WriteRune('\n') for i := range f.elements { @@ -169,7 +178,7 @@ func (f *Form) View() string { // выводим ошибки валидации if f.err != nil && (errors.As(f.err, &errValidate) || errors.As(f.err, &errForm)) { - b.WriteString(common.ErrorBlockStyle.Render(f.err.Error())) + b.WriteString(element.ErrorBlockStyle.Render(f.err.Error())) b.WriteRune('\n') } @@ -179,7 +188,7 @@ func (f *Form) View() string { // выводим прочие ошибки if f.err != nil && !(errors.As(f.err, &errValidate) || errors.As(f.err, &errForm)) { b.WriteRune('\n') - b.WriteString(common.ErrorBlockStyle.Render(f.err.Error())) + b.WriteString(element.ErrorBlockStyle.Render(f.err.Error())) } return b.String() @@ -200,12 +209,12 @@ func (f *Form) submit() (tea.Model, tea.Cmd) { } if !validator.ValidateEmail(email.Value()) { - f.elements[posEmail] = common.SetErrorStyle(email) + f.elements[posEmail] = element.SetErrorStyle(email) errValidate.AddError("Неправильный email") } if !validator.ValidatePassword(password.Value()) { - f.elements[posPassword] = common.SetErrorStyle(password) + f.elements[posPassword] = element.SetErrorStyle(password) errValidate.AddError("Недостаточно сложный пароль") } diff --git a/internal/cli/model.go b/internal/cli/model.go index 815fa86..3e9d557 100644 --- a/internal/cli/model.go +++ b/internal/cli/model.go @@ -5,17 +5,23 @@ import ( "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" "github.com/bjlag/go-keeper/internal/cli/common" + "github.com/bjlag/go-keeper/internal/cli/element" "github.com/bjlag/go-keeper/internal/cli/form/login" "github.com/bjlag/go-keeper/internal/cli/form/register" "github.com/bjlag/go-keeper/internal/cli/message" ) +const defaultWidth = 20 +const listHeight = 14 + type MainModel struct { help help.Model header string + list list.Model loginForm *login.Form registerForm *register.Form @@ -47,6 +53,9 @@ func (m *MainModel) Init() tea.Cmd { func (m *MainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.list.SetWidth(msg.Width) + return m, nil case tea.KeyMsg: switch { case key.Matches(msg, common.Keys.Quit): @@ -59,24 +68,38 @@ func (m *MainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case message.LoginSuccessMessage: m.accessToken = msg.AccessToken m.refreshToken = msg.RefreshToken + + m.list = element.CreateDefaultList("Категории:", defaultWidth, listHeight, + element.Item("Логины"), + element.Item("Тексты"), + element.Item("Файлы"), + element.Item("Банковские карты"), + ) case message.RegisterSuccessMessage: m.accessToken = msg.AccessToken m.refreshToken = msg.RefreshToken + + m.list = element.CreateDefaultList("Категории:", defaultWidth, listHeight, + element.Item("Логины"), + element.Item("Тексты"), + element.Item("Файлы"), + element.Item("Банковские карты"), + ) } - return m, nil + var cmd tea.Cmd + m.list, cmd = m.list.Update(msg) + + return m, cmd } func (m *MainModel) View() string { var b strings.Builder - b.WriteString(common.TitleStyle.Render(m.header)) + b.WriteString(element.TitleStyle.Render(m.header)) b.WriteRune('\n') - b.WriteString(common.TextStyle.Render("Access token:", m.accessToken)) - b.WriteRune('\n') - b.WriteString(common.TextStyle.Render("Refresh token:", m.accessToken)) - b.WriteRune('\n') + b.WriteString(m.list.View()) return b.String() } From 013cac9d00b720b3b03b8d6906f7977767fe89f1 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Sat, 1 Mar 2025 12:07:20 +0300 Subject: [PATCH 21/94] client: ui passwords --- .gitignore | 5 +- internal/app/client/app.go | 14 ++ internal/cli/element/list.go | 7 +- internal/cli/element/password.go | 9 ++ internal/cli/element/style.go | 1 + internal/cli/form/list/form.go | 155 +++++++++++++++++++++ internal/cli/form/login/form.go | 7 +- internal/cli/form/password/form.go | 215 +++++++++++++++++++++++++++++ internal/cli/form/register/form.go | 2 +- internal/cli/message/message.go | 16 ++- internal/cli/model.go | 58 ++++---- internal/cli/option.go | 20 ++- 12 files changed, 463 insertions(+), 46 deletions(-) create mode 100644 internal/cli/element/password.go create mode 100644 internal/cli/form/list/form.go create mode 100644 internal/cli/form/password/form.go diff --git a/.gitignore b/.gitignore index 335c5c6..48c7c70 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,7 @@ vendor/ # Configs config/migrator.yaml config/server.yaml -config/client.yaml \ No newline at end of file +config/client.yaml + +# log +*.log \ No newline at end of file diff --git a/internal/app/client/app.go b/internal/app/client/app.go index 4f0db11..379e4e1 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -3,6 +3,9 @@ package client import ( "context" "fmt" + "github.com/bjlag/go-keeper/internal/cli/form/list" + "github.com/bjlag/go-keeper/internal/cli/form/password" + "os" tea "github.com/charmbracelet/bubbletea" "go.uber.org/zap" @@ -45,8 +48,19 @@ func (a *App) Run(ctx context.Context) error { model := cli.InitModel( cli.WithLoginForm(formLogin.NewForm(ucLogin)), cli.WithRegisterForm(formRegister.NewForm(ucRegister)), + cli.WithListFormForm(list.NewForm()), + cli.WithShowPasswordForm(password.NewForm()), ) + f, err := tea.LogToFile("debug.log", "debug") + if err != nil { + fmt.Println("fatal:", err) + os.Exit(1) + } + defer func() { + _ = f.Close() + }() + _, err = tea.NewProgram(model, tea.WithAltScreen(), tea.WithContext(ctx)).Run() if err != nil { a.log.Error("failed to run cli program", zap.Error(err)) diff --git a/internal/cli/element/list.go b/internal/cli/element/list.go index 12975d9..f821c7d 100644 --- a/internal/cli/element/list.go +++ b/internal/cli/element/list.go @@ -9,7 +9,10 @@ import ( tea "github.com/charmbracelet/bubbletea" ) -type Item string +type Item struct { + ID string + Name string +} func (i Item) FilterValue() string { return "" } @@ -33,7 +36,7 @@ func (d ItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list return } - str := fmt.Sprintf("%d. %s", index+1, i) + str := fmt.Sprintf("%d. %s", index+1, i.Name) fn := ListItemStyle.Render if index == m.Index() { diff --git a/internal/cli/element/password.go b/internal/cli/element/password.go new file mode 100644 index 0000000..29358ea --- /dev/null +++ b/internal/cli/element/password.go @@ -0,0 +1,9 @@ +package element + +type Password struct { + ID string + Name string + Login string + Password string + Note string +} diff --git a/internal/cli/element/style.go b/internal/cli/element/style.go index cbd98f3..3617e79 100644 --- a/internal/cli/element/style.go +++ b/internal/cli/element/style.go @@ -76,6 +76,7 @@ func CreateDefaultList(title string, with, height int, items ...list.Item) list. l.Title = title l.SetShowStatusBar(false) l.SetFilteringEnabled(false) + //l.SetShowTitle(false) l.Styles.Title = ListTitleStyle l.Styles.PaginationStyle = ListPaginationStyle l.Styles.HelpStyle = ListHelpStyle diff --git a/internal/cli/form/list/form.go b/internal/cli/form/list/form.go new file mode 100644 index 0000000..b1edf43 --- /dev/null +++ b/internal/cli/form/list/form.go @@ -0,0 +1,155 @@ +package list + +import ( + "strings" + + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + + "github.com/bjlag/go-keeper/internal/cli/common" + "github.com/bjlag/go-keeper/internal/cli/element" + "github.com/bjlag/go-keeper/internal/cli/message" +) + +type state int + +const ( + stateShowCategoryList state = iota + stateShowPasswordList +) + +const ( + defaultWidth = 40 + listHeight = 14 +) + +type Form struct { + main tea.Model + help help.Model + state state + header string + categories list.Model + passwords list.Model + err error + + //usecase *login.Usecase +} + +func NewForm() *Form { + f := &Form{ + help: help.New(), + header: "Категории", + categories: element.CreateDefaultList("Категории:", defaultWidth, listHeight), + passwords: element.CreateDefaultList("Пароли:", defaultWidth, listHeight), + + //usecase: usecase, + } + + return f +} + +func (f *Form) SetMainModel(m tea.Model) { + f.main = m +} + +func (f *Form) Init() tea.Cmd { + return nil +} + +func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + f.categories.SetWidth(msg.Width) + f.passwords.SetWidth(msg.Width) + return f, nil + case message.OpenCategoryListFormMessage: + f.state = stateShowCategoryList + + // todo получаем данные из базы + + f.categories.SetItems(nil) + f.categories.InsertItem(len(f.categories.Items()), element.Item{Name: "Логины"}) + f.categories.InsertItem(len(f.categories.Items()), element.Item{Name: "Тексты"}) + f.categories.InsertItem(len(f.categories.Items()), element.Item{Name: "Файлы"}) + f.categories.InsertItem(len(f.categories.Items()), element.Item{Name: "Банковские карты"}) + + return f, nil + case message.OpenPasswordListFormMessage: + f.state = stateShowPasswordList + + // todo получаем данные из базы + + f.passwords.SetItems(nil) + f.passwords.Title = f.categories.SelectedItem().(element.Item).Name + ":" + f.passwords.InsertItem(len(f.categories.Items()), element.Item{Name: "Пароль 1"}) + f.passwords.InsertItem(len(f.categories.Items()), element.Item{Name: "Пароль 2"}) + f.passwords.InsertItem(len(f.categories.Items()), element.Item{Name: "Пароль 3"}) + f.passwords.InsertItem(len(f.categories.Items()), element.Item{Name: "Пароль 4"}) + + return f, nil + case tea.KeyMsg: + switch { + case key.Matches(msg, common.Keys.Quit): + return f, tea.Quit + case key.Matches(msg, common.Keys.Enter): + switch f.state { + case stateShowCategoryList: + if i, ok := f.categories.SelectedItem().(element.Item); ok { + return f.Update(message.OpenPasswordListFormMessage{ + Category: i, + }) + } + case stateShowPasswordList: + if i, ok := f.passwords.SelectedItem().(element.Item); ok { + return f.main.Update(message.OpenPasswordFormMessage{ + Item: i, + }) + } + } + + return f, nil + case key.Matches(msg, common.Keys.Back): + switch f.state { + case stateShowPasswordList: + return f.Update(message.OpenCategoryListFormMessage{}) + } + } + } + + var cmd tea.Cmd + switch f.state { + case stateShowCategoryList: + f.categories, cmd = f.categories.Update(msg) + case stateShowPasswordList: + f.passwords, cmd = f.passwords.Update(msg) + } + + return f, cmd +} + +func (f *Form) View() string { + var b strings.Builder + + b.WriteString(element.TitleStyle.Render(f.header)) + b.WriteRune('\n') + + switch f.state { + case stateShowCategoryList: + b.WriteString(f.categories.View()) + case stateShowPasswordList: + b.WriteString(f.passwords.View()) + } + + //b.WriteRune('\n') + //b.WriteString(f.help.View(common.Keys)) + + // выводим прочие ошибки + if f.err != nil { + b.WriteRune('\n') + b.WriteString(element.ErrorBlockStyle.Render(f.err.Error())) + } + + return b.String() +} diff --git a/internal/cli/form/login/form.go b/internal/cli/form/login/form.go index ab448b0..0e74287 100644 --- a/internal/cli/form/login/form.go +++ b/internal/cli/form/login/form.go @@ -61,11 +61,8 @@ func NewForm(usecase *login.Usecase) *Form { for i := range f.elements { if e, ok := f.elements[i].(textinput.Model); ok { if i == posEmail { - e.TextStyle = element.FocusedStyle - e.PromptStyle = element.FocusedStyle e.Focus() - - f.elements[i] = e + f.elements[i] = element.SetFocusStyle(e) continue } e.EchoMode = textinput.EchoPassword @@ -246,7 +243,7 @@ func (f *Form) submit() (tea.Model, tea.Cmd) { return f, nil } - return f.main.Update(message.LoginSuccessMessage{ + return f.main.Update(message.SuccessLoginMessage{ AccessToken: result.AccessToken, RefreshToken: result.RefreshToken, }) diff --git a/internal/cli/form/password/form.go b/internal/cli/form/password/form.go new file mode 100644 index 0000000..01b1f59 --- /dev/null +++ b/internal/cli/form/password/form.go @@ -0,0 +1,215 @@ +package password + +import ( + "errors" + "strings" + + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/textinput" + tea "github.com/charmbracelet/bubbletea" + + "github.com/bjlag/go-keeper/internal/cli/common" + "github.com/bjlag/go-keeper/internal/cli/element" + "github.com/bjlag/go-keeper/internal/cli/message" + "github.com/bjlag/go-keeper/internal/usecase/client/register" +) + +const ( + posLogin int = iota + posPassword + posEditBtn + posDeleteBtn + posBackBtn +) + +type Form struct { + main tea.Model + help help.Model + header string + elements []interface{} + pos int + err error + + category string + + usecase *register.Usecase +} + +func NewForm() *Form { + f := &Form{ + help: help.New(), + header: "Регистрация", + elements: []interface{}{ + posLogin: element.CreateDefaultTextInput("Login", 50), + posPassword: element.CreateDefaultTextInput("Password", 50), + posEditBtn: element.CreateDefaultButton("Изменить"), + posDeleteBtn: element.CreateDefaultButton("Удалить"), + posBackBtn: element.CreateDefaultButton("Назад"), + }, + //usecase: usecase, + } + + if e, ok := f.elements[posLogin].(textinput.Model); ok { + e.Focus() + f.elements[posLogin] = element.SetFocusStyle(e) + } + + return f +} + +func (f *Form) SetMainModel(m tea.Model) { + f.main = m +} + +func (f *Form) Init() tea.Cmd { + return nil +} + +func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + for i := range f.elements { + switch e := f.elements[i].(type) { + case textinput.Model: + e.Width = msg.Width + } + } + return f, nil + case message.OpenPasswordFormMessage: + // todo получаем данные из базы + + f.header = msg.Item.Name + f.category = "Категория" + + if input, ok := f.elements[posLogin].(textinput.Model); ok { + input.SetValue("login") + f.elements[posLogin] = input + } + + if input, ok := f.elements[posPassword].(textinput.Model); ok { + input.SetValue("password") + f.elements[posPassword] = input + } + + return f, nil + case tea.KeyMsg: + switch { + case key.Matches(msg, common.Keys.Quit): + return f, tea.Quit + case key.Matches(msg, common.Keys.Navigation): + if key.Matches(msg, common.Keys.Down, common.Keys.Tab) { + f.pos++ + } else { + f.pos-- + } + + if f.pos > len(f.elements)-1 { + f.pos = 0 + } else if f.pos < 0 { + f.pos = len(f.elements) - 1 + } + + for i := range f.elements { + switch e := f.elements[i].(type) { + case textinput.Model: + if i == f.pos { + e.Focus() + f.elements[i] = element.SetFocusStyle(e) + continue + } + + e.Blur() + f.elements[i] = element.SetNoStyle(e) + case element.Button: + if i == f.pos { + e.Focus() + f.elements[i] = e + continue + } + e.Blur() + f.elements[i] = e + } + } + + return f, nil + case key.Matches(msg, common.Keys.Enter): + f.err = nil + + switch { + case f.pos == posBackBtn: + return f.main.Update(message.OpenPasswordListFormMessage{}) + } + + return f, nil + case key.Matches(msg, common.Keys.Back): + return f.main.Update(message.OpenPasswordListFormMessage{}) + } + } + + return f, f.updateInputs(msg) +} + +func (f *Form) View() string { + var b strings.Builder + + b.WriteString(element.TitleStyle.Render(f.header)) + b.WriteRune('\n') + + b.WriteString("Категория: ") + b.WriteString(f.category) + b.WriteRune('\n') + + for i := range f.elements { + if e, ok := f.elements[i].(textinput.Model); ok { + b.WriteString(e.Placeholder) + b.WriteRune('\n') + b.WriteString(e.View()) + b.WriteRune('\n') + b.WriteRune('\n') + } + } + + b.WriteRune('\n') + + for i := range f.elements { + if e, ok := f.elements[i].(element.Button); ok { + b.WriteString(e.String()) + b.WriteRune('\n') + } + } + + var ( + errValidate *common.ValidateError + errForm *common.FormError + ) + + // выводим ошибки валидации + if f.err != nil && (errors.As(f.err, &errValidate) || errors.As(f.err, &errForm)) { + b.WriteString(element.ErrorBlockStyle.Render(f.err.Error())) + b.WriteRune('\n') + } + + b.WriteRune('\n') + b.WriteString(f.help.View(common.Keys)) + + // выводим прочие ошибки + if f.err != nil && !(errors.As(f.err, &errValidate) || errors.As(f.err, &errForm)) { + b.WriteRune('\n') + b.WriteString(element.ErrorBlockStyle.Render(f.err.Error())) + } + + return b.String() +} + +func (f *Form) updateInputs(msg tea.Msg) tea.Cmd { + cmds := make([]tea.Cmd, len(f.elements)) + + for i := range f.elements { + if m, ok := f.elements[i].(textinput.Model); ok { + f.elements[i], cmds[i] = m.Update(msg) + } + } + + return tea.Batch(cmds...) +} diff --git a/internal/cli/form/register/form.go b/internal/cli/form/register/form.go index f34491e..1c249f9 100644 --- a/internal/cli/form/register/form.go +++ b/internal/cli/form/register/form.go @@ -241,7 +241,7 @@ func (f *Form) submit() (tea.Model, tea.Cmd) { return f, nil } - return f.main.Update(message.RegisterSuccessMessage{ + return f.main.Update(message.SuccessRegisterMessage{ AccessToken: result.AccessToken, RefreshToken: result.RefreshToken, }) diff --git a/internal/cli/message/message.go b/internal/cli/message/message.go index 9a5f612..425ddbe 100644 --- a/internal/cli/message/message.go +++ b/internal/cli/message/message.go @@ -1,11 +1,13 @@ package message -type LoginSuccessMessage struct { +import "github.com/bjlag/go-keeper/internal/cli/element" + +type SuccessLoginMessage struct { AccessToken string RefreshToken string } -type RegisterSuccessMessage struct { +type SuccessRegisterMessage struct { AccessToken string RefreshToken string } @@ -13,3 +15,13 @@ type RegisterSuccessMessage struct { type OpenLoginFormMessage struct{} type OpenRegisterFormMessage struct{} + +type OpenCategoryListFormMessage struct{} + +type OpenPasswordFormMessage struct { + Item element.Item +} + +type OpenPasswordListFormMessage struct { + Category element.Item +} diff --git a/internal/cli/model.go b/internal/cli/model.go index 3e9d557..1996bda 100644 --- a/internal/cli/model.go +++ b/internal/cli/model.go @@ -5,26 +5,25 @@ import ( "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/element" + listf "github.com/bjlag/go-keeper/internal/cli/form/list" "github.com/bjlag/go-keeper/internal/cli/form/login" + "github.com/bjlag/go-keeper/internal/cli/form/password" "github.com/bjlag/go-keeper/internal/cli/form/register" "github.com/bjlag/go-keeper/internal/cli/message" ) -const defaultWidth = 20 -const listHeight = 14 - type MainModel struct { help help.Model header string - list list.Model - loginForm *login.Form - registerForm *register.Form + formLogin *login.Form + formRegister *register.Form + formList *listf.Form + formPassword *password.Form accessToken string refreshToken string @@ -46,51 +45,46 @@ func InitModel(opts ...Option) *MainModel { func (m *MainModel) Init() tea.Cmd { return tea.Batch( func() tea.Msg { - return message.OpenLoginFormMessage{} + return message.SuccessLoginMessage{} + //return message.OpenLoginFormMessage{} }, ) } func (m *MainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { - case tea.WindowSizeMsg: - m.list.SetWidth(msg.Width) - return m, nil case tea.KeyMsg: switch { case key.Matches(msg, common.Keys.Quit): return m, tea.Quit } + + // Forms case message.OpenLoginFormMessage: - return m.loginForm.Update(tea.ClearScreen()) + return m.formLogin.Update(tea.ClearScreen()) case message.OpenRegisterFormMessage: - return m.registerForm.Update(tea.ClearScreen()) - case message.LoginSuccessMessage: + return m.formRegister.Update(tea.ClearScreen()) + case message.OpenCategoryListFormMessage: + return m.formList.Update(msg) + case message.OpenPasswordListFormMessage: + return m.formList.Update(msg) + case message.OpenPasswordFormMessage: + return m.formPassword.Update(msg) + + // Success + case message.SuccessLoginMessage: m.accessToken = msg.AccessToken m.refreshToken = msg.RefreshToken - m.list = element.CreateDefaultList("Категории:", defaultWidth, listHeight, - element.Item("Логины"), - element.Item("Тексты"), - element.Item("Файлы"), - element.Item("Банковские карты"), - ) - case message.RegisterSuccessMessage: + return m.Update(message.OpenCategoryListFormMessage{}) + case message.SuccessRegisterMessage: m.accessToken = msg.AccessToken m.refreshToken = msg.RefreshToken - m.list = element.CreateDefaultList("Категории:", defaultWidth, listHeight, - element.Item("Логины"), - element.Item("Тексты"), - element.Item("Файлы"), - element.Item("Банковские карты"), - ) + return m.Update(message.OpenCategoryListFormMessage{}) } - var cmd tea.Cmd - m.list, cmd = m.list.Update(msg) - - return m, cmd + return m, nil } func (m *MainModel) View() string { @@ -99,7 +93,5 @@ func (m *MainModel) View() string { b.WriteString(element.TitleStyle.Render(m.header)) b.WriteRune('\n') - b.WriteString(m.list.View()) - return b.String() } diff --git a/internal/cli/option.go b/internal/cli/option.go index e42b6c6..94bf741 100644 --- a/internal/cli/option.go +++ b/internal/cli/option.go @@ -1,7 +1,9 @@ package cli import ( + "github.com/bjlag/go-keeper/internal/cli/form/list" "github.com/bjlag/go-keeper/internal/cli/form/login" + "github.com/bjlag/go-keeper/internal/cli/form/password" "github.com/bjlag/go-keeper/internal/cli/form/register" ) @@ -10,13 +12,27 @@ type Option func(*MainModel) func WithLoginForm(form *login.Form) Option { return func(m *MainModel) { form.SetMainModel(m) - m.loginForm = form + m.formLogin = form } } func WithRegisterForm(form *register.Form) Option { return func(m *MainModel) { form.SetMainModel(m) - m.registerForm = form + m.formRegister = form + } +} + +func WithListFormForm(form *list.Form) Option { + return func(m *MainModel) { + form.SetMainModel(m) + m.formList = form + } +} + +func WithShowPasswordForm(form *password.Form) Option { + return func(m *MainModel) { + form.SetMainModel(m) + m.formPassword = form } } From 3f0dcc3a01d9864b2a5a59fb93adf185b256c7ea Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Sat, 1 Mar 2025 23:59:21 +0300 Subject: [PATCH 22/94] server: create `data` table --- ...able.up.sql => 001_create_users_table.up.sql} | 0 migrations/002_create_data_table.up.sql | 16 ++++++++++++++++ 2 files changed, 16 insertions(+) rename migrations/{1_create_users_table.up.sql => 001_create_users_table.up.sql} (100%) create mode 100644 migrations/002_create_data_table.up.sql diff --git a/migrations/1_create_users_table.up.sql b/migrations/001_create_users_table.up.sql similarity index 100% rename from migrations/1_create_users_table.up.sql rename to migrations/001_create_users_table.up.sql diff --git a/migrations/002_create_data_table.up.sql b/migrations/002_create_data_table.up.sql new file mode 100644 index 0000000..c7da468 --- /dev/null +++ b/migrations/002_create_data_table.up.sql @@ -0,0 +1,16 @@ +CREATE TABLE IF NOT EXISTS data ( + guid uuid PRIMARY KEY NOT NULL, + user_guid uuid NOT NULL REFERENCES users (guid) ON DELETE RESTRICT, + encrypted_data text NOT NULL, + created_at timestamptz NOT NULL DEFAULT NOW(), + updated_at timestamptz NOT NULL DEFAULT NOW() +); + +CREATE INDEX data_user_guid_idx ON data (user_guid); + +COMMENT ON TABLE data IS 'Данные (пароли, логины, тексты и пр.)'; +COMMENT ON COLUMN data.guid IS 'GUID'; +COMMENT ON COLUMN data.user_guid IS 'Владелец'; +COMMENT ON COLUMN data.encrypted_data IS 'Сами данные в зашифрованном виде'; +COMMENT ON COLUMN data.created_at IS 'Дата создания записи'; +COMMENT ON COLUMN data.updated_at IS 'Дата изменения записи'; \ No newline at end of file From be9e10617ea27a7fbde48d8e127c8f54ff77d05a Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Sun, 2 Mar 2025 14:38:38 +0300 Subject: [PATCH 23/94] server: rpc `get_all_data` --- internal/app/server/app.go | 6 + internal/domain/data/data.go | 24 ++ internal/generated/rpc/keeper.pb.go | 314 ++++++++++++++---- internal/generated/rpc/keeper_grpc.pb.go | 42 +++ internal/infrastructure/rpc/server/method.go | 15 + internal/infrastructure/store/data/model.go | 35 ++ internal/infrastructure/store/data/store.go | 40 +++ internal/rpc/get_all_data/contract.go | 11 + internal/rpc/get_all_data/handler.go | 77 +++++ .../usecase/server/data/get_all/contract.go | 13 + internal/usecase/server/data/get_all/model.go | 24 ++ .../usecase/server/data/get_all/usecase.go | 48 +++ internal/usecase/server/user/login/usecase.go | 2 +- .../server/user/refresh_tokens/usecase.go | 2 +- .../usecase/server/user/register/usecase.go | 2 +- proto/keeper.proto | 24 ++ 16 files changed, 619 insertions(+), 60 deletions(-) create mode 100644 internal/domain/data/data.go create mode 100644 internal/infrastructure/store/data/model.go create mode 100644 internal/infrastructure/store/data/store.go create mode 100644 internal/rpc/get_all_data/contract.go create mode 100644 internal/rpc/get_all_data/handler.go create mode 100644 internal/usecase/server/data/get_all/contract.go create mode 100644 internal/usecase/server/data/get_all/model.go create mode 100644 internal/usecase/server/data/get_all/usecase.go diff --git a/internal/app/server/app.go b/internal/app/server/app.go index f7000ef..0a59c85 100644 --- a/internal/app/server/app.go +++ b/internal/app/server/app.go @@ -9,10 +9,13 @@ import ( "github.com/bjlag/go-keeper/internal/infrastructure/auth/jwt" "github.com/bjlag/go-keeper/internal/infrastructure/db/pg" "github.com/bjlag/go-keeper/internal/infrastructure/rpc/server" + "github.com/bjlag/go-keeper/internal/infrastructure/store/data" "github.com/bjlag/go-keeper/internal/infrastructure/store/user" + rpcGetAllData "github.com/bjlag/go-keeper/internal/rpc/get_all_data" rpcLogin "github.com/bjlag/go-keeper/internal/rpc/login" rpcRefreshTokens "github.com/bjlag/go-keeper/internal/rpc/refresh_tokens" rpcRegister "github.com/bjlag/go-keeper/internal/rpc/register" + "github.com/bjlag/go-keeper/internal/usecase/server/data/get_all" "github.com/bjlag/go-keeper/internal/usecase/server/user/login" rt "github.com/bjlag/go-keeper/internal/usecase/server/user/refresh_tokens" "github.com/bjlag/go-keeper/internal/usecase/server/user/register" @@ -44,11 +47,13 @@ func (a *App) Run(ctx context.Context) error { }() userStore := user.NewStore(db) + dataStore := data.NewStore(db) tokeGenerator := jwt.NewGenerator(a.cfg.Auth.SecretKey, a.cfg.Auth.AccessTokenExp, a.cfg.Auth.RefreshTokenExp) ucRegister := register.NewUsecase(userStore, tokeGenerator) ucLogin := login.NewUsecase(userStore, tokeGenerator) ucRefreshTokens := rt.NewUsecase(userStore, tokeGenerator) + ucGetAllData := get_all.NewUsecase(dataStore) s := server.NewRPCServer( server.WithAddress(a.cfg.Address.Host, a.cfg.Address.Port), @@ -57,6 +62,7 @@ func (a *App) Run(ctx context.Context) error { server.WithHandler(server.RegisterMethod, rpcRegister.New(ucRegister).Handle), server.WithHandler(server.LoginMethod, rpcLogin.New(ucLogin).Handle), server.WithHandler(server.RefreshTokensMethod, rpcRefreshTokens.New(ucRefreshTokens).Handle), + server.WithHandler(server.GetAllDataMethod, rpcGetAllData.New(ucGetAllData).Handle), ) err = s.Start(ctx) diff --git a/internal/domain/data/data.go b/internal/domain/data/data.go new file mode 100644 index 0000000..31320a1 --- /dev/null +++ b/internal/domain/data/data.go @@ -0,0 +1,24 @@ +package data + +import ( + "time" + + "github.com/google/uuid" +) + +type Category int + +const ( + CategoryLogin Category = iota + CategoryText + CategoryFile + CategoryBankCard +) + +type Data struct { + GUID uuid.UUID + UserGUID uuid.UUID + EncryptedData []byte + CreatedAt time.Time + UpdatedAt time.Time +} diff --git a/internal/generated/rpc/keeper.pb.go b/internal/generated/rpc/keeper.pb.go index b23ff8d..2df6e86 100644 --- a/internal/generated/rpc/keeper.pb.go +++ b/internal/generated/rpc/keeper.pb.go @@ -9,6 +9,7 @@ package rpc import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" unsafe "unsafe" @@ -21,6 +22,7 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +// auth type RegisterIn struct { state protoimpl.MessageState `protogen:"open.v1"` Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"` @@ -325,52 +327,241 @@ func (x *RefreshTokensOut) GetRefreshToken() string { return "" } +// data +type GetAllDataIn struct { + state protoimpl.MessageState `protogen:"open.v1"` + Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` + Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetAllDataIn) Reset() { + *x = GetAllDataIn{} + mi := &file_proto_keeper_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetAllDataIn) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAllDataIn) ProtoMessage() {} + +func (x *GetAllDataIn) ProtoReflect() protoreflect.Message { + mi := &file_proto_keeper_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetAllDataIn.ProtoReflect.Descriptor instead. +func (*GetAllDataIn) Descriptor() ([]byte, []int) { + return file_proto_keeper_proto_rawDescGZIP(), []int{6} +} + +func (x *GetAllDataIn) GetLimit() uint32 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *GetAllDataIn) GetOffset() uint32 { + if x != nil { + return x.Offset + } + return 0 +} + +type GetAllDataOut struct { + state protoimpl.MessageState `protogen:"open.v1"` + Items []*Item `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetAllDataOut) Reset() { + *x = GetAllDataOut{} + mi := &file_proto_keeper_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetAllDataOut) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAllDataOut) ProtoMessage() {} + +func (x *GetAllDataOut) ProtoReflect() protoreflect.Message { + mi := &file_proto_keeper_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetAllDataOut.ProtoReflect.Descriptor instead. +func (*GetAllDataOut) Descriptor() ([]byte, []int) { + return file_proto_keeper_proto_rawDescGZIP(), []int{7} +} + +func (x *GetAllDataOut) GetItems() []*Item { + if x != nil { + return x.Items + } + return nil +} + +type Item struct { + state protoimpl.MessageState `protogen:"open.v1"` + Guid string `protobuf:"bytes,1,opt,name=guid,proto3" json:"guid,omitempty"` + EncryptedData []byte `protobuf:"bytes,2,opt,name=encryptedData,proto3" json:"encryptedData,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=CreatedAt,proto3" json:"CreatedAt,omitempty"` + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=UpdatedAt,proto3" json:"UpdatedAt,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Item) Reset() { + *x = Item{} + mi := &file_proto_keeper_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Item) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Item) ProtoMessage() {} + +func (x *Item) ProtoReflect() protoreflect.Message { + mi := &file_proto_keeper_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Item.ProtoReflect.Descriptor instead. +func (*Item) Descriptor() ([]byte, []int) { + return file_proto_keeper_proto_rawDescGZIP(), []int{8} +} + +func (x *Item) GetGuid() string { + if x != nil { + return x.Guid + } + return "" +} + +func (x *Item) GetEncryptedData() []byte { + if x != nil { + return x.EncryptedData + } + return nil +} + +func (x *Item) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +func (x *Item) GetUpdatedAt() *timestamppb.Timestamp { + if x != nil { + return x.UpdatedAt + } + return nil +} + var File_proto_keeper_proto protoreflect.FileDescriptor var file_proto_keeper_proto_rawDesc = string([]byte{ 0x0a, 0x12, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x22, 0x3e, 0x0a, 0x0a, - 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, - 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, - 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x55, 0x0a, 0x0b, - 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x61, - 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, - 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x22, 0x3b, 0x0a, 0x07, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x12, 0x14, - 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, - 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, - 0x22, 0x52, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x4f, 0x75, 0x74, 0x12, 0x21, 0x0a, 0x0c, + 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 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, 0x3e, 0x0a, + 0x0a, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x65, + 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, + 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x55, 0x0a, + 0x0b, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x36, 0x0a, 0x0f, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x49, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, - 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, - 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x5a, 0x0a, 0x10, - 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x4f, 0x75, 0x74, - 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, - 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, - 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x32, 0xad, 0x01, 0x0a, 0x06, 0x4b, 0x65, 0x65, - 0x70, 0x65, 0x72, 0x12, 0x33, 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, - 0x12, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, - 0x72, 0x49, 0x6e, 0x1a, 0x13, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, - 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x12, 0x2a, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, - 0x6e, 0x12, 0x0f, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, - 0x49, 0x6e, 0x1a, 0x10, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, - 0x6e, 0x4f, 0x75, 0x74, 0x12, 0x42, 0x0a, 0x0d, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x17, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, - 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x49, 0x6e, 0x1a, 0x18, - 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x4f, 0x75, 0x74, 0x42, 0x18, 0x5a, 0x16, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x72, - 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x3b, 0x0a, 0x07, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x12, + 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, + 0x64, 0x22, 0x52, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x4f, 0x75, 0x74, 0x12, 0x21, 0x0a, + 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x36, 0x0a, 0x0f, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x49, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, + 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x5a, 0x0a, + 0x10, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x4f, 0x75, + 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, + 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x3c, 0x0a, 0x0c, 0x47, 0x65, 0x74, + 0x41, 0x6c, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x49, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, + 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, + 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x22, 0x33, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x41, 0x6c, + 0x6c, 0x44, 0x61, 0x74, 0x61, 0x4f, 0x75, 0x74, 0x12, 0x22, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, + 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0xb4, 0x01, 0x0a, + 0x04, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x65, 0x6e, 0x63, + 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x12, + 0x38, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x03, 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, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x64, 0x41, 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, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x41, 0x74, 0x32, 0xe8, 0x01, 0x0a, 0x06, 0x4b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x12, 0x33, + 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x12, 0x2e, 0x6b, 0x65, 0x65, + 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x1a, 0x13, + 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, + 0x4f, 0x75, 0x74, 0x12, 0x2a, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x0f, 0x2e, 0x6b, + 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x1a, 0x10, 0x2e, + 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x4f, 0x75, 0x74, 0x12, + 0x42, 0x0a, 0x0d, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, + 0x12, 0x17, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, + 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x49, 0x6e, 0x1a, 0x18, 0x2e, 0x6b, 0x65, 0x65, 0x70, + 0x65, 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, + 0x4f, 0x75, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x44, 0x61, 0x74, + 0x61, 0x12, 0x14, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, + 0x6c, 0x44, 0x61, 0x74, 0x61, 0x49, 0x6e, 0x1a, 0x15, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, + 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x4f, 0x75, 0x74, 0x42, 0x18, + 0x5a, 0x16, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, + 0x61, 0x74, 0x65, 0x64, 0x2f, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -385,27 +576,36 @@ func file_proto_keeper_proto_rawDescGZIP() []byte { return file_proto_keeper_proto_rawDescData } -var file_proto_keeper_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_proto_keeper_proto_msgTypes = make([]protoimpl.MessageInfo, 9) var file_proto_keeper_proto_goTypes = []any{ - (*RegisterIn)(nil), // 0: keeper.RegisterIn - (*RegisterOut)(nil), // 1: keeper.RegisterOut - (*LoginIn)(nil), // 2: keeper.LoginIn - (*LoginOut)(nil), // 3: keeper.LoginOut - (*RefreshTokensIn)(nil), // 4: keeper.RefreshTokensIn - (*RefreshTokensOut)(nil), // 5: keeper.RefreshTokensOut + (*RegisterIn)(nil), // 0: keeper.RegisterIn + (*RegisterOut)(nil), // 1: keeper.RegisterOut + (*LoginIn)(nil), // 2: keeper.LoginIn + (*LoginOut)(nil), // 3: keeper.LoginOut + (*RefreshTokensIn)(nil), // 4: keeper.RefreshTokensIn + (*RefreshTokensOut)(nil), // 5: keeper.RefreshTokensOut + (*GetAllDataIn)(nil), // 6: keeper.GetAllDataIn + (*GetAllDataOut)(nil), // 7: keeper.GetAllDataOut + (*Item)(nil), // 8: keeper.Item + (*timestamppb.Timestamp)(nil), // 9: google.protobuf.Timestamp } var file_proto_keeper_proto_depIdxs = []int32{ - 0, // 0: keeper.Keeper.Register:input_type -> keeper.RegisterIn - 2, // 1: keeper.Keeper.Login:input_type -> keeper.LoginIn - 4, // 2: keeper.Keeper.RefreshTokens:input_type -> keeper.RefreshTokensIn - 1, // 3: keeper.Keeper.Register:output_type -> keeper.RegisterOut - 3, // 4: keeper.Keeper.Login:output_type -> keeper.LoginOut - 5, // 5: keeper.Keeper.RefreshTokens:output_type -> keeper.RefreshTokensOut - 3, // [3:6] is the sub-list for method output_type - 0, // [0:3] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 8, // 0: keeper.GetAllDataOut.items:type_name -> keeper.Item + 9, // 1: keeper.Item.CreatedAt:type_name -> google.protobuf.Timestamp + 9, // 2: keeper.Item.UpdatedAt:type_name -> google.protobuf.Timestamp + 0, // 3: keeper.Keeper.Register:input_type -> keeper.RegisterIn + 2, // 4: keeper.Keeper.Login:input_type -> keeper.LoginIn + 4, // 5: keeper.Keeper.RefreshTokens:input_type -> keeper.RefreshTokensIn + 6, // 6: keeper.Keeper.GetAllData:input_type -> keeper.GetAllDataIn + 1, // 7: keeper.Keeper.Register:output_type -> keeper.RegisterOut + 3, // 8: keeper.Keeper.Login:output_type -> keeper.LoginOut + 5, // 9: keeper.Keeper.RefreshTokens:output_type -> keeper.RefreshTokensOut + 7, // 10: keeper.Keeper.GetAllData:output_type -> keeper.GetAllDataOut + 7, // [7:11] is the sub-list for method output_type + 3, // [3:7] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name } func init() { file_proto_keeper_proto_init() } @@ -419,7 +619,7 @@ func file_proto_keeper_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_keeper_proto_rawDesc), len(file_proto_keeper_proto_rawDesc)), NumEnums: 0, - NumMessages: 6, + NumMessages: 9, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/generated/rpc/keeper_grpc.pb.go b/internal/generated/rpc/keeper_grpc.pb.go index 8a556f1..b689bde 100644 --- a/internal/generated/rpc/keeper_grpc.pb.go +++ b/internal/generated/rpc/keeper_grpc.pb.go @@ -22,15 +22,19 @@ const ( Keeper_Register_FullMethodName = "/keeper.Keeper/Register" Keeper_Login_FullMethodName = "/keeper.Keeper/Login" Keeper_RefreshTokens_FullMethodName = "/keeper.Keeper/RefreshTokens" + Keeper_GetAllData_FullMethodName = "/keeper.Keeper/GetAllData" ) // KeeperClient is the client API for Keeper 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 KeeperClient interface { + // auth Register(ctx context.Context, in *RegisterIn, opts ...grpc.CallOption) (*RegisterOut, error) Login(ctx context.Context, in *LoginIn, opts ...grpc.CallOption) (*LoginOut, error) RefreshTokens(ctx context.Context, in *RefreshTokensIn, opts ...grpc.CallOption) (*RefreshTokensOut, error) + // data + GetAllData(ctx context.Context, in *GetAllDataIn, opts ...grpc.CallOption) (*GetAllDataOut, error) } type keeperClient struct { @@ -71,13 +75,26 @@ func (c *keeperClient) RefreshTokens(ctx context.Context, in *RefreshTokensIn, o return out, nil } +func (c *keeperClient) GetAllData(ctx context.Context, in *GetAllDataIn, opts ...grpc.CallOption) (*GetAllDataOut, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetAllDataOut) + err := c.cc.Invoke(ctx, Keeper_GetAllData_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // KeeperServer is the server API for Keeper service. // All implementations should embed UnimplementedKeeperServer // for forward compatibility. type KeeperServer interface { + // auth Register(context.Context, *RegisterIn) (*RegisterOut, error) Login(context.Context, *LoginIn) (*LoginOut, error) RefreshTokens(context.Context, *RefreshTokensIn) (*RefreshTokensOut, error) + // data + GetAllData(context.Context, *GetAllDataIn) (*GetAllDataOut, error) } // UnimplementedKeeperServer should be embedded to have @@ -96,6 +113,9 @@ func (UnimplementedKeeperServer) Login(context.Context, *LoginIn) (*LoginOut, er func (UnimplementedKeeperServer) RefreshTokens(context.Context, *RefreshTokensIn) (*RefreshTokensOut, error) { return nil, status.Errorf(codes.Unimplemented, "method RefreshTokens not implemented") } +func (UnimplementedKeeperServer) GetAllData(context.Context, *GetAllDataIn) (*GetAllDataOut, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetAllData not implemented") +} func (UnimplementedKeeperServer) testEmbeddedByValue() {} // UnsafeKeeperServer may be embedded to opt out of forward compatibility for this service. @@ -170,6 +190,24 @@ func _Keeper_RefreshTokens_Handler(srv interface{}, ctx context.Context, dec fun return interceptor(ctx, in, info, handler) } +func _Keeper_GetAllData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetAllDataIn) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(KeeperServer).GetAllData(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Keeper_GetAllData_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(KeeperServer).GetAllData(ctx, req.(*GetAllDataIn)) + } + return interceptor(ctx, in, info, handler) +} + // Keeper_ServiceDesc is the grpc.ServiceDesc for Keeper service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -189,6 +227,10 @@ var Keeper_ServiceDesc = grpc.ServiceDesc{ MethodName: "RefreshTokens", Handler: _Keeper_RefreshTokens_Handler, }, + { + MethodName: "GetAllData", + Handler: _Keeper_GetAllData_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "proto/keeper.proto", diff --git a/internal/infrastructure/rpc/server/method.go b/internal/infrastructure/rpc/server/method.go index 7d65d25..2ff626f 100644 --- a/internal/infrastructure/rpc/server/method.go +++ b/internal/infrastructure/rpc/server/method.go @@ -13,6 +13,7 @@ const ( RegisterMethod = "Register" LoginMethod = "Login" RefreshTokensMethod = "RefreshTokens" + GetAllDataMethod = "GetAllData" ) func (s RPCServer) Register(ctx context.Context, in *pb.RegisterIn) (*pb.RegisterOut, error) { @@ -57,6 +58,20 @@ func (s RPCServer) RefreshTokens(ctx context.Context, in *pb.RefreshTokensIn) (* return h(ctx, in) } +func (s RPCServer) GetAllData(ctx context.Context, in *pb.GetAllDataIn) (*pb.GetAllDataOut, error) { + handler, err := s.getHandler(GetAllDataMethod) + if err != nil { + return nil, err + } + + h, ok := handler.(func(context.Context, *pb.GetAllDataIn) (*pb.GetAllDataOut, error)) + if !ok { + return nil, status.Errorf(codes.Internal, "handler for %s method not found", GetAllDataMethod) + } + + return h(ctx, in) +} + func (s RPCServer) getHandler(name string) (any, error) { handler, ok := s.handlers[name] if !ok { diff --git a/internal/infrastructure/store/data/model.go b/internal/infrastructure/store/data/model.go new file mode 100644 index 0000000..bfa15d4 --- /dev/null +++ b/internal/infrastructure/store/data/model.go @@ -0,0 +1,35 @@ +package data + +import ( + "time" + + "github.com/google/uuid" + + "github.com/bjlag/go-keeper/internal/domain/data" +) + +type Row struct { + GUID uuid.UUID `db:"guid"` + UserGUID uuid.UUID `db:"user_guid"` + EncryptedData []byte `db:"encrypted_data"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` +} + +func (r *Row) convertToModel() data.Data { + return data.Data{ + GUID: r.GUID, + UserGUID: r.UserGUID, + EncryptedData: r.EncryptedData, + CreatedAt: r.CreatedAt, + UpdatedAt: r.UpdatedAt, + } +} + +func convertToModels(rows []Row) []data.Data { + result := make([]data.Data, 0, len(rows)) + for _, row := range rows { + result = append(result, row.convertToModel()) + } + return result +} diff --git a/internal/infrastructure/store/data/store.go b/internal/infrastructure/store/data/store.go new file mode 100644 index 0000000..2efc3a5 --- /dev/null +++ b/internal/infrastructure/store/data/store.go @@ -0,0 +1,40 @@ +package data + +import ( + "context" + "fmt" + + "github.com/google/uuid" + "github.com/jmoiron/sqlx" + + model "github.com/bjlag/go-keeper/internal/domain/data" +) + +type Store struct { + db *sqlx.DB +} + +func NewStore(db *sqlx.DB) *Store { + return &Store{ + db: db, + } +} + +func (s *Store) GetAllByUser(ctx context.Context, userGUID uuid.UUID, limit, offset uint32) ([]model.Data, error) { + const op = "store.data.GetAllByUser" + + query := ` + SELECT guid, user_guid, encrypted_data, created_at, updated_at + FROM data + WHERE user_guid = $1 + LIMIT $2 + OFFSET $3 + ` + + var rows []Row + if err := s.db.SelectContext(ctx, &rows, query, userGUID, limit, offset); err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + + return convertToModels(rows), nil +} diff --git a/internal/rpc/get_all_data/contract.go b/internal/rpc/get_all_data/contract.go new file mode 100644 index 0000000..3001a32 --- /dev/null +++ b/internal/rpc/get_all_data/contract.go @@ -0,0 +1,11 @@ +package get_all_data + +import ( + "context" + + "github.com/bjlag/go-keeper/internal/usecase/server/data/get_all" +) + +type usecase interface { + Do(ctx context.Context, data get_all.Data) (*get_all.Result, error) +} diff --git a/internal/rpc/get_all_data/handler.go b/internal/rpc/get_all_data/handler.go new file mode 100644 index 0000000..cd8be62 --- /dev/null +++ b/internal/rpc/get_all_data/handler.go @@ -0,0 +1,77 @@ +package get_all_data + +import ( + "context" + "errors" + + "github.com/google/uuid" + "go.uber.org/zap" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/timestamppb" + + pb "github.com/bjlag/go-keeper/internal/generated/rpc" + "github.com/bjlag/go-keeper/internal/infrastructure/logger" + "github.com/bjlag/go-keeper/internal/usecase/server/data/get_all" +) + +const ( + limitDefault = 40 + limitMax = 100 +) + +type Handler struct { + usecase usecase +} + +func New(usecase usecase) *Handler { + return &Handler{ + usecase: usecase, + } +} + +func (h *Handler) Handle(ctx context.Context, in *pb.GetAllDataIn) (*pb.GetAllDataOut, error) { + log := logger.FromCtx(ctx) + + if in.GetLimit() > limitMax { + in.Limit = limitMax + } + + if in.GetLimit() <= 0 { + in.Limit = limitDefault + } + + if in.GetOffset() <= 0 { + in.Offset = 0 + } + + // todo проверить авторизацию + // todo достать GUID пользователя из контекста + userGUID := uuid.New() + + result, err := h.usecase.Do(ctx, get_all.Data{ + UserGUID: userGUID, + Limit: in.Limit, + Offset: in.Offset, + }) + if err != nil { + if !errors.Is(err, get_all.ErrNoData) { + log.Error("Failed to get all data", zap.Error(err)) + return nil, status.Error(codes.Internal, "internal error") + } + } + + itemsOut := make([]*pb.Item, 0, len(result.Items)) + for _, item := range result.Items { + itemsOut = append(itemsOut, &pb.Item{ + Guid: item.GUID.String(), + EncryptedData: item.EncryptedData, + CreatedAt: timestamppb.New(item.CreatedAt), + UpdatedAt: timestamppb.New(item.CreatedAt), + }) + } + + return &pb.GetAllDataOut{ + Items: itemsOut, + }, nil +} diff --git a/internal/usecase/server/data/get_all/contract.go b/internal/usecase/server/data/get_all/contract.go new file mode 100644 index 0000000..9763b56 --- /dev/null +++ b/internal/usecase/server/data/get_all/contract.go @@ -0,0 +1,13 @@ +package get_all + +import ( + "context" + + "github.com/google/uuid" + + model "github.com/bjlag/go-keeper/internal/domain/data" +) + +type dataStore interface { + GetAllByUser(ctx context.Context, userGUID uuid.UUID, limit, offset uint32) ([]model.Data, error) +} diff --git a/internal/usecase/server/data/get_all/model.go b/internal/usecase/server/data/get_all/model.go new file mode 100644 index 0000000..0b2c544 --- /dev/null +++ b/internal/usecase/server/data/get_all/model.go @@ -0,0 +1,24 @@ +package get_all + +import ( + "time" + + "github.com/google/uuid" +) + +type Data struct { + UserGUID uuid.UUID + Limit uint32 + Offset uint32 +} + +type Result struct { + Items []Item +} + +type Item struct { + GUID uuid.UUID + EncryptedData []byte + CreatedAt time.Time + UpdatedAt time.Time +} diff --git a/internal/usecase/server/data/get_all/usecase.go b/internal/usecase/server/data/get_all/usecase.go new file mode 100644 index 0000000..46df6f7 --- /dev/null +++ b/internal/usecase/server/data/get_all/usecase.go @@ -0,0 +1,48 @@ +package get_all + +import ( + "context" + "errors" + "fmt" + + storeUser "github.com/bjlag/go-keeper/internal/infrastructure/store/user" +) + +var ErrNoData = errors.New("no data") + +type Usecase struct { + dataStore dataStore +} + +func NewUsecase(dataStore dataStore) *Usecase { + return &Usecase{ + dataStore: dataStore, + } +} + +func (u Usecase) Do(ctx context.Context, data Data) (*Result, error) { + const op = "usecase.data.getAll.Do" + + rows, err := u.dataStore.GetAllByUser(ctx, data.UserGUID, data.Limit, data.Offset) + if err != nil { + if errors.Is(err, storeUser.ErrNotFound) { + return nil, ErrNoData + } + + return nil, fmt.Errorf("%s: %w", op, err) + } + + items := make([]Item, 0, len(rows)) + for _, r := range rows { + items = append(items, Item{ + GUID: r.GUID, + EncryptedData: r.EncryptedData, + CreatedAt: r.CreatedAt, + UpdatedAt: r.UpdatedAt, + }) + } + + return &Result{ + Items: items, + }, nil +} diff --git a/internal/usecase/server/user/login/usecase.go b/internal/usecase/server/user/login/usecase.go index 02097d7..2295924 100644 --- a/internal/usecase/server/user/login/usecase.go +++ b/internal/usecase/server/user/login/usecase.go @@ -28,7 +28,7 @@ func NewUsecase(userStore userStore, tokenGenerator tokenGenerator) *Usecase { } func (u Usecase) Do(ctx context.Context, data Data) (*Result, error) { - const op = "usecase.login.Do" + const op = "usecase.user.login.Do" user, err := u.userStore.GetByEmail(ctx, data.Email) if err != nil { diff --git a/internal/usecase/server/user/refresh_tokens/usecase.go b/internal/usecase/server/user/refresh_tokens/usecase.go index 1c24a58..66f3562 100644 --- a/internal/usecase/server/user/refresh_tokens/usecase.go +++ b/internal/usecase/server/user/refresh_tokens/usecase.go @@ -27,7 +27,7 @@ func NewUsecase(userStore userStore, tokenGenerator tokenGenerator) *Usecase { } func (u Usecase) Do(ctx context.Context, data Data) (*Result, error) { - const op = "usecase.refreshTokens.Do" + const op = "usecase.user.refreshTokens.Do" guid, err := u.tokenGenerator.GetUserGUID(data.RefreshToken) if err != nil { diff --git a/internal/usecase/server/user/register/usecase.go b/internal/usecase/server/user/register/usecase.go index 63436e4..4d27274 100644 --- a/internal/usecase/server/user/register/usecase.go +++ b/internal/usecase/server/user/register/usecase.go @@ -28,7 +28,7 @@ func NewUsecase(userStore userStore, tokenGenerator tokenGenerator) *Usecase { } func (u Usecase) Do(ctx context.Context, data Data) (*Result, error) { - const op = "usecase.register.Do" + const op = "usecase.user.register.Do" exist, err := u.isUserExists(ctx, data.Email) if err != nil { diff --git a/proto/keeper.proto b/proto/keeper.proto index 128c764..8aff47c 100644 --- a/proto/keeper.proto +++ b/proto/keeper.proto @@ -1,15 +1,22 @@ syntax = "proto3"; +import "google/protobuf/timestamp.proto"; + package keeper; option go_package = "internal/generated/rpc"; service Keeper { + // auth rpc Register(RegisterIn) returns (RegisterOut); rpc Login(LoginIn) returns (LoginOut); rpc RefreshTokens(RefreshTokensIn) returns (RefreshTokensOut); + + // data + rpc GetAllData(GetAllDataIn) returns (GetAllDataOut); } +// auth message RegisterIn { string email = 1; string password = 2; @@ -37,4 +44,21 @@ message RefreshTokensIn { message RefreshTokensOut { string access_token = 1; string refresh_token = 2; +} + +// data +message GetAllDataIn { + uint32 limit = 1; + uint32 offset = 2; +} + +message GetAllDataOut { + repeated Item items = 1; +} + +message Item { + string guid = 1; + bytes encryptedData = 2; + google.protobuf.Timestamp CreatedAt = 3; + google.protobuf.Timestamp UpdatedAt = 4; } \ No newline at end of file From 9be66cd6daae0365b39cc88cfcb54c508082b823 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Sun, 2 Mar 2025 23:26:19 +0300 Subject: [PATCH 24/94] server: auth --- internal/app/server/app.go | 9 +- internal/infrastructure/auth/context.go | 31 +++++++ internal/infrastructure/auth/jwt/jwt.go | 90 +++++++++++++++---- .../infrastructure/rpc/interceptor/auth.go | 62 +++++++++++++ internal/infrastructure/rpc/server/option.go | 7 ++ internal/infrastructure/rpc/server/server.go | 3 + internal/infrastructure/store/user/store.go | 3 +- internal/rpc/get_all_data/handler.go | 14 +-- .../server/user/refresh_tokens/contract.go | 6 +- .../server/user/refresh_tokens/usecase.go | 2 +- 10 files changed, 196 insertions(+), 31 deletions(-) create mode 100644 internal/infrastructure/auth/context.go create mode 100644 internal/infrastructure/rpc/interceptor/auth.go diff --git a/internal/app/server/app.go b/internal/app/server/app.go index 0a59c85..fc36b13 100644 --- a/internal/app/server/app.go +++ b/internal/app/server/app.go @@ -48,15 +48,16 @@ func (a *App) Run(ctx context.Context) error { userStore := user.NewStore(db) dataStore := data.NewStore(db) - tokeGenerator := jwt.NewGenerator(a.cfg.Auth.SecretKey, a.cfg.Auth.AccessTokenExp, a.cfg.Auth.RefreshTokenExp) + jwtGenerator := jwt.NewGenerator(a.cfg.Auth.SecretKey, a.cfg.Auth.AccessTokenExp, a.cfg.Auth.RefreshTokenExp) - ucRegister := register.NewUsecase(userStore, tokeGenerator) - ucLogin := login.NewUsecase(userStore, tokeGenerator) - ucRefreshTokens := rt.NewUsecase(userStore, tokeGenerator) + ucRegister := register.NewUsecase(userStore, jwtGenerator) + ucLogin := login.NewUsecase(userStore, jwtGenerator) + ucRefreshTokens := rt.NewUsecase(userStore, jwtGenerator) ucGetAllData := get_all.NewUsecase(dataStore) s := server.NewRPCServer( server.WithAddress(a.cfg.Address.Host, a.cfg.Address.Port), + server.WithJWTGenerator(jwtGenerator), server.WithLogger(a.log), server.WithHandler(server.RegisterMethod, rpcRegister.New(ucRegister).Handle), diff --git a/internal/infrastructure/auth/context.go b/internal/infrastructure/auth/context.go new file mode 100644 index 0000000..2ef33fe --- /dev/null +++ b/internal/infrastructure/auth/context.go @@ -0,0 +1,31 @@ +package auth + +import ( + "context" + + "github.com/google/uuid" +) + +type ctxKeyUserGUID int + +const UserGUIDKey ctxKeyUserGUID = 0 + +func UserGUIDWithCtx(ctx context.Context, guid uuid.UUID) context.Context { + if ctxGUID, ok := ctx.Value(UserGUIDKey).(string); ok { + if ctxGUID == guid.String() { + return ctx + } + } + + return context.WithValue(ctx, UserGUIDKey, guid.String()) +} + +func UserGUIDFromCtx(ctx context.Context) uuid.UUID { + if s, ok := ctx.Value(UserGUIDKey).(string); ok { + if guid, err := uuid.Parse(s); err == nil { + return guid + } + } + + return uuid.Nil +} diff --git a/internal/infrastructure/auth/jwt/jwt.go b/internal/infrastructure/auth/jwt/jwt.go index afc5691..d09656a 100644 --- a/internal/infrastructure/auth/jwt/jwt.go +++ b/internal/infrastructure/auth/jwt/jwt.go @@ -6,11 +6,20 @@ import ( "time" "github.com/golang-jwt/jwt/v4" + "github.com/google/uuid" +) + +const ( + prefixOp = "auth.jwt." + + subjectAccessToken = "access_token" + subjectRefreshToken = "refresh_token" ) var ( ErrInvalidToken = errors.New("invalid token") ErrUnexpectedSigningMethod = errors.New("unexpected signing method") + ErrUnexpectedTypeToken = errors.New("unexpected type token") ) type Claims struct { @@ -31,32 +40,56 @@ func NewGenerator(secretKey string, accessTokenExp, refreshTokenExp time.Duratio } } -func (g *Generator) GetUserGUID(tokenString string) (string, error) { - c := &Claims{} +func (g *Generator) GetUserGUIDFromAccessToken(tokenString string) (uuid.UUID, error) { + const op = prefixOp + "GetUserGUIDFromAccessToken" - token, err := jwt.ParseWithClaims(tokenString, c, func(t *jwt.Token) (interface{}, error) { - if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, fmt.Errorf("%w: %v", ErrUnexpectedSigningMethod, t.Header["alg"]) + token, clams, err := g.ParseToken(tokenString, subjectAccessToken) + if err != nil { + var e *jwt.ValidationError + if errors.As(err, &e) { + return uuid.Nil, ErrInvalidToken } - return []byte(g.secretKey), nil - }) + return uuid.Nil, fmt.Errorf("%s: %w", op, err) + } + + if !token.Valid || clams.Issuer == "" { + return uuid.Nil, ErrInvalidToken + } + + guid, err := uuid.Parse(clams.Issuer) + if err != nil { + return uuid.Nil, fmt.Errorf("%s: %w", op, err) + } + + return guid, nil +} + +func (g *Generator) GetUserGUIDFromRefreshToken(tokenString string) (uuid.UUID, error) { + const op = prefixOp + "GetUserGUIDFromRefreshToken" + + token, clams, err := g.ParseToken(tokenString, subjectRefreshToken) if err != nil { var e *jwt.ValidationError if errors.As(err, &e) { - return "", ErrInvalidToken + return uuid.Nil, ErrInvalidToken } - return "", err + return uuid.Nil, fmt.Errorf("%s: %w", op, err) } - if !token.Valid || c.Issuer == "" { - return "", ErrInvalidToken + if !token.Valid || clams.Issuer == "" { + return uuid.Nil, ErrInvalidToken } - return c.Issuer, nil + guid, err := uuid.Parse(clams.Issuer) + if err != nil { + return uuid.Nil, fmt.Errorf("%s: %w", op, err) + } + + return guid, nil } func (g *Generator) GenerateTokens(uuid string) (accessToken string, refreshToken string, err error) { - const op = "GenerateTokens" + const op = prefixOp + "GenerateTokens" var claim *Claims @@ -75,14 +108,14 @@ func (g *Generator) GenerateTokens(uuid string) (accessToken string, refreshToke } func (g *Generator) GenerateAccessToken(uuid string) (string, *Claims, error) { - const op = "GenerateAccessToken" + const op = prefixOp + "GenerateAccessToken" now := time.Now() claim := &Claims{ RegisteredClaims: jwt.RegisteredClaims{ Issuer: uuid, ExpiresAt: jwt.NewNumericDate(now.Add(g.accessTokenExp)), - Subject: "access_token", + Subject: subjectAccessToken, IssuedAt: jwt.NewNumericDate(now), }, } @@ -97,14 +130,14 @@ func (g *Generator) GenerateAccessToken(uuid string) (string, *Claims, error) { } func (g *Generator) GenerateRefreshToken(cl *Claims) (string, error) { - const op = "GenerateRefreshToken" + const op = prefixOp + "GenerateRefreshToken" now := time.Now() claim := &Claims{ RegisteredClaims: jwt.RegisteredClaims{ Issuer: cl.Issuer, ExpiresAt: jwt.NewNumericDate(now.Add(g.refreshTokenExp)), - Subject: "refresh_token", + Subject: subjectRefreshToken, IssuedAt: jwt.NewNumericDate(now), }, } @@ -117,3 +150,26 @@ func (g *Generator) GenerateRefreshToken(cl *Claims) (string, error) { return tokenString, nil } + +func (g *Generator) ParseToken(tokenString, subjectClaim string) (*jwt.Token, *Claims, error) { + const op = prefixOp + "ParseToken" + + c := &Claims{} + fn := func(t *jwt.Token) (interface{}, error) { + if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("%w: %v", ErrUnexpectedSigningMethod, t.Header["alg"]) + } + claim, ok := t.Claims.(*Claims) + if ok && claim.Subject != subjectClaim { + return nil, fmt.Errorf("%w: %v", ErrUnexpectedTypeToken, claim.Subject) + } + return []byte(g.secretKey), nil + } + + t, err := jwt.ParseWithClaims(tokenString, c, fn) + if err != nil { + return nil, nil, fmt.Errorf("%s: %w", op, err) + } + + return t, c, nil +} diff --git a/internal/infrastructure/rpc/interceptor/auth.go b/internal/infrastructure/rpc/interceptor/auth.go new file mode 100644 index 0000000..3a9858a --- /dev/null +++ b/internal/infrastructure/rpc/interceptor/auth.go @@ -0,0 +1,62 @@ +package interceptor + +import ( + "context" + "errors" + "strings" + + "go.uber.org/zap" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + + "github.com/bjlag/go-keeper/internal/generated/rpc" + "github.com/bjlag/go-keeper/internal/infrastructure/auth" + "github.com/bjlag/go-keeper/internal/infrastructure/auth/jwt" +) + +const ( + AuthMeta = "authorization" + bearerAuth = "Bearer" +) + +var methodSkip = map[string]struct{}{ + rpc.Keeper_Login_FullMethodName: {}, + rpc.Keeper_Register_FullMethodName: {}, + rpc.Keeper_RefreshTokens_FullMethodName: {}, +} + +func CheckAccessTokenInterceptor(jwtGenerator *jwt.Generator, log *zap.Logger) grpc.UnaryServerInterceptor { + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + if _, ok := methodSkip[info.FullMethod]; ok { + return handler(ctx, req) + } + + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + meta := md.Get(AuthMeta) + if len(meta) == 0 { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + token, found := strings.CutPrefix(meta[0], bearerAuth) + if !found { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + + userGUID, err := jwtGenerator.GetUserGUIDFromAccessToken(strings.TrimLeft(token, " ")) + if err != nil { + if errors.Is(err, jwt.ErrInvalidToken) { + return nil, status.Errorf(codes.PermissionDenied, "permission denied") + } + log.Error("Failed to get user GUID", zap.Error(err)) + return nil, status.Errorf(codes.Internal, "internal error") + } + + return handler(auth.UserGUIDWithCtx(ctx, userGUID), req) + } +} diff --git a/internal/infrastructure/rpc/server/option.go b/internal/infrastructure/rpc/server/option.go index 96701fd..711588a 100644 --- a/internal/infrastructure/rpc/server/option.go +++ b/internal/infrastructure/rpc/server/option.go @@ -1,6 +1,7 @@ package server import ( + "github.com/bjlag/go-keeper/internal/infrastructure/auth/jwt" "go.uber.org/zap" ) @@ -19,6 +20,12 @@ func WithLogger(logger *zap.Logger) Option { } } +func WithJWTGenerator(jwt *jwt.Generator) Option { + return func(s *RPCServer) { + s.jwt = jwt + } +} + func WithHandler(method string, handler any) Option { return func(s *RPCServer) { s.handlers[method] = handler diff --git a/internal/infrastructure/rpc/server/server.go b/internal/infrastructure/rpc/server/server.go index c941a99..ad7d4d8 100644 --- a/internal/infrastructure/rpc/server/server.go +++ b/internal/infrastructure/rpc/server/server.go @@ -10,6 +10,7 @@ import ( "google.golang.org/grpc" pb "github.com/bjlag/go-keeper/internal/generated/rpc" + "github.com/bjlag/go-keeper/internal/infrastructure/auth/jwt" "github.com/bjlag/go-keeper/internal/infrastructure/rpc/interceptor" ) @@ -19,6 +20,7 @@ type RPCServer struct { host string port int handlers map[string]any + jwt *jwt.Generator log *zap.Logger } @@ -50,6 +52,7 @@ func (s RPCServer) Start(ctx context.Context) error { grpcServer := grpc.NewServer( grpc.ChainUnaryInterceptor( interceptor.LoggerServerInterceptor(s.log), + interceptor.CheckAccessTokenInterceptor(s.jwt, s.log), ), ) diff --git a/internal/infrastructure/store/user/store.go b/internal/infrastructure/store/user/store.go index e2ddb2b..5fb3b78 100644 --- a/internal/infrastructure/store/user/store.go +++ b/internal/infrastructure/store/user/store.go @@ -5,6 +5,7 @@ import ( "database/sql" "errors" "fmt" + "github.com/google/uuid" "github.com/jmoiron/sqlx" @@ -23,7 +24,7 @@ func NewStore(db *sqlx.DB) *Store { } } -func (s Store) GetByGUID(ctx context.Context, guid string) (*model.User, error) { +func (s Store) GetByGUID(ctx context.Context, guid uuid.UUID) (*model.User, error) { const op = "store.user.GetByGUID" query := ` diff --git a/internal/rpc/get_all_data/handler.go b/internal/rpc/get_all_data/handler.go index cd8be62..dfb6936 100644 --- a/internal/rpc/get_all_data/handler.go +++ b/internal/rpc/get_all_data/handler.go @@ -11,6 +11,7 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" pb "github.com/bjlag/go-keeper/internal/generated/rpc" + "github.com/bjlag/go-keeper/internal/infrastructure/auth" "github.com/bjlag/go-keeper/internal/infrastructure/logger" "github.com/bjlag/go-keeper/internal/usecase/server/data/get_all" ) @@ -33,6 +34,11 @@ func New(usecase usecase) *Handler { func (h *Handler) Handle(ctx context.Context, in *pb.GetAllDataIn) (*pb.GetAllDataOut, error) { log := logger.FromCtx(ctx) + userGUID := auth.UserGUIDFromCtx(ctx) + if userGUID == uuid.Nil { + return nil, status.Error(codes.PermissionDenied, "permission denied") + } + if in.GetLimit() > limitMax { in.Limit = limitMax } @@ -45,14 +51,10 @@ func (h *Handler) Handle(ctx context.Context, in *pb.GetAllDataIn) (*pb.GetAllDa in.Offset = 0 } - // todo проверить авторизацию - // todo достать GUID пользователя из контекста - userGUID := uuid.New() - result, err := h.usecase.Do(ctx, get_all.Data{ UserGUID: userGUID, - Limit: in.Limit, - Offset: in.Offset, + Limit: in.GetLimit(), + Offset: in.GetOffset(), }) if err != nil { if !errors.Is(err, get_all.ErrNoData) { diff --git a/internal/usecase/server/user/refresh_tokens/contract.go b/internal/usecase/server/user/refresh_tokens/contract.go index cf2142e..98481b2 100644 --- a/internal/usecase/server/user/refresh_tokens/contract.go +++ b/internal/usecase/server/user/refresh_tokens/contract.go @@ -3,14 +3,16 @@ package refresh_tokens import ( "context" + "github.com/google/uuid" + model "github.com/bjlag/go-keeper/internal/domain/user" ) type userStore interface { - GetByGUID(ctx context.Context, guid string) (*model.User, error) + GetByGUID(ctx context.Context, guid uuid.UUID) (*model.User, error) } type tokenGenerator interface { - GetUserGUID(tokenString string) (string, error) + GetUserGUIDFromRefreshToken(tokenString string) (uuid.UUID, error) GenerateTokens(guid string) (accessToken, refreshToken string, err error) } diff --git a/internal/usecase/server/user/refresh_tokens/usecase.go b/internal/usecase/server/user/refresh_tokens/usecase.go index 66f3562..1a0d607 100644 --- a/internal/usecase/server/user/refresh_tokens/usecase.go +++ b/internal/usecase/server/user/refresh_tokens/usecase.go @@ -29,7 +29,7 @@ func NewUsecase(userStore userStore, tokenGenerator tokenGenerator) *Usecase { func (u Usecase) Do(ctx context.Context, data Data) (*Result, error) { const op = "usecase.user.refreshTokens.Do" - guid, err := u.tokenGenerator.GetUserGUID(data.RefreshToken) + guid, err := u.tokenGenerator.GetUserGUIDFromRefreshToken(data.RefreshToken) if err != nil { if errors.Is(err, jwt.ErrInvalidToken) { return nil, ErrInvalidRefreshToken From f16b11fa7d2085bcf8a5e29ca9aec600bf656334 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Mon, 3 Mar 2025 00:10:22 +0300 Subject: [PATCH 25/94] server: auth --- internal/app/server/app.go | 12 +++++------ internal/infrastructure/auth/{jwt => }/jwt.go | 20 +++++++++---------- .../infrastructure/rpc/interceptor/auth.go | 7 +++---- internal/infrastructure/rpc/server/option.go | 5 +++-- internal/infrastructure/rpc/server/server.go | 4 ++-- .../server/user/refresh_tokens/usecase.go | 4 ++-- 6 files changed, 26 insertions(+), 26 deletions(-) rename internal/infrastructure/auth/{jwt => }/jwt.go (83%) diff --git a/internal/app/server/app.go b/internal/app/server/app.go index fc36b13..3af0703 100644 --- a/internal/app/server/app.go +++ b/internal/app/server/app.go @@ -6,7 +6,7 @@ import ( "go.uber.org/zap" - "github.com/bjlag/go-keeper/internal/infrastructure/auth/jwt" + "github.com/bjlag/go-keeper/internal/infrastructure/auth" "github.com/bjlag/go-keeper/internal/infrastructure/db/pg" "github.com/bjlag/go-keeper/internal/infrastructure/rpc/server" "github.com/bjlag/go-keeper/internal/infrastructure/store/data" @@ -48,16 +48,16 @@ func (a *App) Run(ctx context.Context) error { userStore := user.NewStore(db) dataStore := data.NewStore(db) - jwtGenerator := jwt.NewGenerator(a.cfg.Auth.SecretKey, a.cfg.Auth.AccessTokenExp, a.cfg.Auth.RefreshTokenExp) + jwt := auth.NewJWT(a.cfg.Auth.SecretKey, a.cfg.Auth.AccessTokenExp, a.cfg.Auth.RefreshTokenExp) - ucRegister := register.NewUsecase(userStore, jwtGenerator) - ucLogin := login.NewUsecase(userStore, jwtGenerator) - ucRefreshTokens := rt.NewUsecase(userStore, jwtGenerator) + ucRegister := register.NewUsecase(userStore, jwt) + ucLogin := login.NewUsecase(userStore, jwt) + ucRefreshTokens := rt.NewUsecase(userStore, jwt) ucGetAllData := get_all.NewUsecase(dataStore) s := server.NewRPCServer( server.WithAddress(a.cfg.Address.Host, a.cfg.Address.Port), - server.WithJWTGenerator(jwtGenerator), + server.WithJWT(jwt), server.WithLogger(a.log), server.WithHandler(server.RegisterMethod, rpcRegister.New(ucRegister).Handle), diff --git a/internal/infrastructure/auth/jwt/jwt.go b/internal/infrastructure/auth/jwt.go similarity index 83% rename from internal/infrastructure/auth/jwt/jwt.go rename to internal/infrastructure/auth/jwt.go index d09656a..fea3d9a 100644 --- a/internal/infrastructure/auth/jwt/jwt.go +++ b/internal/infrastructure/auth/jwt.go @@ -1,4 +1,4 @@ -package jwt +package auth import ( "errors" @@ -26,21 +26,21 @@ type Claims struct { jwt.RegisteredClaims } -type Generator struct { +type JWT struct { secretKey string accessTokenExp time.Duration refreshTokenExp time.Duration } -func NewGenerator(secretKey string, accessTokenExp, refreshTokenExp time.Duration) *Generator { - return &Generator{ +func NewJWT(secretKey string, accessTokenExp, refreshTokenExp time.Duration) *JWT { + return &JWT{ secretKey: secretKey, accessTokenExp: accessTokenExp, refreshTokenExp: refreshTokenExp, } } -func (g *Generator) GetUserGUIDFromAccessToken(tokenString string) (uuid.UUID, error) { +func (g *JWT) GetUserGUIDFromAccessToken(tokenString string) (uuid.UUID, error) { const op = prefixOp + "GetUserGUIDFromAccessToken" token, clams, err := g.ParseToken(tokenString, subjectAccessToken) @@ -64,7 +64,7 @@ func (g *Generator) GetUserGUIDFromAccessToken(tokenString string) (uuid.UUID, e return guid, nil } -func (g *Generator) GetUserGUIDFromRefreshToken(tokenString string) (uuid.UUID, error) { +func (g *JWT) GetUserGUIDFromRefreshToken(tokenString string) (uuid.UUID, error) { const op = prefixOp + "GetUserGUIDFromRefreshToken" token, clams, err := g.ParseToken(tokenString, subjectRefreshToken) @@ -88,7 +88,7 @@ func (g *Generator) GetUserGUIDFromRefreshToken(tokenString string) (uuid.UUID, return guid, nil } -func (g *Generator) GenerateTokens(uuid string) (accessToken string, refreshToken string, err error) { +func (g *JWT) GenerateTokens(uuid string) (accessToken string, refreshToken string, err error) { const op = prefixOp + "GenerateTokens" var claim *Claims @@ -107,7 +107,7 @@ func (g *Generator) GenerateTokens(uuid string) (accessToken string, refreshToke return } -func (g *Generator) GenerateAccessToken(uuid string) (string, *Claims, error) { +func (g *JWT) GenerateAccessToken(uuid string) (string, *Claims, error) { const op = prefixOp + "GenerateAccessToken" now := time.Now() @@ -129,7 +129,7 @@ func (g *Generator) GenerateAccessToken(uuid string) (string, *Claims, error) { return tokenString, claim, nil } -func (g *Generator) GenerateRefreshToken(cl *Claims) (string, error) { +func (g *JWT) GenerateRefreshToken(cl *Claims) (string, error) { const op = prefixOp + "GenerateRefreshToken" now := time.Now() @@ -151,7 +151,7 @@ func (g *Generator) GenerateRefreshToken(cl *Claims) (string, error) { return tokenString, nil } -func (g *Generator) ParseToken(tokenString, subjectClaim string) (*jwt.Token, *Claims, error) { +func (g *JWT) ParseToken(tokenString, subjectClaim string) (*jwt.Token, *Claims, error) { const op = prefixOp + "ParseToken" c := &Claims{} diff --git a/internal/infrastructure/rpc/interceptor/auth.go b/internal/infrastructure/rpc/interceptor/auth.go index 3a9858a..17ae45a 100644 --- a/internal/infrastructure/rpc/interceptor/auth.go +++ b/internal/infrastructure/rpc/interceptor/auth.go @@ -13,7 +13,6 @@ import ( "github.com/bjlag/go-keeper/internal/generated/rpc" "github.com/bjlag/go-keeper/internal/infrastructure/auth" - "github.com/bjlag/go-keeper/internal/infrastructure/auth/jwt" ) const ( @@ -27,7 +26,7 @@ var methodSkip = map[string]struct{}{ rpc.Keeper_RefreshTokens_FullMethodName: {}, } -func CheckAccessTokenInterceptor(jwtGenerator *jwt.Generator, log *zap.Logger) grpc.UnaryServerInterceptor { +func CheckAccessTokenInterceptor(jwt *auth.JWT, log *zap.Logger) grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { if _, ok := methodSkip[info.FullMethod]; ok { return handler(ctx, req) @@ -48,9 +47,9 @@ func CheckAccessTokenInterceptor(jwtGenerator *jwt.Generator, log *zap.Logger) g return nil, status.Errorf(codes.PermissionDenied, "permission denied") } - userGUID, err := jwtGenerator.GetUserGUIDFromAccessToken(strings.TrimLeft(token, " ")) + userGUID, err := jwt.GetUserGUIDFromAccessToken(strings.TrimLeft(token, " ")) if err != nil { - if errors.Is(err, jwt.ErrInvalidToken) { + if errors.Is(err, auth.ErrInvalidToken) { return nil, status.Errorf(codes.PermissionDenied, "permission denied") } log.Error("Failed to get user GUID", zap.Error(err)) diff --git a/internal/infrastructure/rpc/server/option.go b/internal/infrastructure/rpc/server/option.go index 711588a..998cf7d 100644 --- a/internal/infrastructure/rpc/server/option.go +++ b/internal/infrastructure/rpc/server/option.go @@ -1,8 +1,9 @@ package server import ( - "github.com/bjlag/go-keeper/internal/infrastructure/auth/jwt" "go.uber.org/zap" + + "github.com/bjlag/go-keeper/internal/infrastructure/auth" ) type Option func(*RPCServer) @@ -20,7 +21,7 @@ func WithLogger(logger *zap.Logger) Option { } } -func WithJWTGenerator(jwt *jwt.Generator) Option { +func WithJWT(jwt *auth.JWT) Option { return func(s *RPCServer) { s.jwt = jwt } diff --git a/internal/infrastructure/rpc/server/server.go b/internal/infrastructure/rpc/server/server.go index ad7d4d8..ba352f0 100644 --- a/internal/infrastructure/rpc/server/server.go +++ b/internal/infrastructure/rpc/server/server.go @@ -10,7 +10,7 @@ import ( "google.golang.org/grpc" pb "github.com/bjlag/go-keeper/internal/generated/rpc" - "github.com/bjlag/go-keeper/internal/infrastructure/auth/jwt" + "github.com/bjlag/go-keeper/internal/infrastructure/auth" "github.com/bjlag/go-keeper/internal/infrastructure/rpc/interceptor" ) @@ -20,7 +20,7 @@ type RPCServer struct { host string port int handlers map[string]any - jwt *jwt.Generator + jwt *auth.JWT log *zap.Logger } diff --git a/internal/usecase/server/user/refresh_tokens/usecase.go b/internal/usecase/server/user/refresh_tokens/usecase.go index 1a0d607..77859f6 100644 --- a/internal/usecase/server/user/refresh_tokens/usecase.go +++ b/internal/usecase/server/user/refresh_tokens/usecase.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/bjlag/go-keeper/internal/infrastructure/auth/jwt" + "github.com/bjlag/go-keeper/internal/infrastructure/auth" storeUser "github.com/bjlag/go-keeper/internal/infrastructure/store/user" ) @@ -31,7 +31,7 @@ func (u Usecase) Do(ctx context.Context, data Data) (*Result, error) { guid, err := u.tokenGenerator.GetUserGUIDFromRefreshToken(data.RefreshToken) if err != nil { - if errors.Is(err, jwt.ErrInvalidToken) { + if errors.Is(err, auth.ErrInvalidToken) { return nil, ErrInvalidRefreshToken } return nil, fmt.Errorf("%s: %w", op, err) From ebd3f66e44cb7cc9fb4cec013963fd7bd59b78f1 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Mon, 3 Mar 2025 17:51:50 +0300 Subject: [PATCH 26/94] client: add to get all data --- .../infrastructure/rpc/client/get_all_data.go | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 internal/infrastructure/rpc/client/get_all_data.go diff --git a/internal/infrastructure/rpc/client/get_all_data.go b/internal/infrastructure/rpc/client/get_all_data.go new file mode 100644 index 0000000..51f8700 --- /dev/null +++ b/internal/infrastructure/rpc/client/get_all_data.go @@ -0,0 +1,57 @@ +package client + +import ( + "context" + "fmt" + "time" + + "github.com/bjlag/go-keeper/internal/generated/rpc" +) + +type GetAllDataIn struct { + Limit uint32 + Offset uint32 +} + +type GetAllDataOut struct { + Items []GetAllDataItem +} + +type GetAllDataItem struct { + GUID string + EncryptedData []byte + CreatedAt time.Time + UpdatedAt time.Time +} + +func (c RPCClient) GetAllData(ctx context.Context, in GetAllDataIn) (*GetAllDataOut, error) { + const op = "client.rpc.GetAllData" + + rpcIn := &rpc.GetAllDataIn{ + Limit: in.Limit, + Offset: in.Offset, + } + + out, err := c.client.GetAllData(ctx, rpcIn) + if err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + + if out == nil || len(out.Items) == 0 { + return nil, nil + } + + items := make([]GetAllDataItem, len(out.Items)) + for i, item := range out.Items { + items[i] = GetAllDataItem{ + GUID: item.Guid, + EncryptedData: item.EncryptedData, + CreatedAt: item.CreatedAt.AsTime(), + UpdatedAt: item.UpdatedAt.AsTime(), + } + } + + return &GetAllDataOut{ + Items: items, + }, nil +} From 9eb1606aeb483613ff39f6b740712ef6f0e3c418 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Mon, 3 Mar 2025 19:49:08 +0300 Subject: [PATCH 27/94] migrator: support sqlite --- .gitignore | 8 ++- Makefile | 6 ++- cmd/migrator/config.go | 1 + cmd/migrator/main.go | 50 ++++++++++++++++--- ...or.yaml.dist => migrator_client.yaml.dist} | 4 +- config/migrator_server.yaml.dist | 12 +++++ go.mod | 1 + go.sum | 3 +- .../001_create_users_table.up.sql | 0 .../{ => server}/002_create_data_table.up.sql | 0 10 files changed, 72 insertions(+), 13 deletions(-) rename config/{migrator.yaml.dist => migrator_client.yaml.dist} (74%) create mode 100644 config/migrator_server.yaml.dist rename migrations/{ => server}/001_create_users_table.up.sql (100%) rename migrations/{ => server}/002_create_data_table.up.sql (100%) diff --git a/.gitignore b/.gitignore index 48c7c70..00f7da5 100644 --- a/.gitignore +++ b/.gitignore @@ -28,9 +28,13 @@ vendor/ .vscode # Configs -config/migrator.yaml +config/migrator_client.yaml +config/migrator_server.yaml config/server.yaml config/client.yaml # log -*.log \ No newline at end of file +*.log + +# client +client.db \ No newline at end of file diff --git a/Makefile b/Makefile index aaa7443..083e732 100644 --- a/Makefile +++ b/Makefile @@ -46,8 +46,10 @@ docker-down-clear: ## migrate: apply migrations .PHONY: migrate migrate: - @echo " > Apply migrations" - @go run $(DIR)/cmd/migrator -c="./config/migrator.yaml" + @echo " > Apply migrations (server)" + @go run $(DIR)/cmd/migrator -c="./config/migrator_server.yaml" + @echo " > Apply migrations (client)" + @go run $(DIR)/cmd/migrator -c="./config/migrator_client.yaml" ## lint: start linter .PHONY: lint diff --git a/cmd/migrator/config.go b/cmd/migrator/config.go index 638453d..b810d8e 100644 --- a/cmd/migrator/config.go +++ b/cmd/migrator/config.go @@ -6,6 +6,7 @@ type Config struct { MigrationsTable string `yaml:"migrationsTable" env:"MIGRATIONS_TABLE" env-default:"migrations" env-description:"Migrations table name" json:"migrations_table"` Database struct { + Type string `yaml:"type" env:"DB_TYPE" env-default:"pg" env-description:"Database type" json:"type"` Host string `yaml:"host" env:"DB_HOST" env-description:"Database host" json:"host"` Port string `yaml:"port" env:"DB_PORT" env-description:"Database port" json:"port"` Name string `yaml:"name" env:"DB_NAME" env-description:"Database name" json:"name"` diff --git a/cmd/migrator/main.go b/cmd/migrator/main.go index 0dedc8f..2122f6e 100644 --- a/cmd/migrator/main.go +++ b/cmd/migrator/main.go @@ -4,13 +4,17 @@ import ( "errors" "flag" "fmt" + "io/fs" logNative "log" "github.com/golang-migrate/migrate/v4" + "github.com/golang-migrate/migrate/v4/database" "github.com/golang-migrate/migrate/v4/database/pgx/v5" + "github.com/golang-migrate/migrate/v4/database/sqlite3" _ "github.com/golang-migrate/migrate/v4/source/file" "github.com/ilyakaznacheev/cleanenv" "github.com/jmoiron/sqlx" + _ "github.com/mattn/go-sqlite3" "go.uber.org/zap" "github.com/bjlag/go-keeper/internal/infrastructure/db/pg" @@ -18,7 +22,8 @@ import ( ) const ( - configPathDefault = "./config/migrator.yaml" + typePG = "pg" + typeSqlite = "sqlite" ) func main() { @@ -30,7 +35,7 @@ func main() { var configPath string - flag.StringVar(&configPath, "c", configPathDefault, "Path to config file") + flag.StringVar(&configPath, "c", "", "Path to config file") flag.Parse() var cfg Config @@ -44,10 +49,23 @@ func main() { }() log.Debug("Config loaded", zap.Any("config", cfg)) + log = log.With(zap.String("db_type", cfg.Database.Type)) + + var ( + err error + db *sqlx.DB + ) + + switch cfg.Database.Type { + case typePG: + dbConf := cfg.Database + db, err = pg.New(pg.GetDSN(dbConf.Host, dbConf.Port, dbConf.Name, dbConf.User, dbConf.Password)).Connect() + case typeSqlite: + db, err = sqlx.Open("sqlite3", "./client.db") + } - dbConf := cfg.Database - db, err := pg.New(pg.GetDSN(dbConf.Host, dbConf.Port, dbConf.Name, dbConf.User, dbConf.Password)).Connect() if err != nil { + log.Error("Failed to open db", zap.Error(err)) panic(err) } @@ -64,6 +82,11 @@ func main() { log.Info("No changes") return } + var e *fs.PathError + if errors.As(err, &e) { + log.Info("No migration files") + return + } log.Error("Migration failed", zap.Error(err)) return } @@ -74,9 +97,7 @@ func main() { func initMigrator(db *sqlx.DB, cfg Config) (*migrate.Migrate, error) { const op = "initMigrator" - driver, err := pgx.WithInstance(db.DB, &pgx.Config{ - MigrationsTable: cfg.MigrationsTable, - }) + driver, err := getDBDriver(db, cfg) if err != nil { return nil, fmt.Errorf("%s get driver: %w", op, err) } @@ -92,3 +113,18 @@ func initMigrator(db *sqlx.DB, cfg Config) (*migrate.Migrate, error) { return m, nil } + +func getDBDriver(db *sqlx.DB, cfg Config) (database.Driver, error) { + switch cfg.Database.Type { + case typePG: + return pgx.WithInstance(db.DB, &pgx.Config{ + MigrationsTable: cfg.MigrationsTable, + }) + case typeSqlite: + return sqlite3.WithInstance(db.DB, &sqlite3.Config{ + MigrationsTable: cfg.MigrationsTable, + }) + default: + return nil, fmt.Errorf("uknown database type: %s", cfg.Database.Type) //nolint:err113 + } +} diff --git a/config/migrator.yaml.dist b/config/migrator_client.yaml.dist similarity index 74% rename from config/migrator.yaml.dist rename to config/migrator_client.yaml.dist index 9862e0a..cb40c47 100644 --- a/config/migrator.yaml.dist +++ b/config/migrator_client.yaml.dist @@ -1,8 +1,10 @@ env: dev -source_path: "./migrations" + +source_path: "./migrations/client" migrations_table: "migrations" database: + type: pg host: localhost port: 5444 name: master diff --git a/config/migrator_server.yaml.dist b/config/migrator_server.yaml.dist new file mode 100644 index 0000000..6cdcf4a --- /dev/null +++ b/config/migrator_server.yaml.dist @@ -0,0 +1,12 @@ +env: dev + +source_path: "./migrations/server" +migrations_table: "migrations" + +database: + type: sqlite + host: localhost + port: 5444 + name: master + user: postgres + password: secret \ No newline at end of file diff --git a/go.mod b/go.mod index 9f4a4c9..d499ebf 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/jackc/pgx/v5 v5.7.2 github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.10.9 + github.com/mattn/go-sqlite3 v1.14.24 github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.33.0 diff --git a/go.sum b/go.sum index a6327d5..5a015e8 100644 --- a/go.sum +++ b/go.sum @@ -91,8 +91,9 @@ github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2J github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= +github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= diff --git a/migrations/001_create_users_table.up.sql b/migrations/server/001_create_users_table.up.sql similarity index 100% rename from migrations/001_create_users_table.up.sql rename to migrations/server/001_create_users_table.up.sql diff --git a/migrations/002_create_data_table.up.sql b/migrations/server/002_create_data_table.up.sql similarity index 100% rename from migrations/002_create_data_table.up.sql rename to migrations/server/002_create_data_table.up.sql From 53611d1fa5e5a29df54c5ca4a494c53160d8fe5b Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Mon, 3 Mar 2025 20:14:06 +0300 Subject: [PATCH 28/94] client: add `data` table --- migrations/client/001_create_data_table.up.sql | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 migrations/client/001_create_data_table.up.sql diff --git a/migrations/client/001_create_data_table.up.sql b/migrations/client/001_create_data_table.up.sql new file mode 100644 index 0000000..cb20491 --- /dev/null +++ b/migrations/client/001_create_data_table.up.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS data ( + guid text PRIMARY KEY, + encrypted_data text NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL +); From d1b4b53ca23ed6765611c4dafc49f5ee1b45ad3b Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Mon, 3 Mar 2025 22:50:24 +0300 Subject: [PATCH 29/94] client: tokens store --- internal/app/client/app.go | 18 +++++++++++-- internal/cli/model.go | 12 ++++----- internal/cli/option.go | 7 +++++ .../store/client/token/store.go | 27 +++++++++++++++++++ 4 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 internal/infrastructure/store/client/token/store.go diff --git a/internal/app/client/app.go b/internal/app/client/app.go index 379e4e1..53df7c2 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -2,18 +2,21 @@ package client import ( "context" + "database/sql" "fmt" - "github.com/bjlag/go-keeper/internal/cli/form/list" - "github.com/bjlag/go-keeper/internal/cli/form/password" "os" tea "github.com/charmbracelet/bubbletea" + _ "github.com/mattn/go-sqlite3" "go.uber.org/zap" "github.com/bjlag/go-keeper/internal/cli" + "github.com/bjlag/go-keeper/internal/cli/form/list" formLogin "github.com/bjlag/go-keeper/internal/cli/form/login" + "github.com/bjlag/go-keeper/internal/cli/form/password" formRegister "github.com/bjlag/go-keeper/internal/cli/form/register" rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" + "github.com/bjlag/go-keeper/internal/infrastructure/store/client/token" "github.com/bjlag/go-keeper/internal/usecase/client/login" "github.com/bjlag/go-keeper/internal/usecase/client/register" ) @@ -42,10 +45,21 @@ func (a *App) Run(ctx context.Context) error { _ = rpcClient.Close() }() + db, err := sql.Open("sqlite3", "./client.db") + if err != nil { + a.log.Error("failed to open db", zap.Error(err)) + return fmt.Errorf("%s%w", op, err) + } + + _ = db + + storeTokens := token.NewStore() + ucLogin := login.NewUsecase(rpcClient) ucRegister := register.NewUsecase(rpcClient) model := cli.InitModel( + cli.WithStoreTokens(storeTokens), cli.WithLoginForm(formLogin.NewForm(ucLogin)), cli.WithRegisterForm(formRegister.NewForm(ucRegister)), cli.WithListFormForm(list.NewForm()), diff --git a/internal/cli/model.go b/internal/cli/model.go index 1996bda..6eea16b 100644 --- a/internal/cli/model.go +++ b/internal/cli/model.go @@ -14,6 +14,7 @@ import ( "github.com/bjlag/go-keeper/internal/cli/form/password" "github.com/bjlag/go-keeper/internal/cli/form/register" "github.com/bjlag/go-keeper/internal/cli/message" + "github.com/bjlag/go-keeper/internal/infrastructure/store/client/token" ) type MainModel struct { @@ -25,8 +26,7 @@ type MainModel struct { formList *listf.Form formPassword *password.Form - accessToken string - refreshToken string + storeTokens *token.Store } func InitModel(opts ...Option) *MainModel { @@ -73,13 +73,13 @@ func (m *MainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // Success case message.SuccessLoginMessage: - m.accessToken = msg.AccessToken - m.refreshToken = msg.RefreshToken + m.storeTokens.SaveTokens(msg.AccessToken, msg.RefreshToken) + + // todo получить все данные return m.Update(message.OpenCategoryListFormMessage{}) case message.SuccessRegisterMessage: - m.accessToken = msg.AccessToken - m.refreshToken = msg.RefreshToken + m.storeTokens.SaveTokens(msg.AccessToken, msg.RefreshToken) return m.Update(message.OpenCategoryListFormMessage{}) } diff --git a/internal/cli/option.go b/internal/cli/option.go index 94bf741..296198d 100644 --- a/internal/cli/option.go +++ b/internal/cli/option.go @@ -5,10 +5,17 @@ import ( "github.com/bjlag/go-keeper/internal/cli/form/login" "github.com/bjlag/go-keeper/internal/cli/form/password" "github.com/bjlag/go-keeper/internal/cli/form/register" + "github.com/bjlag/go-keeper/internal/infrastructure/store/client/token" ) type Option func(*MainModel) +func WithStoreTokens(store *token.Store) Option { + return func(m *MainModel) { + m.storeTokens = store + } +} + func WithLoginForm(form *login.Form) Option { return func(m *MainModel) { form.SetMainModel(m) diff --git a/internal/infrastructure/store/client/token/store.go b/internal/infrastructure/store/client/token/store.go new file mode 100644 index 0000000..22f3d0e --- /dev/null +++ b/internal/infrastructure/store/client/token/store.go @@ -0,0 +1,27 @@ +package token + +type tokens struct { + accessToken string + refreshToken string +} + +type Store struct { + tokens tokens +} + +func NewStore() *Store { + return &Store{} +} + +func (s *Store) SaveTokens(accessToken, refreshToken string) { + s.tokens.accessToken = accessToken + s.tokens.refreshToken = refreshToken +} + +func (s *Store) AccessToken() string { + return s.tokens.accessToken +} + +func (s *Store) RefreshToken() string { + return s.tokens.refreshToken +} From b7b0f55b61044c74bc8b9ba941d55d9d5c35a83a Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Tue, 4 Mar 2025 00:47:38 +0300 Subject: [PATCH 30/94] client: auth --- internal/app/client/app.go | 9 ++-- internal/cli/form/list/form.go | 27 +++++++++++- internal/cli/form/login/form.go | 11 ++++- internal/cli/model.go | 8 ++-- internal/infrastructure/rpc/client/client.go | 4 +- .../infrastructure/rpc/client/get_all_data.go | 16 +++---- .../infrastructure/rpc/interceptor/auth.go | 44 +++++++++++++++++-- internal/infrastructure/rpc/server/server.go | 2 +- 8 files changed, 96 insertions(+), 25 deletions(-) diff --git a/internal/app/client/app.go b/internal/app/client/app.go index 53df7c2..f33810d 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -36,7 +36,9 @@ func NewApp(cfg Config, log *zap.Logger) *App { func (a *App) Run(ctx context.Context) error { const op = "app.Run" - rpcClient, err := rpc.NewRPCClient(a.cfg.Server.Host, a.cfg.Server.Port, a.log) + storeTokens := token.NewStore() + + rpcClient, err := rpc.NewRPCClient(a.cfg.Server.Host, a.cfg.Server.Port, storeTokens, a.log) if err != nil { a.log.Error("failed to create rpc client", zap.Error(err)) return fmt.Errorf("%s:%w", op, err) @@ -53,16 +55,15 @@ func (a *App) Run(ctx context.Context) error { _ = db - storeTokens := token.NewStore() - ucLogin := login.NewUsecase(rpcClient) ucRegister := register.NewUsecase(rpcClient) model := cli.InitModel( cli.WithStoreTokens(storeTokens), + cli.WithLoginForm(formLogin.NewForm(ucLogin)), cli.WithRegisterForm(formRegister.NewForm(ucRegister)), - cli.WithListFormForm(list.NewForm()), + cli.WithListFormForm(list.NewForm(rpcClient)), cli.WithShowPasswordForm(password.NewForm()), ) diff --git a/internal/cli/form/list/form.go b/internal/cli/form/list/form.go index b1edf43..62a5cf5 100644 --- a/internal/cli/form/list/form.go +++ b/internal/cli/form/list/form.go @@ -1,6 +1,10 @@ package list import ( + "context" + "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "strings" "github.com/charmbracelet/bubbles/help" @@ -34,16 +38,18 @@ type Form struct { passwords list.Model err error + rpcClient *client.RPCClient //usecase *login.Usecase } -func NewForm() *Form { +func NewForm(rpcClient *client.RPCClient) *Form { f := &Form{ help: help.New(), header: "Категории", categories: element.CreateDefaultList("Категории:", defaultWidth, listHeight), passwords: element.CreateDefaultList("Пароли:", defaultWidth, listHeight), + rpcClient: rpcClient, //usecase: usecase, } @@ -68,6 +74,25 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { f.state = stateShowCategoryList // todo получаем данные из базы + // todo получить все данные + in := &client.GetAllDataIn{ + Limit: 10, + Offset: 0, + } + out, err := f.rpcClient.GetAllData(context.TODO(), in) + if err != nil { + if s, ok := status.FromError(err); ok { + if s.Code() == codes.PermissionDenied { + return f.main.Update(message.OpenLoginFormMessage{}) + } + return f, nil + } + + f.err = err + return f, nil + } + + _ = out f.categories.SetItems(nil) f.categories.InsertItem(len(f.categories.Items()), element.Item{Name: "Логины"}) diff --git a/internal/cli/form/login/form.go b/internal/cli/form/login/form.go index 0e74287..c3d2c38 100644 --- a/internal/cli/form/login/form.go +++ b/internal/cli/form/login/form.go @@ -86,12 +86,19 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: for i := range f.elements { - switch e := f.elements[i].(type) { - case textinput.Model: + if e, ok := f.elements[i].(textinput.Model); ok { e.Width = msg.Width } } return f, nil + case message.OpenLoginFormMessage: + for i := range f.elements { + switch e := f.elements[i].(type) { + case textinput.Model: + e.SetValue("") + f.elements[i] = e + } + } case tea.KeyMsg: switch { case key.Matches(msg, common.Keys.Quit): diff --git a/internal/cli/model.go b/internal/cli/model.go index 6eea16b..516e364 100644 --- a/internal/cli/model.go +++ b/internal/cli/model.go @@ -45,8 +45,8 @@ func InitModel(opts ...Option) *MainModel { func (m *MainModel) Init() tea.Cmd { return tea.Batch( func() tea.Msg { - return message.SuccessLoginMessage{} - //return message.OpenLoginFormMessage{} + //return message.SuccessLoginMessage{} + return message.OpenLoginFormMessage{} }, ) } @@ -61,7 +61,7 @@ func (m *MainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // Forms case message.OpenLoginFormMessage: - return m.formLogin.Update(tea.ClearScreen()) + return m.formLogin.Update(message.OpenLoginFormMessage{}) case message.OpenRegisterFormMessage: return m.formRegister.Update(tea.ClearScreen()) case message.OpenCategoryListFormMessage: @@ -75,8 +75,6 @@ func (m *MainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case message.SuccessLoginMessage: m.storeTokens.SaveTokens(msg.AccessToken, msg.RefreshToken) - // todo получить все данные - return m.Update(message.OpenCategoryListFormMessage{}) case message.SuccessRegisterMessage: m.storeTokens.SaveTokens(msg.AccessToken, msg.RefreshToken) diff --git a/internal/infrastructure/rpc/client/client.go b/internal/infrastructure/rpc/client/client.go index e389452..7c107d9 100644 --- a/internal/infrastructure/rpc/client/client.go +++ b/internal/infrastructure/rpc/client/client.go @@ -9,6 +9,7 @@ import ( "github.com/bjlag/go-keeper/internal/generated/rpc" "github.com/bjlag/go-keeper/internal/infrastructure/rpc/interceptor" + "github.com/bjlag/go-keeper/internal/infrastructure/store/client/token" ) type RPCClient struct { @@ -16,12 +17,13 @@ type RPCClient struct { client rpc.KeeperClient } -func NewRPCClient(serverHost string, serverPort int, log *zap.Logger) (*RPCClient, error) { +func NewRPCClient(serverHost string, serverPort int, tokensStore *token.Store, log *zap.Logger) (*RPCClient, error) { conn, err := grpc.NewClient( fmt.Sprintf("%s:%d", serverHost, serverPort), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithChainUnaryInterceptor( interceptor.LoggerClientInterceptor(log), + interceptor.AuthClientInterceptor(tokensStore), ), ) if err != nil { diff --git a/internal/infrastructure/rpc/client/get_all_data.go b/internal/infrastructure/rpc/client/get_all_data.go index 51f8700..bff219d 100644 --- a/internal/infrastructure/rpc/client/get_all_data.go +++ b/internal/infrastructure/rpc/client/get_all_data.go @@ -24,7 +24,7 @@ type GetAllDataItem struct { UpdatedAt time.Time } -func (c RPCClient) GetAllData(ctx context.Context, in GetAllDataIn) (*GetAllDataOut, error) { +func (c RPCClient) GetAllData(ctx context.Context, in *GetAllDataIn) (*GetAllDataOut, error) { const op = "client.rpc.GetAllData" rpcIn := &rpc.GetAllDataIn{ @@ -37,17 +37,17 @@ func (c RPCClient) GetAllData(ctx context.Context, in GetAllDataIn) (*GetAllData return nil, fmt.Errorf("%s: %w", op, err) } - if out == nil || len(out.Items) == 0 { + if out == nil || len(out.GetItems()) == 0 { return nil, nil } - items := make([]GetAllDataItem, len(out.Items)) - for i, item := range out.Items { + items := make([]GetAllDataItem, len(out.GetItems())) + for i, item := range out.GetItems() { items[i] = GetAllDataItem{ - GUID: item.Guid, - EncryptedData: item.EncryptedData, - CreatedAt: item.CreatedAt.AsTime(), - UpdatedAt: item.UpdatedAt.AsTime(), + GUID: item.GetGuid(), + EncryptedData: item.GetEncryptedData(), + CreatedAt: item.GetCreatedAt().AsTime(), + UpdatedAt: item.GetUpdatedAt().AsTime(), } } diff --git a/internal/infrastructure/rpc/interceptor/auth.go b/internal/infrastructure/rpc/interceptor/auth.go index 17ae45a..053d305 100644 --- a/internal/infrastructure/rpc/interceptor/auth.go +++ b/internal/infrastructure/rpc/interceptor/auth.go @@ -3,6 +3,7 @@ package interceptor import ( "context" "errors" + "fmt" "strings" "go.uber.org/zap" @@ -13,6 +14,7 @@ import ( "github.com/bjlag/go-keeper/internal/generated/rpc" "github.com/bjlag/go-keeper/internal/infrastructure/auth" + "github.com/bjlag/go-keeper/internal/infrastructure/store/client/token" ) const ( @@ -26,7 +28,7 @@ var methodSkip = map[string]struct{}{ rpc.Keeper_RefreshTokens_FullMethodName: {}, } -func CheckAccessTokenInterceptor(jwt *auth.JWT, log *zap.Logger) grpc.UnaryServerInterceptor { +func CheckAccessTokenServerInterceptor(jwt *auth.JWT, log *zap.Logger) grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { if _, ok := methodSkip[info.FullMethod]; ok { return handler(ctx, req) @@ -42,12 +44,12 @@ func CheckAccessTokenInterceptor(jwt *auth.JWT, log *zap.Logger) grpc.UnaryServe return nil, status.Errorf(codes.PermissionDenied, "permission denied") } - token, found := strings.CutPrefix(meta[0], bearerAuth) + accessToken, found := strings.CutPrefix(meta[0], bearerAuth) if !found { return nil, status.Errorf(codes.PermissionDenied, "permission denied") } - userGUID, err := jwt.GetUserGUIDFromAccessToken(strings.TrimLeft(token, " ")) + userGUID, err := jwt.GetUserGUIDFromAccessToken(strings.TrimLeft(accessToken, " ")) if err != nil { if errors.Is(err, auth.ErrInvalidToken) { return nil, status.Errorf(codes.PermissionDenied, "permission denied") @@ -59,3 +61,39 @@ func CheckAccessTokenInterceptor(jwt *auth.JWT, log *zap.Logger) grpc.UnaryServe return handler(auth.UserGUIDWithCtx(ctx, userGUID), req) } } + +func AuthClientInterceptor(tokens *token.Store) grpc.UnaryClientInterceptor { + return func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + if _, ok := methodSkip[method]; ok { + return invoker(ctx, method, req, reply, cc, opts...) + } + + ctx = metadata.AppendToOutgoingContext(ctx, AuthMeta, fmt.Sprintf("%s %s", bearerAuth, tokens.AccessToken())) + err := invoker(ctx, method, req, reply, cc, opts...) + if status.Code(err) == codes.PermissionDenied { + in := &rpc.RefreshTokensIn{ + RefreshToken: tokens.RefreshToken(), + } + out := &rpc.RefreshTokensOut{} + err = cc.Invoke(ctx, rpc.Keeper_RefreshTokens_FullMethodName, in, out) + if err != nil { + if status.Code(err) == codes.FailedPrecondition { + return status.Errorf(codes.PermissionDenied, err.Error()) + } + return err + } + + tokens.SaveTokens(out.GetAccessToken(), out.GetRefreshToken()) + + md, ok := metadata.FromOutgoingContext(ctx) + if !ok { + md = metadata.New(nil) + } + + md.Set(AuthMeta, fmt.Sprintf("%s %s", bearerAuth, tokens.AccessToken())) + err = invoker(metadata.NewOutgoingContext(ctx, md), method, req, reply, cc, opts...) + } + + return err + } +} diff --git a/internal/infrastructure/rpc/server/server.go b/internal/infrastructure/rpc/server/server.go index ba352f0..40f3b64 100644 --- a/internal/infrastructure/rpc/server/server.go +++ b/internal/infrastructure/rpc/server/server.go @@ -52,7 +52,7 @@ func (s RPCServer) Start(ctx context.Context) error { grpcServer := grpc.NewServer( grpc.ChainUnaryInterceptor( interceptor.LoggerServerInterceptor(s.log), - interceptor.CheckAccessTokenInterceptor(s.jwt, s.log), + interceptor.CheckAccessTokenServerInterceptor(s.jwt, s.log), ), ) From 02d70e18507f8979c14d2e5fcb02b3f07cb8f779 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Tue, 4 Mar 2025 18:17:17 +0300 Subject: [PATCH 31/94] client: refactoring --- internal/app/client/app.go | 24 ++--- internal/cli/common/message.go | 5 + internal/cli/element/button.go | 10 +- internal/cli/element/list.go | 19 +++- internal/cli/element/password.go | 9 -- internal/cli/element/textinput.go | 18 ++++ internal/cli/message/message.go | 27 ------ internal/cli/model.go | 95 ------------------- internal/cli/model/item/message.go | 13 +++ .../password/form.go => model/item/model.go} | 51 +++++----- internal/cli/model/list/message.go | 11 +++ .../list/form.go => model/list/model.go} | 86 ++++++++++------- internal/cli/model/login/message.go | 8 ++ .../login/form.go => model/login/model.go} | 45 +++++---- internal/cli/model/master/model.go | 92 ++++++++++++++++++ internal/cli/model/master/option.go | 45 +++++++++ internal/cli/model/register/message.go | 12 +++ .../form.go => model/register/model.go} | 49 +++++----- internal/cli/option.go | 45 --------- internal/cli/{element => style}/style.go | 35 +------ 20 files changed, 373 insertions(+), 326 deletions(-) create mode 100644 internal/cli/common/message.go delete mode 100644 internal/cli/element/password.go create mode 100644 internal/cli/element/textinput.go delete mode 100644 internal/cli/message/message.go delete mode 100644 internal/cli/model.go create mode 100644 internal/cli/model/item/message.go rename internal/cli/{form/password/form.go => model/item/model.go} (80%) create mode 100644 internal/cli/model/list/message.go rename internal/cli/{form/list/form.go => model/list/model.go} (70%) create mode 100644 internal/cli/model/login/message.go rename internal/cli/{form/login/form.go => model/login/model.go} (83%) create mode 100644 internal/cli/model/master/model.go create mode 100644 internal/cli/model/master/option.go create mode 100644 internal/cli/model/register/message.go rename internal/cli/{form/register/form.go => model/register/model.go} (81%) delete mode 100644 internal/cli/option.go rename internal/cli/{element => style}/style.go (67%) diff --git a/internal/app/client/app.go b/internal/app/client/app.go index f33810d..11eedf0 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -10,11 +10,11 @@ import ( _ "github.com/mattn/go-sqlite3" "go.uber.org/zap" - "github.com/bjlag/go-keeper/internal/cli" - "github.com/bjlag/go-keeper/internal/cli/form/list" - formLogin "github.com/bjlag/go-keeper/internal/cli/form/login" - "github.com/bjlag/go-keeper/internal/cli/form/password" - formRegister "github.com/bjlag/go-keeper/internal/cli/form/register" + "github.com/bjlag/go-keeper/internal/cli/model/item" + "github.com/bjlag/go-keeper/internal/cli/model/list" + formLogin "github.com/bjlag/go-keeper/internal/cli/model/login" + "github.com/bjlag/go-keeper/internal/cli/model/master" + formRegister "github.com/bjlag/go-keeper/internal/cli/model/register" rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" "github.com/bjlag/go-keeper/internal/infrastructure/store/client/token" "github.com/bjlag/go-keeper/internal/usecase/client/login" @@ -58,13 +58,13 @@ func (a *App) Run(ctx context.Context) error { ucLogin := login.NewUsecase(rpcClient) ucRegister := register.NewUsecase(rpcClient) - model := cli.InitModel( - cli.WithStoreTokens(storeTokens), + m := master.InitModel( + master.WithStoreTokens(storeTokens), - cli.WithLoginForm(formLogin.NewForm(ucLogin)), - cli.WithRegisterForm(formRegister.NewForm(ucRegister)), - cli.WithListFormForm(list.NewForm(rpcClient)), - cli.WithShowPasswordForm(password.NewForm()), + master.WithLoginForm(formLogin.InitModel(ucLogin)), + master.WithRegisterForm(formRegister.InitModel(ucRegister)), + master.WithListFormForm(list.InitModel(rpcClient)), + master.WithShowPasswordForm(item.InitModel()), ) f, err := tea.LogToFile("debug.log", "debug") @@ -76,7 +76,7 @@ func (a *App) Run(ctx context.Context) error { _ = f.Close() }() - _, err = tea.NewProgram(model, tea.WithAltScreen(), tea.WithContext(ctx)).Run() + _, err = tea.NewProgram(m, tea.WithAltScreen(), tea.WithContext(ctx)).Run() if err != nil { a.log.Error("failed to run cli program", zap.Error(err)) } diff --git a/internal/cli/common/message.go b/internal/cli/common/message.go new file mode 100644 index 0000000..604af77 --- /dev/null +++ b/internal/cli/common/message.go @@ -0,0 +1,5 @@ +package common + +type BackMessage struct { + State int +} diff --git a/internal/cli/element/button.go b/internal/cli/element/button.go index 78d1433..87a9ff0 100644 --- a/internal/cli/element/button.go +++ b/internal/cli/element/button.go @@ -2,7 +2,7 @@ package element import ( "fmt" - + "github.com/bjlag/go-keeper/internal/cli/style" "github.com/charmbracelet/lipgloss" ) @@ -21,6 +21,14 @@ func NewButton(text string) Button { } } +func CreateDefaultButton(text string) Button { + b := NewButton(text) + b.FocusedStyle = style.FocusedStyle + b.BlurredStyle = style.BlurredStyle + + return b +} + func (b *Button) String() string { if b.focus { return fmt.Sprintf("[ %s ]", b.FocusedStyle.Render(b.text)) diff --git a/internal/cli/element/list.go b/internal/cli/element/list.go index f821c7d..8436546 100644 --- a/internal/cli/element/list.go +++ b/internal/cli/element/list.go @@ -5,10 +5,25 @@ import ( "io" "strings" + "github.com/bjlag/go-keeper/internal/cli/style" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" ) +func CreateDefaultList(title string, with, height int, items ...list.Item) list.Model { + l := list.New(items, ItemDelegate{}, with, height) + + l.Title = title + l.SetShowStatusBar(false) + l.SetFilteringEnabled(false) + //l.SetShowTitle(false) + l.Styles.Title = style.ListTitleStyle + l.Styles.PaginationStyle = style.ListPaginationStyle + l.Styles.HelpStyle = style.ListHelpStyle + + return l +} + type Item struct { ID string Name string @@ -38,10 +53,10 @@ func (d ItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list str := fmt.Sprintf("%d. %s", index+1, i.Name) - fn := ListItemStyle.Render + fn := style.ListItemStyle.Render if index == m.Index() { fn = func(s ...string) string { - return SelectedListItemStyle.Render("> " + strings.Join(s, " ")) + return style.SelectedListItemStyle.Render("> " + strings.Join(s, " ")) } } diff --git a/internal/cli/element/password.go b/internal/cli/element/password.go deleted file mode 100644 index 29358ea..0000000 --- a/internal/cli/element/password.go +++ /dev/null @@ -1,9 +0,0 @@ -package element - -type Password struct { - ID string - Name string - Login string - Password string - Note string -} diff --git a/internal/cli/element/textinput.go b/internal/cli/element/textinput.go new file mode 100644 index 0000000..e263747 --- /dev/null +++ b/internal/cli/element/textinput.go @@ -0,0 +1,18 @@ +package element + +import ( + "github.com/charmbracelet/bubbles/textinput" + + "github.com/bjlag/go-keeper/internal/cli/style" +) + +func CreateDefaultTextInput(placeholder string, limit int) textinput.Model { + m := textinput.New() + + m.Cursor.Style = style.CursorStyle + m.PlaceholderStyle = style.BlurredStyle + m.CharLimit = limit + m.Placeholder = placeholder + + return m +} diff --git a/internal/cli/message/message.go b/internal/cli/message/message.go deleted file mode 100644 index 425ddbe..0000000 --- a/internal/cli/message/message.go +++ /dev/null @@ -1,27 +0,0 @@ -package message - -import "github.com/bjlag/go-keeper/internal/cli/element" - -type SuccessLoginMessage struct { - AccessToken string - RefreshToken string -} - -type SuccessRegisterMessage struct { - AccessToken string - RefreshToken string -} - -type OpenLoginFormMessage struct{} - -type OpenRegisterFormMessage struct{} - -type OpenCategoryListFormMessage struct{} - -type OpenPasswordFormMessage struct { - Item element.Item -} - -type OpenPasswordListFormMessage struct { - Category element.Item -} diff --git a/internal/cli/model.go b/internal/cli/model.go deleted file mode 100644 index 516e364..0000000 --- a/internal/cli/model.go +++ /dev/null @@ -1,95 +0,0 @@ -package cli - -import ( - "strings" - - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/key" - tea "github.com/charmbracelet/bubbletea" - - "github.com/bjlag/go-keeper/internal/cli/common" - "github.com/bjlag/go-keeper/internal/cli/element" - listf "github.com/bjlag/go-keeper/internal/cli/form/list" - "github.com/bjlag/go-keeper/internal/cli/form/login" - "github.com/bjlag/go-keeper/internal/cli/form/password" - "github.com/bjlag/go-keeper/internal/cli/form/register" - "github.com/bjlag/go-keeper/internal/cli/message" - "github.com/bjlag/go-keeper/internal/infrastructure/store/client/token" -) - -type MainModel struct { - help help.Model - header string - - formLogin *login.Form - formRegister *register.Form - formList *listf.Form - formPassword *password.Form - - storeTokens *token.Store -} - -func InitModel(opts ...Option) *MainModel { - m := &MainModel{ - help: help.New(), - header: "Go Keeper", - } - - for _, opt := range opts { - opt(m) - } - - return m -} - -func (m *MainModel) Init() tea.Cmd { - return tea.Batch( - func() tea.Msg { - //return message.SuccessLoginMessage{} - return message.OpenLoginFormMessage{} - }, - ) -} - -func (m *MainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg := msg.(type) { - case tea.KeyMsg: - switch { - case key.Matches(msg, common.Keys.Quit): - return m, tea.Quit - } - - // Forms - case message.OpenLoginFormMessage: - return m.formLogin.Update(message.OpenLoginFormMessage{}) - case message.OpenRegisterFormMessage: - return m.formRegister.Update(tea.ClearScreen()) - case message.OpenCategoryListFormMessage: - return m.formList.Update(msg) - case message.OpenPasswordListFormMessage: - return m.formList.Update(msg) - case message.OpenPasswordFormMessage: - return m.formPassword.Update(msg) - - // Success - case message.SuccessLoginMessage: - m.storeTokens.SaveTokens(msg.AccessToken, msg.RefreshToken) - - return m.Update(message.OpenCategoryListFormMessage{}) - case message.SuccessRegisterMessage: - m.storeTokens.SaveTokens(msg.AccessToken, msg.RefreshToken) - - return m.Update(message.OpenCategoryListFormMessage{}) - } - - return m, nil -} - -func (m *MainModel) View() string { - var b strings.Builder - - b.WriteString(element.TitleStyle.Render(m.header)) - b.WriteRune('\n') - - return b.String() -} diff --git a/internal/cli/model/item/message.go b/internal/cli/model/item/message.go new file mode 100644 index 0000000..3d0a1c0 --- /dev/null +++ b/internal/cli/model/item/message.go @@ -0,0 +1,13 @@ +package item + +import ( + tea "github.com/charmbracelet/bubbletea" + + "github.com/bjlag/go-keeper/internal/cli/element" +) + +type OpenMessage struct { + BackModel tea.Model + BackState int + Item element.Item +} diff --git a/internal/cli/form/password/form.go b/internal/cli/model/item/model.go similarity index 80% rename from internal/cli/form/password/form.go rename to internal/cli/model/item/model.go index 01b1f59..2d0bcbc 100644 --- a/internal/cli/form/password/form.go +++ b/internal/cli/model/item/model.go @@ -1,4 +1,4 @@ -package password +package item import ( "errors" @@ -11,8 +11,7 @@ import ( "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/element" - "github.com/bjlag/go-keeper/internal/cli/message" - "github.com/bjlag/go-keeper/internal/usecase/client/register" + "github.com/bjlag/go-keeper/internal/cli/style" ) const ( @@ -23,7 +22,7 @@ const ( posBackBtn ) -type Form struct { +type Model struct { main tea.Model help help.Model header string @@ -31,13 +30,14 @@ type Form struct { pos int err error - category string + backModel tea.Model + backState int - usecase *register.Usecase + category string } -func NewForm() *Form { - f := &Form{ +func InitModel() *Model { + f := &Model{ help: help.New(), header: "Регистрация", elements: []interface{}{ @@ -52,21 +52,21 @@ func NewForm() *Form { if e, ok := f.elements[posLogin].(textinput.Model); ok { e.Focus() - f.elements[posLogin] = element.SetFocusStyle(e) + f.elements[posLogin] = style.SetFocusStyle(e) } return f } -func (f *Form) SetMainModel(m tea.Model) { +func (f *Model) SetMainModel(m tea.Model) { f.main = m } -func (f *Form) Init() tea.Cmd { +func (f *Model) Init() tea.Cmd { return nil } -func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: for i := range f.elements { @@ -76,12 +76,15 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } return f, nil - case message.OpenPasswordFormMessage: + case OpenMessage: // todo получаем данные из базы f.header = msg.Item.Name f.category = "Категория" + f.backState = msg.BackState + f.backModel = msg.BackModel + if input, ok := f.elements[posLogin].(textinput.Model); ok { input.SetValue("login") f.elements[posLogin] = input @@ -115,12 +118,12 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case textinput.Model: if i == f.pos { e.Focus() - f.elements[i] = element.SetFocusStyle(e) + f.elements[i] = style.SetFocusStyle(e) continue } e.Blur() - f.elements[i] = element.SetNoStyle(e) + f.elements[i] = style.SetNoStyle(e) case element.Button: if i == f.pos { e.Focus() @@ -138,22 +141,26 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch { case f.pos == posBackBtn: - return f.main.Update(message.OpenPasswordListFormMessage{}) + return f.backModel.Update(common.BackMessage{ + State: f.backState, + }) } return f, nil case key.Matches(msg, common.Keys.Back): - return f.main.Update(message.OpenPasswordListFormMessage{}) + return f.backModel.Update(common.BackMessage{ + State: f.backState, + }) } } return f, f.updateInputs(msg) } -func (f *Form) View() string { +func (f *Model) View() string { var b strings.Builder - b.WriteString(element.TitleStyle.Render(f.header)) + b.WriteString(style.TitleStyle.Render(f.header)) b.WriteRune('\n') b.WriteString("Категория: ") @@ -186,7 +193,7 @@ func (f *Form) View() string { // выводим ошибки валидации if f.err != nil && (errors.As(f.err, &errValidate) || errors.As(f.err, &errForm)) { - b.WriteString(element.ErrorBlockStyle.Render(f.err.Error())) + b.WriteString(style.ErrorBlockStyle.Render(f.err.Error())) b.WriteRune('\n') } @@ -196,13 +203,13 @@ func (f *Form) View() string { // выводим прочие ошибки if f.err != nil && !(errors.As(f.err, &errValidate) || errors.As(f.err, &errForm)) { b.WriteRune('\n') - b.WriteString(element.ErrorBlockStyle.Render(f.err.Error())) + b.WriteString(style.ErrorBlockStyle.Render(f.err.Error())) } return b.String() } -func (f *Form) updateInputs(msg tea.Msg) tea.Cmd { +func (f *Model) updateInputs(msg tea.Msg) tea.Cmd { cmds := make([]tea.Cmd, len(f.elements)) for i := range f.elements { diff --git a/internal/cli/model/list/message.go b/internal/cli/model/list/message.go new file mode 100644 index 0000000..096fb97 --- /dev/null +++ b/internal/cli/model/list/message.go @@ -0,0 +1,11 @@ +package list + +import "github.com/bjlag/go-keeper/internal/cli/element" + +type GetAllDataMessage struct{} + +type OpenCategoryListMessage struct{} + +type OpenPasswordListMessage struct { + Category element.Item +} diff --git a/internal/cli/form/list/form.go b/internal/cli/model/list/model.go similarity index 70% rename from internal/cli/form/list/form.go rename to internal/cli/model/list/model.go index 62a5cf5..a7dec0f 100644 --- a/internal/cli/form/list/form.go +++ b/internal/cli/model/list/model.go @@ -2,26 +2,26 @@ package list import ( "context" - "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" "strings" "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/element" - "github.com/bjlag/go-keeper/internal/cli/message" + "github.com/bjlag/go-keeper/internal/cli/model/item" + "github.com/bjlag/go-keeper/internal/cli/model/login" + "github.com/bjlag/go-keeper/internal/cli/style" + "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" ) -type state int - const ( - stateShowCategoryList state = iota - stateShowPasswordList + stateCategoryList int = iota + statePasswordList ) const ( @@ -29,10 +29,10 @@ const ( listHeight = 14 ) -type Form struct { +type Model struct { main tea.Model help help.Model - state state + state int header string categories list.Model passwords list.Model @@ -42,8 +42,8 @@ type Form struct { //usecase *login.Usecase } -func NewForm(rpcClient *client.RPCClient) *Form { - f := &Form{ +func InitModel(rpcClient *client.RPCClient) *Model { + f := &Model{ help: help.New(), header: "Категории", categories: element.CreateDefaultList("Категории:", defaultWidth, listHeight), @@ -56,25 +56,31 @@ func NewForm(rpcClient *client.RPCClient) *Form { return f } -func (f *Form) SetMainModel(m tea.Model) { +func (f *Model) SetMainModel(m tea.Model) { f.main = m } -func (f *Form) Init() tea.Cmd { +func (f *Model) Init() tea.Cmd { return nil } -func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: f.categories.SetWidth(msg.Width) f.passwords.SetWidth(msg.Width) return f, nil - case message.OpenCategoryListFormMessage: - f.state = stateShowCategoryList + case common.BackMessage: + switch msg.State { + case stateCategoryList: + return f.Update(OpenCategoryListMessage{}) + case statePasswordList: + return f.Update(OpenPasswordListMessage{}) + } + + case GetAllDataMessage: + f.state = stateCategoryList - // todo получаем данные из базы - // todo получить все данные in := &client.GetAllDataIn{ Limit: 10, Offset: 0, @@ -83,7 +89,7 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if err != nil { if s, ok := status.FromError(err); ok { if s.Code() == codes.PermissionDenied { - return f.main.Update(message.OpenLoginFormMessage{}) + return f.main.Update(login.OpenMessage{}) } return f, nil } @@ -94,6 +100,12 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { _ = out + return f.Update(OpenCategoryListMessage{}) + case OpenCategoryListMessage: + f.state = stateCategoryList + + // todo получаем данные из базы + f.categories.SetItems(nil) f.categories.InsertItem(len(f.categories.Items()), element.Item{Name: "Логины"}) f.categories.InsertItem(len(f.categories.Items()), element.Item{Name: "Тексты"}) @@ -101,8 +113,8 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { f.categories.InsertItem(len(f.categories.Items()), element.Item{Name: "Банковские карты"}) return f, nil - case message.OpenPasswordListFormMessage: - f.state = stateShowPasswordList + case OpenPasswordListMessage: + f.state = statePasswordList // todo получаем данные из базы @@ -120,16 +132,18 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return f, tea.Quit case key.Matches(msg, common.Keys.Enter): switch f.state { - case stateShowCategoryList: + case stateCategoryList: if i, ok := f.categories.SelectedItem().(element.Item); ok { - return f.Update(message.OpenPasswordListFormMessage{ + return f.Update(OpenPasswordListMessage{ Category: i, }) } - case stateShowPasswordList: + case statePasswordList: if i, ok := f.passwords.SelectedItem().(element.Item); ok { - return f.main.Update(message.OpenPasswordFormMessage{ - Item: i, + return f.main.Update(item.OpenMessage{ + BackModel: f, + BackState: f.state, + Item: i, }) } } @@ -137,33 +151,33 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return f, nil case key.Matches(msg, common.Keys.Back): switch f.state { - case stateShowPasswordList: - return f.Update(message.OpenCategoryListFormMessage{}) + case statePasswordList: + return f.Update(OpenCategoryListMessage{}) } } } var cmd tea.Cmd switch f.state { - case stateShowCategoryList: + case stateCategoryList: f.categories, cmd = f.categories.Update(msg) - case stateShowPasswordList: + case statePasswordList: f.passwords, cmd = f.passwords.Update(msg) } return f, cmd } -func (f *Form) View() string { +func (f *Model) View() string { var b strings.Builder - b.WriteString(element.TitleStyle.Render(f.header)) + b.WriteString(style.TitleStyle.Render(f.header)) b.WriteRune('\n') switch f.state { - case stateShowCategoryList: + case stateCategoryList: b.WriteString(f.categories.View()) - case stateShowPasswordList: + case statePasswordList: b.WriteString(f.passwords.View()) } @@ -173,7 +187,7 @@ func (f *Form) View() string { // выводим прочие ошибки if f.err != nil { b.WriteRune('\n') - b.WriteString(element.ErrorBlockStyle.Render(f.err.Error())) + b.WriteString(style.ErrorBlockStyle.Render(f.err.Error())) } return b.String() diff --git a/internal/cli/model/login/message.go b/internal/cli/model/login/message.go new file mode 100644 index 0000000..aa0e9ae --- /dev/null +++ b/internal/cli/model/login/message.go @@ -0,0 +1,8 @@ +package login + +type OpenMessage struct{} + +type SuccessMessage struct { + AccessToken string + RefreshToken string +} diff --git a/internal/cli/form/login/form.go b/internal/cli/model/login/model.go similarity index 83% rename from internal/cli/form/login/form.go rename to internal/cli/model/login/model.go index c3d2c38..b364fca 100644 --- a/internal/cli/form/login/form.go +++ b/internal/cli/model/login/model.go @@ -14,7 +14,8 @@ import ( "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/element" - "github.com/bjlag/go-keeper/internal/cli/message" + "github.com/bjlag/go-keeper/internal/cli/model/register" + "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/infrastructure/validator" "github.com/bjlag/go-keeper/internal/usecase/client/login" ) @@ -32,7 +33,7 @@ const ( var errPasswordInvalid = common.NewFormError("Неверный email или пароль") -type Form struct { +type Model struct { main tea.Model help help.Model header string @@ -43,8 +44,8 @@ type Form struct { usecase *login.Usecase } -func NewForm(usecase *login.Usecase) *Form { - f := &Form{ +func InitModel(usecase *login.Usecase) *Model { + f := &Model{ help: help.New(), header: "Авторизация", elements: []interface{}{ @@ -62,7 +63,7 @@ func NewForm(usecase *login.Usecase) *Form { if e, ok := f.elements[i].(textinput.Model); ok { if i == posEmail { e.Focus() - f.elements[i] = element.SetFocusStyle(e) + f.elements[i] = style.SetFocusStyle(e) continue } e.EchoMode = textinput.EchoPassword @@ -74,15 +75,15 @@ func NewForm(usecase *login.Usecase) *Form { return f } -func (f *Form) SetMainModel(m tea.Model) { +func (f *Model) SetMainModel(m tea.Model) { f.main = m } -func (f *Form) Init() tea.Cmd { +func (f *Model) Init() tea.Cmd { return nil } -func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: for i := range f.elements { @@ -91,7 +92,7 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } return f, nil - case message.OpenLoginFormMessage: + case OpenMessage: for i := range f.elements { switch e := f.elements[i].(type) { case textinput.Model: @@ -121,12 +122,12 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case textinput.Model: if i == f.pos { e.Focus() - f.elements[i] = element.SetFocusStyle(e) + f.elements[i] = style.SetFocusStyle(e) continue } e.Blur() - f.elements[i] = element.SetNoStyle(e) + f.elements[i] = style.SetNoStyle(e) case element.Button: if i == f.pos { e.Focus() @@ -146,7 +147,9 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case f.pos == posSubmitBtn || f.pos == posEmail || f.pos == posPassword: return f.submit() case f.pos == posRegisterBtn: - return f.main.Update(message.OpenRegisterFormMessage{}) + return f.main.Update(register.OpenMessage{ + BackModel: f, + }) case f.pos == posCloseBtn: return f, tea.Quit } @@ -158,10 +161,10 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return f, f.updateInputs(msg) } -func (f *Form) View() string { +func (f *Model) View() string { var b strings.Builder - b.WriteString(element.TitleStyle.Render(f.header)) + b.WriteString(style.TitleStyle.Render(f.header)) b.WriteRune('\n') for i := range f.elements { @@ -187,7 +190,7 @@ func (f *Form) View() string { // выводим ошибки валидации if f.err != nil && (errors.As(f.err, &errValidate) || errors.As(f.err, &errForm)) { - b.WriteString(element.ErrorBlockStyle.Render(f.err.Error())) + b.WriteString(style.ErrorBlockStyle.Render(f.err.Error())) b.WriteRune('\n') } @@ -197,13 +200,13 @@ func (f *Form) View() string { // выводим прочие ошибки if f.err != nil && !(errors.As(f.err, &errValidate) || errors.As(f.err, &errForm)) { b.WriteRune('\n') - b.WriteString(element.ErrorBlockStyle.Render(f.err.Error())) + b.WriteString(style.ErrorBlockStyle.Render(f.err.Error())) } return b.String() } -func (f *Form) submit() (tea.Model, tea.Cmd) { +func (f *Model) submit() (tea.Model, tea.Cmd) { errValidate := common.NewValidateError() email, ok := f.elements[posEmail].(textinput.Model) @@ -218,12 +221,12 @@ func (f *Form) submit() (tea.Model, tea.Cmd) { } if !validator.ValidateEmail(email.Value()) { - f.elements[posEmail] = element.SetErrorStyle(email) + f.elements[posEmail] = style.SetErrorStyle(email) errValidate.AddError("Неверно заполнен email") } if password.Value() == "" { - f.elements[posPassword] = element.SetErrorStyle(password) + f.elements[posPassword] = style.SetErrorStyle(password) errValidate.AddError("Не заполнен пароль") } @@ -250,13 +253,13 @@ func (f *Form) submit() (tea.Model, tea.Cmd) { return f, nil } - return f.main.Update(message.SuccessLoginMessage{ + return f.main.Update(SuccessMessage{ AccessToken: result.AccessToken, RefreshToken: result.RefreshToken, }) } -func (f *Form) updateInputs(msg tea.Msg) tea.Cmd { +func (f *Model) updateInputs(msg tea.Msg) tea.Cmd { cmds := make([]tea.Cmd, len(f.elements)) for i := range f.elements { diff --git a/internal/cli/model/master/model.go b/internal/cli/model/master/model.go new file mode 100644 index 0000000..f983cc6 --- /dev/null +++ b/internal/cli/model/master/model.go @@ -0,0 +1,92 @@ +package master + +import ( + "strings" + + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" + tea "github.com/charmbracelet/bubbletea" + + "github.com/bjlag/go-keeper/internal/cli/common" + "github.com/bjlag/go-keeper/internal/cli/model/item" + listf "github.com/bjlag/go-keeper/internal/cli/model/list" + "github.com/bjlag/go-keeper/internal/cli/model/login" + "github.com/bjlag/go-keeper/internal/cli/model/register" + "github.com/bjlag/go-keeper/internal/cli/style" + "github.com/bjlag/go-keeper/internal/infrastructure/store/client/token" +) + +type Model struct { + help help.Model + header string + + formLogin *login.Model + formRegister *register.Model + formList *listf.Model + formPassword *item.Model + + storeTokens *token.Store +} + +func InitModel(opts ...Option) *Model { + m := &Model{ + help: help.New(), + header: "Go Keeper", + } + + for _, opt := range opts { + opt(m) + } + + return m +} + +func (m *Model) Init() tea.Cmd { + return tea.Batch( + func() tea.Msg { + //return message.SuccessLoginMessage{} + return login.OpenMessage{} + }, + ) +} + +func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch { + case key.Matches(msg, common.Keys.Quit): + return m, tea.Quit + } + + // Forms + case login.OpenMessage: + return m.formLogin.Update(msg) + case register.OpenMessage: + return m.formRegister.Update(msg) + case listf.OpenCategoryListMessage: + return m.formList.Update(msg) + case listf.OpenPasswordListMessage: + return m.formList.Update(msg) + case item.OpenMessage: + return m.formPassword.Update(msg) + + // Success + case login.SuccessMessage: + m.storeTokens.SaveTokens(msg.AccessToken, msg.RefreshToken) + return m.formList.Update(listf.GetAllDataMessage{}) + case register.SuccessMessage: + m.storeTokens.SaveTokens(msg.AccessToken, msg.RefreshToken) + return m.formList.Update(listf.GetAllDataMessage{}) + } + + return m, nil +} + +func (m *Model) View() string { + var b strings.Builder + + b.WriteString(style.TitleStyle.Render(m.header)) + b.WriteRune('\n') + + return b.String() +} diff --git a/internal/cli/model/master/option.go b/internal/cli/model/master/option.go new file mode 100644 index 0000000..ff04e41 --- /dev/null +++ b/internal/cli/model/master/option.go @@ -0,0 +1,45 @@ +package master + +import ( + "github.com/bjlag/go-keeper/internal/cli/model/item" + "github.com/bjlag/go-keeper/internal/cli/model/list" + "github.com/bjlag/go-keeper/internal/cli/model/login" + "github.com/bjlag/go-keeper/internal/cli/model/register" + "github.com/bjlag/go-keeper/internal/infrastructure/store/client/token" +) + +type Option func(*Model) + +func WithStoreTokens(store *token.Store) Option { + return func(m *Model) { + m.storeTokens = store + } +} + +func WithLoginForm(form *login.Model) Option { + return func(m *Model) { + form.SetMainModel(m) + m.formLogin = form + } +} + +func WithRegisterForm(form *register.Model) Option { + return func(m *Model) { + form.SetMainModel(m) + m.formRegister = form + } +} + +func WithListFormForm(form *list.Model) Option { + return func(m *Model) { + form.SetMainModel(m) + m.formList = form + } +} + +func WithShowPasswordForm(form *item.Model) Option { + return func(m *Model) { + form.SetMainModel(m) + m.formPassword = form + } +} diff --git a/internal/cli/model/register/message.go b/internal/cli/model/register/message.go new file mode 100644 index 0000000..5e36236 --- /dev/null +++ b/internal/cli/model/register/message.go @@ -0,0 +1,12 @@ +package register + +import tea "github.com/charmbracelet/bubbletea" + +type SuccessMessage struct { + AccessToken string + RefreshToken string +} + +type OpenMessage struct { + BackModel tea.Model +} diff --git a/internal/cli/form/register/form.go b/internal/cli/model/register/model.go similarity index 81% rename from internal/cli/form/register/form.go rename to internal/cli/model/register/model.go index 1c249f9..5a8435d 100644 --- a/internal/cli/form/register/form.go +++ b/internal/cli/model/register/model.go @@ -14,7 +14,7 @@ import ( "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/element" - "github.com/bjlag/go-keeper/internal/cli/message" + "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/infrastructure/validator" "github.com/bjlag/go-keeper/internal/usecase/client/register" ) @@ -31,7 +31,7 @@ const ( var errUserAlreadyRegistered = common.NewFormError("Пользователь уже зарегистрирован") -type Form struct { +type Model struct { main tea.Model help help.Model header string @@ -39,11 +39,12 @@ type Form struct { pos int err error - usecase *register.Usecase + backModel tea.Model + usecase *register.Usecase } -func NewForm(usecase *register.Usecase) *Form { - f := &Form{ +func InitModel(usecase *register.Usecase) *Model { + f := &Model{ help: help.New(), header: "Регистрация", elements: []interface{}{ @@ -58,8 +59,8 @@ func NewForm(usecase *register.Usecase) *Form { for i := range f.elements { if e, ok := f.elements[i].(textinput.Model); ok { if i == posEmail { - e.TextStyle = element.FocusedStyle - e.PromptStyle = element.FocusedStyle + e.TextStyle = style.FocusedStyle + e.PromptStyle = style.FocusedStyle e.Focus() f.elements[i] = e @@ -74,15 +75,15 @@ func NewForm(usecase *register.Usecase) *Form { return f } -func (f *Form) SetMainModel(m tea.Model) { +func (f *Model) SetMainModel(m tea.Model) { f.main = m } -func (f *Form) Init() tea.Cmd { +func (f *Model) Init() tea.Cmd { return nil } -func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: for i := range f.elements { @@ -92,6 +93,8 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } return f, nil + case OpenMessage: + f.backModel = msg.BackModel case tea.KeyMsg: switch { case key.Matches(msg, common.Keys.Quit): @@ -114,12 +117,12 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case textinput.Model: if i == f.pos { e.Focus() - f.elements[i] = element.SetFocusStyle(e) + f.elements[i] = style.SetFocusStyle(e) continue } e.Blur() - f.elements[i] = element.SetNoStyle(e) + f.elements[i] = style.SetNoStyle(e) case element.Button: if i == f.pos { e.Focus() @@ -132,6 +135,8 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } return f, nil + case key.Matches(msg, common.Keys.Back): + return f.backModel.Update(common.BackMessage{}) case key.Matches(msg, common.Keys.Enter): f.err = nil @@ -139,7 +144,7 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case f.pos == posSubmitBtn || f.pos == posEmail || f.pos == posPassword: return f.submit() case f.pos == posBackBtn: - return f.main.Update(message.OpenLoginFormMessage{}) + return f.main.Update(common.BackMessage{}) } return f, nil @@ -149,10 +154,10 @@ func (f *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return f, f.updateInputs(msg) } -func (f *Form) View() string { +func (f *Model) View() string { var b strings.Builder - b.WriteString(element.TitleStyle.Render(f.header)) + b.WriteString(style.TitleStyle.Render(f.header)) b.WriteRune('\n') for i := range f.elements { @@ -178,7 +183,7 @@ func (f *Form) View() string { // выводим ошибки валидации if f.err != nil && (errors.As(f.err, &errValidate) || errors.As(f.err, &errForm)) { - b.WriteString(element.ErrorBlockStyle.Render(f.err.Error())) + b.WriteString(style.ErrorBlockStyle.Render(f.err.Error())) b.WriteRune('\n') } @@ -188,13 +193,13 @@ func (f *Form) View() string { // выводим прочие ошибки if f.err != nil && !(errors.As(f.err, &errValidate) || errors.As(f.err, &errForm)) { b.WriteRune('\n') - b.WriteString(element.ErrorBlockStyle.Render(f.err.Error())) + b.WriteString(style.ErrorBlockStyle.Render(f.err.Error())) } return b.String() } -func (f *Form) submit() (tea.Model, tea.Cmd) { +func (f *Model) submit() (tea.Model, tea.Cmd) { errValidate := common.NewValidateError() email, ok := f.elements[posEmail].(textinput.Model) @@ -209,12 +214,12 @@ func (f *Form) submit() (tea.Model, tea.Cmd) { } if !validator.ValidateEmail(email.Value()) { - f.elements[posEmail] = element.SetErrorStyle(email) + f.elements[posEmail] = style.SetErrorStyle(email) errValidate.AddError("Неправильный email") } if !validator.ValidatePassword(password.Value()) { - f.elements[posPassword] = element.SetErrorStyle(password) + f.elements[posPassword] = style.SetErrorStyle(password) errValidate.AddError("Недостаточно сложный пароль") } @@ -241,13 +246,13 @@ func (f *Form) submit() (tea.Model, tea.Cmd) { return f, nil } - return f.main.Update(message.SuccessRegisterMessage{ + return f.main.Update(SuccessMessage{ AccessToken: result.AccessToken, RefreshToken: result.RefreshToken, }) } -func (f *Form) updateInputs(msg tea.Msg) tea.Cmd { +func (f *Model) updateInputs(msg tea.Msg) tea.Cmd { cmds := make([]tea.Cmd, len(f.elements)) for i := range f.elements { diff --git a/internal/cli/option.go b/internal/cli/option.go deleted file mode 100644 index 296198d..0000000 --- a/internal/cli/option.go +++ /dev/null @@ -1,45 +0,0 @@ -package cli - -import ( - "github.com/bjlag/go-keeper/internal/cli/form/list" - "github.com/bjlag/go-keeper/internal/cli/form/login" - "github.com/bjlag/go-keeper/internal/cli/form/password" - "github.com/bjlag/go-keeper/internal/cli/form/register" - "github.com/bjlag/go-keeper/internal/infrastructure/store/client/token" -) - -type Option func(*MainModel) - -func WithStoreTokens(store *token.Store) Option { - return func(m *MainModel) { - m.storeTokens = store - } -} - -func WithLoginForm(form *login.Form) Option { - return func(m *MainModel) { - form.SetMainModel(m) - m.formLogin = form - } -} - -func WithRegisterForm(form *register.Form) Option { - return func(m *MainModel) { - form.SetMainModel(m) - m.formRegister = form - } -} - -func WithListFormForm(form *list.Form) Option { - return func(m *MainModel) { - form.SetMainModel(m) - m.formList = form - } -} - -func WithShowPasswordForm(form *password.Form) Option { - return func(m *MainModel) { - form.SetMainModel(m) - m.formPassword = form - } -} diff --git a/internal/cli/element/style.go b/internal/cli/style/style.go similarity index 67% rename from internal/cli/element/style.go rename to internal/cli/style/style.go index 3617e79..a2c5717 100644 --- a/internal/cli/element/style.go +++ b/internal/cli/style/style.go @@ -1,4 +1,4 @@ -package element +package style import ( "github.com/charmbracelet/bubbles/list" @@ -50,36 +50,3 @@ func SetErrorStyle(input textinput.Model) textinput.Model { input.PromptStyle = ErrorStyle return input } - -func CreateDefaultTextInput(placeholder string, limit int) textinput.Model { - m := textinput.New() - - m.Cursor.Style = CursorStyle - m.PlaceholderStyle = BlurredStyle - m.CharLimit = limit - m.Placeholder = placeholder - - return m -} - -func CreateDefaultButton(text string) Button { - b := NewButton(text) - b.FocusedStyle = FocusedStyle - b.BlurredStyle = BlurredStyle - - return b -} - -func CreateDefaultList(title string, with, height int, items ...list.Item) list.Model { - l := list.New(items, ItemDelegate{}, with, height) - - l.Title = title - l.SetShowStatusBar(false) - l.SetFilteringEnabled(false) - //l.SetShowTitle(false) - l.Styles.Title = ListTitleStyle - l.Styles.PaginationStyle = ListPaginationStyle - l.Styles.HelpStyle = ListHelpStyle - - return l -} From dc6eb3a63a4bd5cf258689772d3e245f51a70977 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Tue, 4 Mar 2025 23:12:34 +0300 Subject: [PATCH 32/94] client: sqlite db --- cmd/migrator/main.go | 6 +++- internal/app/client/app.go | 10 +++---- internal/app/server/app.go | 4 +-- internal/infrastructure/db/sqlite/sqlite.go | 30 +++++++++++++++++++ .../store/{ => server}/data/model.go | 0 .../store/{ => server}/data/store.go | 0 .../store/{ => server}/user/model.go | 0 .../store/{ => server}/user/store.go | 0 .../usecase/server/data/get_all/usecase.go | 2 +- internal/usecase/server/user/login/usecase.go | 2 +- .../server/user/refresh_tokens/usecase.go | 2 +- .../usecase/server/user/register/usecase.go | 2 +- 12 files changed, 45 insertions(+), 13 deletions(-) create mode 100644 internal/infrastructure/db/sqlite/sqlite.go rename internal/infrastructure/store/{ => server}/data/model.go (100%) rename internal/infrastructure/store/{ => server}/data/store.go (100%) rename internal/infrastructure/store/{ => server}/user/model.go (100%) rename internal/infrastructure/store/{ => server}/user/store.go (100%) diff --git a/cmd/migrator/main.go b/cmd/migrator/main.go index 2122f6e..872a1f7 100644 --- a/cmd/migrator/main.go +++ b/cmd/migrator/main.go @@ -18,6 +18,7 @@ import ( "go.uber.org/zap" "github.com/bjlag/go-keeper/internal/infrastructure/db/pg" + "github.com/bjlag/go-keeper/internal/infrastructure/db/sqlite" "github.com/bjlag/go-keeper/internal/infrastructure/logger" ) @@ -61,8 +62,11 @@ func main() { dbConf := cfg.Database db, err = pg.New(pg.GetDSN(dbConf.Host, dbConf.Port, dbConf.Name, dbConf.User, dbConf.Password)).Connect() case typeSqlite: - db, err = sqlx.Open("sqlite3", "./client.db") + db, err = sqlite.New("./client.db").Connect() } + defer func() { + _ = db.Close() + }() if err != nil { log.Error("Failed to open db", zap.Error(err)) diff --git a/internal/app/client/app.go b/internal/app/client/app.go index 11eedf0..4acbb94 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -2,9 +2,7 @@ package client import ( "context" - "database/sql" "fmt" - "os" tea "github.com/charmbracelet/bubbletea" _ "github.com/mattn/go-sqlite3" @@ -15,6 +13,7 @@ import ( formLogin "github.com/bjlag/go-keeper/internal/cli/model/login" "github.com/bjlag/go-keeper/internal/cli/model/master" formRegister "github.com/bjlag/go-keeper/internal/cli/model/register" + "github.com/bjlag/go-keeper/internal/infrastructure/db/sqlite" rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" "github.com/bjlag/go-keeper/internal/infrastructure/store/client/token" "github.com/bjlag/go-keeper/internal/usecase/client/login" @@ -47,10 +46,10 @@ func (a *App) Run(ctx context.Context) error { _ = rpcClient.Close() }() - db, err := sql.Open("sqlite3", "./client.db") + db, err := sqlite.New("./client.db").Connect() if err != nil { a.log.Error("failed to open db", zap.Error(err)) - return fmt.Errorf("%s%w", op, err) + return fmt.Errorf("%s: %w", op, err) } _ = db @@ -69,8 +68,7 @@ func (a *App) Run(ctx context.Context) error { f, err := tea.LogToFile("debug.log", "debug") if err != nil { - fmt.Println("fatal:", err) - os.Exit(1) + panic(err) } defer func() { _ = f.Close() diff --git a/internal/app/server/app.go b/internal/app/server/app.go index 3af0703..1d53a54 100644 --- a/internal/app/server/app.go +++ b/internal/app/server/app.go @@ -9,8 +9,8 @@ import ( "github.com/bjlag/go-keeper/internal/infrastructure/auth" "github.com/bjlag/go-keeper/internal/infrastructure/db/pg" "github.com/bjlag/go-keeper/internal/infrastructure/rpc/server" - "github.com/bjlag/go-keeper/internal/infrastructure/store/data" - "github.com/bjlag/go-keeper/internal/infrastructure/store/user" + "github.com/bjlag/go-keeper/internal/infrastructure/store/server/data" + "github.com/bjlag/go-keeper/internal/infrastructure/store/server/user" rpcGetAllData "github.com/bjlag/go-keeper/internal/rpc/get_all_data" rpcLogin "github.com/bjlag/go-keeper/internal/rpc/login" rpcRefreshTokens "github.com/bjlag/go-keeper/internal/rpc/refresh_tokens" diff --git a/internal/infrastructure/db/sqlite/sqlite.go b/internal/infrastructure/db/sqlite/sqlite.go new file mode 100644 index 0000000..2d7b142 --- /dev/null +++ b/internal/infrastructure/db/sqlite/sqlite.go @@ -0,0 +1,30 @@ +package sqlite + +import ( + "fmt" + + "github.com/jmoiron/sqlx" + _ "github.com/mattn/go-sqlite3" +) + +type SQLite struct { + dsn string +} + +func New(dsn string) *SQLite { + return &SQLite{ + dsn: dsn, + } +} + +func (l SQLite) Connect() (*sqlx.DB, error) { + const op = "sqlite.Connect" + + db, err := sqlx.Connect("sqlite3", l.dsn) + if err != nil { + + return nil, fmt.Errorf("%s: %w", op, err) + } + + return db, nil +} diff --git a/internal/infrastructure/store/data/model.go b/internal/infrastructure/store/server/data/model.go similarity index 100% rename from internal/infrastructure/store/data/model.go rename to internal/infrastructure/store/server/data/model.go diff --git a/internal/infrastructure/store/data/store.go b/internal/infrastructure/store/server/data/store.go similarity index 100% rename from internal/infrastructure/store/data/store.go rename to internal/infrastructure/store/server/data/store.go diff --git a/internal/infrastructure/store/user/model.go b/internal/infrastructure/store/server/user/model.go similarity index 100% rename from internal/infrastructure/store/user/model.go rename to internal/infrastructure/store/server/user/model.go diff --git a/internal/infrastructure/store/user/store.go b/internal/infrastructure/store/server/user/store.go similarity index 100% rename from internal/infrastructure/store/user/store.go rename to internal/infrastructure/store/server/user/store.go diff --git a/internal/usecase/server/data/get_all/usecase.go b/internal/usecase/server/data/get_all/usecase.go index 46df6f7..4db4d89 100644 --- a/internal/usecase/server/data/get_all/usecase.go +++ b/internal/usecase/server/data/get_all/usecase.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - storeUser "github.com/bjlag/go-keeper/internal/infrastructure/store/user" + storeUser "github.com/bjlag/go-keeper/internal/infrastructure/store/server/user" ) var ErrNoData = errors.New("no data") diff --git a/internal/usecase/server/user/login/usecase.go b/internal/usecase/server/user/login/usecase.go index 2295924..0979145 100644 --- a/internal/usecase/server/user/login/usecase.go +++ b/internal/usecase/server/user/login/usecase.go @@ -7,7 +7,7 @@ import ( "golang.org/x/crypto/bcrypt" - storeUser "github.com/bjlag/go-keeper/internal/infrastructure/store/user" + storeUser "github.com/bjlag/go-keeper/internal/infrastructure/store/server/user" ) var ( diff --git a/internal/usecase/server/user/refresh_tokens/usecase.go b/internal/usecase/server/user/refresh_tokens/usecase.go index 77859f6..8849ef8 100644 --- a/internal/usecase/server/user/refresh_tokens/usecase.go +++ b/internal/usecase/server/user/refresh_tokens/usecase.go @@ -6,7 +6,7 @@ import ( "fmt" "github.com/bjlag/go-keeper/internal/infrastructure/auth" - storeUser "github.com/bjlag/go-keeper/internal/infrastructure/store/user" + storeUser "github.com/bjlag/go-keeper/internal/infrastructure/store/server/user" ) var ( diff --git a/internal/usecase/server/user/register/usecase.go b/internal/usecase/server/user/register/usecase.go index 4d27274..53b4d21 100644 --- a/internal/usecase/server/user/register/usecase.go +++ b/internal/usecase/server/user/register/usecase.go @@ -10,7 +10,7 @@ import ( "golang.org/x/crypto/bcrypt" model "github.com/bjlag/go-keeper/internal/domain/user" - storeUser "github.com/bjlag/go-keeper/internal/infrastructure/store/user" + storeUser "github.com/bjlag/go-keeper/internal/infrastructure/store/server/user" ) var ErrUserAlreadyExists = errors.New("user already exists") From a3ae87610f30ae019f3fb02b6317d3571d73d129 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Tue, 4 Mar 2025 23:32:53 +0300 Subject: [PATCH 33/94] server: refactoring rpc method --- internal/app/server/app.go | 8 +- internal/cli/model/list/model.go | 4 +- internal/domain/data/data.go | 2 +- internal/generated/rpc/keeper.pb.go | 121 +++++++++--------- internal/generated/rpc/keeper_grpc.pb.go | 30 ++--- .../infrastructure/rpc/client/get_all_data.go | 14 +- internal/infrastructure/rpc/server/method.go | 10 +- .../store/server/{data => item}/model.go | 10 +- .../store/server/{data => item}/store.go | 8 +- .../contract.go | 2 +- .../handler.go | 8 +- .../usecase/server/data/get_all/contract.go | 2 +- .../usecase/server/data/get_all/usecase.go | 2 +- .../client/001_create_data_table.up.sql | 2 +- .../server/002_create_data_table.up.sql | 16 +-- proto/keeper.proto | 6 +- 16 files changed, 123 insertions(+), 122 deletions(-) rename internal/infrastructure/store/server/{data => item}/model.go (77%) rename internal/infrastructure/store/server/{data => item}/store.go (84%) rename internal/rpc/{get_all_data => get_all_items}/contract.go (89%) rename internal/rpc/{get_all_data => get_all_items}/handler.go (88%) diff --git a/internal/app/server/app.go b/internal/app/server/app.go index 1d53a54..9c6d6f8 100644 --- a/internal/app/server/app.go +++ b/internal/app/server/app.go @@ -9,9 +9,9 @@ import ( "github.com/bjlag/go-keeper/internal/infrastructure/auth" "github.com/bjlag/go-keeper/internal/infrastructure/db/pg" "github.com/bjlag/go-keeper/internal/infrastructure/rpc/server" - "github.com/bjlag/go-keeper/internal/infrastructure/store/server/data" + "github.com/bjlag/go-keeper/internal/infrastructure/store/server/item" "github.com/bjlag/go-keeper/internal/infrastructure/store/server/user" - rpcGetAllData "github.com/bjlag/go-keeper/internal/rpc/get_all_data" + rpcGetAllItems "github.com/bjlag/go-keeper/internal/rpc/get_all_items" rpcLogin "github.com/bjlag/go-keeper/internal/rpc/login" rpcRefreshTokens "github.com/bjlag/go-keeper/internal/rpc/refresh_tokens" rpcRegister "github.com/bjlag/go-keeper/internal/rpc/register" @@ -47,7 +47,7 @@ func (a *App) Run(ctx context.Context) error { }() userStore := user.NewStore(db) - dataStore := data.NewStore(db) + dataStore := item.NewStore(db) jwt := auth.NewJWT(a.cfg.Auth.SecretKey, a.cfg.Auth.AccessTokenExp, a.cfg.Auth.RefreshTokenExp) ucRegister := register.NewUsecase(userStore, jwt) @@ -63,7 +63,7 @@ func (a *App) Run(ctx context.Context) error { server.WithHandler(server.RegisterMethod, rpcRegister.New(ucRegister).Handle), server.WithHandler(server.LoginMethod, rpcLogin.New(ucLogin).Handle), server.WithHandler(server.RefreshTokensMethod, rpcRefreshTokens.New(ucRefreshTokens).Handle), - server.WithHandler(server.GetAllDataMethod, rpcGetAllData.New(ucGetAllData).Handle), + server.WithHandler(server.GetAllItemsMethod, rpcGetAllItems.New(ucGetAllData).Handle), ) err = s.Start(ctx) diff --git a/internal/cli/model/list/model.go b/internal/cli/model/list/model.go index a7dec0f..9270861 100644 --- a/internal/cli/model/list/model.go +++ b/internal/cli/model/list/model.go @@ -81,11 +81,11 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case GetAllDataMessage: f.state = stateCategoryList - in := &client.GetAllDataIn{ + in := &client.GetAllItemsIn{ Limit: 10, Offset: 0, } - out, err := f.rpcClient.GetAllData(context.TODO(), in) + out, err := f.rpcClient.GetAllItems(context.TODO(), in) if err != nil { if s, ok := status.FromError(err); ok { if s.Code() == codes.PermissionDenied { diff --git a/internal/domain/data/data.go b/internal/domain/data/data.go index 31320a1..a8dd3bf 100644 --- a/internal/domain/data/data.go +++ b/internal/domain/data/data.go @@ -15,7 +15,7 @@ const ( CategoryBankCard ) -type Data struct { +type Item struct { GUID uuid.UUID UserGUID uuid.UUID EncryptedData []byte diff --git a/internal/generated/rpc/keeper.pb.go b/internal/generated/rpc/keeper.pb.go index 2df6e86..2481325 100644 --- a/internal/generated/rpc/keeper.pb.go +++ b/internal/generated/rpc/keeper.pb.go @@ -328,7 +328,7 @@ func (x *RefreshTokensOut) GetRefreshToken() string { } // data -type GetAllDataIn struct { +type GetAllItemsIn struct { state protoimpl.MessageState `protogen:"open.v1"` Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` @@ -336,20 +336,20 @@ type GetAllDataIn struct { sizeCache protoimpl.SizeCache } -func (x *GetAllDataIn) Reset() { - *x = GetAllDataIn{} +func (x *GetAllItemsIn) Reset() { + *x = GetAllItemsIn{} mi := &file_proto_keeper_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GetAllDataIn) String() string { +func (x *GetAllItemsIn) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetAllDataIn) ProtoMessage() {} +func (*GetAllItemsIn) ProtoMessage() {} -func (x *GetAllDataIn) ProtoReflect() protoreflect.Message { +func (x *GetAllItemsIn) ProtoReflect() protoreflect.Message { mi := &file_proto_keeper_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -361,46 +361,46 @@ func (x *GetAllDataIn) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetAllDataIn.ProtoReflect.Descriptor instead. -func (*GetAllDataIn) Descriptor() ([]byte, []int) { +// Deprecated: Use GetAllItemsIn.ProtoReflect.Descriptor instead. +func (*GetAllItemsIn) Descriptor() ([]byte, []int) { return file_proto_keeper_proto_rawDescGZIP(), []int{6} } -func (x *GetAllDataIn) GetLimit() uint32 { +func (x *GetAllItemsIn) GetLimit() uint32 { if x != nil { return x.Limit } return 0 } -func (x *GetAllDataIn) GetOffset() uint32 { +func (x *GetAllItemsIn) GetOffset() uint32 { if x != nil { return x.Offset } return 0 } -type GetAllDataOut struct { +type GetAllItemsOut struct { state protoimpl.MessageState `protogen:"open.v1"` Items []*Item `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *GetAllDataOut) Reset() { - *x = GetAllDataOut{} +func (x *GetAllItemsOut) Reset() { + *x = GetAllItemsOut{} mi := &file_proto_keeper_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GetAllDataOut) String() string { +func (x *GetAllItemsOut) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetAllDataOut) ProtoMessage() {} +func (*GetAllItemsOut) ProtoMessage() {} -func (x *GetAllDataOut) ProtoReflect() protoreflect.Message { +func (x *GetAllItemsOut) ProtoReflect() protoreflect.Message { mi := &file_proto_keeper_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -412,12 +412,12 @@ func (x *GetAllDataOut) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetAllDataOut.ProtoReflect.Descriptor instead. -func (*GetAllDataOut) Descriptor() ([]byte, []int) { +// Deprecated: Use GetAllItemsOut.ProtoReflect.Descriptor instead. +func (*GetAllItemsOut) Descriptor() ([]byte, []int) { return file_proto_keeper_proto_rawDescGZIP(), []int{7} } -func (x *GetAllDataOut) GetItems() []*Item { +func (x *GetAllItemsOut) GetItems() []*Item { if x != nil { return x.Items } @@ -526,42 +526,43 @@ var file_proto_keeper_proto_rawDesc = string([]byte{ 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, - 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x3c, 0x0a, 0x0c, 0x47, 0x65, 0x74, - 0x41, 0x6c, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x49, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, - 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, - 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x22, 0x33, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x41, 0x6c, - 0x6c, 0x44, 0x61, 0x74, 0x61, 0x4f, 0x75, 0x74, 0x12, 0x22, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, - 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0xb4, 0x01, 0x0a, - 0x04, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x65, 0x6e, 0x63, - 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x12, - 0x38, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x03, 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, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x64, 0x41, 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, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x64, 0x41, 0x74, 0x32, 0xe8, 0x01, 0x0a, 0x06, 0x4b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x12, 0x33, - 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x12, 0x2e, 0x6b, 0x65, 0x65, - 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x1a, 0x13, - 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, - 0x4f, 0x75, 0x74, 0x12, 0x2a, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x0f, 0x2e, 0x6b, - 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x1a, 0x10, 0x2e, - 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x4f, 0x75, 0x74, 0x12, - 0x42, 0x0a, 0x0d, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, - 0x12, 0x17, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, - 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x49, 0x6e, 0x1a, 0x18, 0x2e, 0x6b, 0x65, 0x65, 0x70, - 0x65, 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, - 0x4f, 0x75, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x44, 0x61, 0x74, - 0x61, 0x12, 0x14, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, - 0x6c, 0x44, 0x61, 0x74, 0x61, 0x49, 0x6e, 0x1a, 0x15, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, - 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x44, 0x61, 0x74, 0x61, 0x4f, 0x75, 0x74, 0x42, 0x18, - 0x5a, 0x16, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, - 0x61, 0x74, 0x65, 0x64, 0x2f, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x3d, 0x0a, 0x0d, 0x47, 0x65, 0x74, + 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x49, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, + 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x22, 0x34, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x41, + 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x4f, 0x75, 0x74, 0x12, 0x22, 0x0a, 0x05, 0x69, 0x74, + 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6b, 0x65, 0x65, 0x70, + 0x65, 0x72, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0xb4, + 0x01, 0x0a, 0x04, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x65, + 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, + 0x61, 0x12, 0x38, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x03, + 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, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 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, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x64, 0x41, 0x74, 0x32, 0xeb, 0x01, 0x0a, 0x06, 0x4b, 0x65, 0x65, 0x70, 0x65, 0x72, + 0x12, 0x33, 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x12, 0x2e, 0x6b, + 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, + 0x1a, 0x13, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x65, 0x72, 0x4f, 0x75, 0x74, 0x12, 0x2a, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x0f, + 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x1a, + 0x10, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x4f, 0x75, + 0x74, 0x12, 0x42, 0x0a, 0x0d, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x73, 0x12, 0x17, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, + 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x49, 0x6e, 0x1a, 0x18, 0x2e, 0x6b, 0x65, + 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x73, 0x4f, 0x75, 0x74, 0x12, 0x3c, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, + 0x74, 0x65, 0x6d, 0x73, 0x12, 0x15, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x47, 0x65, + 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x49, 0x6e, 0x1a, 0x16, 0x2e, 0x6b, 0x65, + 0x65, 0x70, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, + 0x4f, 0x75, 0x74, 0x42, 0x18, 0x5a, 0x16, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, + 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -584,23 +585,23 @@ var file_proto_keeper_proto_goTypes = []any{ (*LoginOut)(nil), // 3: keeper.LoginOut (*RefreshTokensIn)(nil), // 4: keeper.RefreshTokensIn (*RefreshTokensOut)(nil), // 5: keeper.RefreshTokensOut - (*GetAllDataIn)(nil), // 6: keeper.GetAllDataIn - (*GetAllDataOut)(nil), // 7: keeper.GetAllDataOut + (*GetAllItemsIn)(nil), // 6: keeper.GetAllItemsIn + (*GetAllItemsOut)(nil), // 7: keeper.GetAllItemsOut (*Item)(nil), // 8: keeper.Item (*timestamppb.Timestamp)(nil), // 9: google.protobuf.Timestamp } var file_proto_keeper_proto_depIdxs = []int32{ - 8, // 0: keeper.GetAllDataOut.items:type_name -> keeper.Item + 8, // 0: keeper.GetAllItemsOut.items:type_name -> keeper.Item 9, // 1: keeper.Item.CreatedAt:type_name -> google.protobuf.Timestamp 9, // 2: keeper.Item.UpdatedAt:type_name -> google.protobuf.Timestamp 0, // 3: keeper.Keeper.Register:input_type -> keeper.RegisterIn 2, // 4: keeper.Keeper.Login:input_type -> keeper.LoginIn 4, // 5: keeper.Keeper.RefreshTokens:input_type -> keeper.RefreshTokensIn - 6, // 6: keeper.Keeper.GetAllData:input_type -> keeper.GetAllDataIn + 6, // 6: keeper.Keeper.GetAllItems:input_type -> keeper.GetAllItemsIn 1, // 7: keeper.Keeper.Register:output_type -> keeper.RegisterOut 3, // 8: keeper.Keeper.Login:output_type -> keeper.LoginOut 5, // 9: keeper.Keeper.RefreshTokens:output_type -> keeper.RefreshTokensOut - 7, // 10: keeper.Keeper.GetAllData:output_type -> keeper.GetAllDataOut + 7, // 10: keeper.Keeper.GetAllItems:output_type -> keeper.GetAllItemsOut 7, // [7:11] is the sub-list for method output_type 3, // [3:7] is the sub-list for method input_type 3, // [3:3] is the sub-list for extension type_name diff --git a/internal/generated/rpc/keeper_grpc.pb.go b/internal/generated/rpc/keeper_grpc.pb.go index b689bde..7b284f9 100644 --- a/internal/generated/rpc/keeper_grpc.pb.go +++ b/internal/generated/rpc/keeper_grpc.pb.go @@ -22,7 +22,7 @@ const ( Keeper_Register_FullMethodName = "/keeper.Keeper/Register" Keeper_Login_FullMethodName = "/keeper.Keeper/Login" Keeper_RefreshTokens_FullMethodName = "/keeper.Keeper/RefreshTokens" - Keeper_GetAllData_FullMethodName = "/keeper.Keeper/GetAllData" + Keeper_GetAllItems_FullMethodName = "/keeper.Keeper/GetAllItems" ) // KeeperClient is the client API for Keeper service. @@ -34,7 +34,7 @@ type KeeperClient interface { Login(ctx context.Context, in *LoginIn, opts ...grpc.CallOption) (*LoginOut, error) RefreshTokens(ctx context.Context, in *RefreshTokensIn, opts ...grpc.CallOption) (*RefreshTokensOut, error) // data - GetAllData(ctx context.Context, in *GetAllDataIn, opts ...grpc.CallOption) (*GetAllDataOut, error) + GetAllItems(ctx context.Context, in *GetAllItemsIn, opts ...grpc.CallOption) (*GetAllItemsOut, error) } type keeperClient struct { @@ -75,10 +75,10 @@ func (c *keeperClient) RefreshTokens(ctx context.Context, in *RefreshTokensIn, o return out, nil } -func (c *keeperClient) GetAllData(ctx context.Context, in *GetAllDataIn, opts ...grpc.CallOption) (*GetAllDataOut, error) { +func (c *keeperClient) GetAllItems(ctx context.Context, in *GetAllItemsIn, opts ...grpc.CallOption) (*GetAllItemsOut, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(GetAllDataOut) - err := c.cc.Invoke(ctx, Keeper_GetAllData_FullMethodName, in, out, cOpts...) + out := new(GetAllItemsOut) + err := c.cc.Invoke(ctx, Keeper_GetAllItems_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -94,7 +94,7 @@ type KeeperServer interface { Login(context.Context, *LoginIn) (*LoginOut, error) RefreshTokens(context.Context, *RefreshTokensIn) (*RefreshTokensOut, error) // data - GetAllData(context.Context, *GetAllDataIn) (*GetAllDataOut, error) + GetAllItems(context.Context, *GetAllItemsIn) (*GetAllItemsOut, error) } // UnimplementedKeeperServer should be embedded to have @@ -113,8 +113,8 @@ func (UnimplementedKeeperServer) Login(context.Context, *LoginIn) (*LoginOut, er func (UnimplementedKeeperServer) RefreshTokens(context.Context, *RefreshTokensIn) (*RefreshTokensOut, error) { return nil, status.Errorf(codes.Unimplemented, "method RefreshTokens not implemented") } -func (UnimplementedKeeperServer) GetAllData(context.Context, *GetAllDataIn) (*GetAllDataOut, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetAllData not implemented") +func (UnimplementedKeeperServer) GetAllItems(context.Context, *GetAllItemsIn) (*GetAllItemsOut, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetAllItems not implemented") } func (UnimplementedKeeperServer) testEmbeddedByValue() {} @@ -190,20 +190,20 @@ func _Keeper_RefreshTokens_Handler(srv interface{}, ctx context.Context, dec fun return interceptor(ctx, in, info, handler) } -func _Keeper_GetAllData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetAllDataIn) +func _Keeper_GetAllItems_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetAllItemsIn) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(KeeperServer).GetAllData(ctx, in) + return srv.(KeeperServer).GetAllItems(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: Keeper_GetAllData_FullMethodName, + FullMethod: Keeper_GetAllItems_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(KeeperServer).GetAllData(ctx, req.(*GetAllDataIn)) + return srv.(KeeperServer).GetAllItems(ctx, req.(*GetAllItemsIn)) } return interceptor(ctx, in, info, handler) } @@ -228,8 +228,8 @@ var Keeper_ServiceDesc = grpc.ServiceDesc{ Handler: _Keeper_RefreshTokens_Handler, }, { - MethodName: "GetAllData", - Handler: _Keeper_GetAllData_Handler, + MethodName: "GetAllItems", + Handler: _Keeper_GetAllItems_Handler, }, }, Streams: []grpc.StreamDesc{}, diff --git a/internal/infrastructure/rpc/client/get_all_data.go b/internal/infrastructure/rpc/client/get_all_data.go index bff219d..d47b8c8 100644 --- a/internal/infrastructure/rpc/client/get_all_data.go +++ b/internal/infrastructure/rpc/client/get_all_data.go @@ -8,12 +8,12 @@ import ( "github.com/bjlag/go-keeper/internal/generated/rpc" ) -type GetAllDataIn struct { +type GetAllItemsIn struct { Limit uint32 Offset uint32 } -type GetAllDataOut struct { +type GetAllItemsOut struct { Items []GetAllDataItem } @@ -24,15 +24,15 @@ type GetAllDataItem struct { UpdatedAt time.Time } -func (c RPCClient) GetAllData(ctx context.Context, in *GetAllDataIn) (*GetAllDataOut, error) { - const op = "client.rpc.GetAllData" +func (c RPCClient) GetAllItems(ctx context.Context, in *GetAllItemsIn) (*GetAllItemsOut, error) { + const op = "client.rpc.GetAllItems" - rpcIn := &rpc.GetAllDataIn{ + rpcIn := &rpc.GetAllItemsIn{ Limit: in.Limit, Offset: in.Offset, } - out, err := c.client.GetAllData(ctx, rpcIn) + out, err := c.client.GetAllItems(ctx, rpcIn) if err != nil { return nil, fmt.Errorf("%s: %w", op, err) } @@ -51,7 +51,7 @@ func (c RPCClient) GetAllData(ctx context.Context, in *GetAllDataIn) (*GetAllDat } } - return &GetAllDataOut{ + return &GetAllItemsOut{ Items: items, }, nil } diff --git a/internal/infrastructure/rpc/server/method.go b/internal/infrastructure/rpc/server/method.go index 2ff626f..444b069 100644 --- a/internal/infrastructure/rpc/server/method.go +++ b/internal/infrastructure/rpc/server/method.go @@ -13,7 +13,7 @@ const ( RegisterMethod = "Register" LoginMethod = "Login" RefreshTokensMethod = "RefreshTokens" - GetAllDataMethod = "GetAllData" + GetAllItemsMethod = "GetAllItems" ) func (s RPCServer) Register(ctx context.Context, in *pb.RegisterIn) (*pb.RegisterOut, error) { @@ -58,15 +58,15 @@ func (s RPCServer) RefreshTokens(ctx context.Context, in *pb.RefreshTokensIn) (* return h(ctx, in) } -func (s RPCServer) GetAllData(ctx context.Context, in *pb.GetAllDataIn) (*pb.GetAllDataOut, error) { - handler, err := s.getHandler(GetAllDataMethod) +func (s RPCServer) GetAllItems(ctx context.Context, in *pb.GetAllItemsIn) (*pb.GetAllItemsOut, error) { + handler, err := s.getHandler(GetAllItemsMethod) if err != nil { return nil, err } - h, ok := handler.(func(context.Context, *pb.GetAllDataIn) (*pb.GetAllDataOut, error)) + h, ok := handler.(func(context.Context, *pb.GetAllItemsIn) (*pb.GetAllItemsOut, error)) if !ok { - return nil, status.Errorf(codes.Internal, "handler for %s method not found", GetAllDataMethod) + return nil, status.Errorf(codes.Internal, "handler for %s method not found", GetAllItemsMethod) } return h(ctx, in) diff --git a/internal/infrastructure/store/server/data/model.go b/internal/infrastructure/store/server/item/model.go similarity index 77% rename from internal/infrastructure/store/server/data/model.go rename to internal/infrastructure/store/server/item/model.go index bfa15d4..94d88ac 100644 --- a/internal/infrastructure/store/server/data/model.go +++ b/internal/infrastructure/store/server/item/model.go @@ -1,4 +1,4 @@ -package data +package item import ( "time" @@ -16,8 +16,8 @@ type Row struct { UpdatedAt time.Time `db:"updated_at"` } -func (r *Row) convertToModel() data.Data { - return data.Data{ +func (r *Row) convertToModel() data.Item { + return data.Item{ GUID: r.GUID, UserGUID: r.UserGUID, EncryptedData: r.EncryptedData, @@ -26,8 +26,8 @@ func (r *Row) convertToModel() data.Data { } } -func convertToModels(rows []Row) []data.Data { - result := make([]data.Data, 0, len(rows)) +func convertToModels(rows []Row) []data.Item { + result := make([]data.Item, 0, len(rows)) for _, row := range rows { result = append(result, row.convertToModel()) } diff --git a/internal/infrastructure/store/server/data/store.go b/internal/infrastructure/store/server/item/store.go similarity index 84% rename from internal/infrastructure/store/server/data/store.go rename to internal/infrastructure/store/server/item/store.go index 2efc3a5..698c332 100644 --- a/internal/infrastructure/store/server/data/store.go +++ b/internal/infrastructure/store/server/item/store.go @@ -1,4 +1,4 @@ -package data +package item import ( "context" @@ -20,12 +20,12 @@ func NewStore(db *sqlx.DB) *Store { } } -func (s *Store) GetAllByUser(ctx context.Context, userGUID uuid.UUID, limit, offset uint32) ([]model.Data, error) { - const op = "store.data.GetAllByUser" +func (s *Store) GetAllByUser(ctx context.Context, userGUID uuid.UUID, limit, offset uint32) ([]model.Item, error) { + const op = "store.item.GetAllByUser" query := ` SELECT guid, user_guid, encrypted_data, created_at, updated_at - FROM data + FROM items WHERE user_guid = $1 LIMIT $2 OFFSET $3 diff --git a/internal/rpc/get_all_data/contract.go b/internal/rpc/get_all_items/contract.go similarity index 89% rename from internal/rpc/get_all_data/contract.go rename to internal/rpc/get_all_items/contract.go index 3001a32..d6a863c 100644 --- a/internal/rpc/get_all_data/contract.go +++ b/internal/rpc/get_all_items/contract.go @@ -1,4 +1,4 @@ -package get_all_data +package get_all_items import ( "context" diff --git a/internal/rpc/get_all_data/handler.go b/internal/rpc/get_all_items/handler.go similarity index 88% rename from internal/rpc/get_all_data/handler.go rename to internal/rpc/get_all_items/handler.go index dfb6936..cc8c6b6 100644 --- a/internal/rpc/get_all_data/handler.go +++ b/internal/rpc/get_all_items/handler.go @@ -1,4 +1,4 @@ -package get_all_data +package get_all_items import ( "context" @@ -31,7 +31,7 @@ func New(usecase usecase) *Handler { } } -func (h *Handler) Handle(ctx context.Context, in *pb.GetAllDataIn) (*pb.GetAllDataOut, error) { +func (h *Handler) Handle(ctx context.Context, in *pb.GetAllItemsIn) (*pb.GetAllItemsOut, error) { log := logger.FromCtx(ctx) userGUID := auth.UserGUIDFromCtx(ctx) @@ -58,7 +58,7 @@ func (h *Handler) Handle(ctx context.Context, in *pb.GetAllDataIn) (*pb.GetAllDa }) if err != nil { if !errors.Is(err, get_all.ErrNoData) { - log.Error("Failed to get all data", zap.Error(err)) + log.Error("Failed to get all item", zap.Error(err)) return nil, status.Error(codes.Internal, "internal error") } } @@ -73,7 +73,7 @@ func (h *Handler) Handle(ctx context.Context, in *pb.GetAllDataIn) (*pb.GetAllDa }) } - return &pb.GetAllDataOut{ + return &pb.GetAllItemsOut{ Items: itemsOut, }, nil } diff --git a/internal/usecase/server/data/get_all/contract.go b/internal/usecase/server/data/get_all/contract.go index 9763b56..e2b711f 100644 --- a/internal/usecase/server/data/get_all/contract.go +++ b/internal/usecase/server/data/get_all/contract.go @@ -9,5 +9,5 @@ import ( ) type dataStore interface { - GetAllByUser(ctx context.Context, userGUID uuid.UUID, limit, offset uint32) ([]model.Data, error) + GetAllByUser(ctx context.Context, userGUID uuid.UUID, limit, offset uint32) ([]model.Item, error) } diff --git a/internal/usecase/server/data/get_all/usecase.go b/internal/usecase/server/data/get_all/usecase.go index 4db4d89..fe48491 100644 --- a/internal/usecase/server/data/get_all/usecase.go +++ b/internal/usecase/server/data/get_all/usecase.go @@ -21,7 +21,7 @@ func NewUsecase(dataStore dataStore) *Usecase { } func (u Usecase) Do(ctx context.Context, data Data) (*Result, error) { - const op = "usecase.data.getAll.Do" + const op = "usecase.item.getAll.Do" rows, err := u.dataStore.GetAllByUser(ctx, data.UserGUID, data.Limit, data.Offset) if err != nil { diff --git a/migrations/client/001_create_data_table.up.sql b/migrations/client/001_create_data_table.up.sql index cb20491..8da0304 100644 --- a/migrations/client/001_create_data_table.up.sql +++ b/migrations/client/001_create_data_table.up.sql @@ -1,4 +1,4 @@ -CREATE TABLE IF NOT EXISTS data ( +CREATE TABLE IF NOT EXISTS items ( guid text PRIMARY KEY, encrypted_data text NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, diff --git a/migrations/server/002_create_data_table.up.sql b/migrations/server/002_create_data_table.up.sql index c7da468..7cb4792 100644 --- a/migrations/server/002_create_data_table.up.sql +++ b/migrations/server/002_create_data_table.up.sql @@ -1,4 +1,4 @@ -CREATE TABLE IF NOT EXISTS data ( +CREATE TABLE IF NOT EXISTS items ( guid uuid PRIMARY KEY NOT NULL, user_guid uuid NOT NULL REFERENCES users (guid) ON DELETE RESTRICT, encrypted_data text NOT NULL, @@ -6,11 +6,11 @@ CREATE TABLE IF NOT EXISTS data ( updated_at timestamptz NOT NULL DEFAULT NOW() ); -CREATE INDEX data_user_guid_idx ON data (user_guid); +CREATE INDEX items_user_guid_idx ON items (user_guid); -COMMENT ON TABLE data IS 'Данные (пароли, логины, тексты и пр.)'; -COMMENT ON COLUMN data.guid IS 'GUID'; -COMMENT ON COLUMN data.user_guid IS 'Владелец'; -COMMENT ON COLUMN data.encrypted_data IS 'Сами данные в зашифрованном виде'; -COMMENT ON COLUMN data.created_at IS 'Дата создания записи'; -COMMENT ON COLUMN data.updated_at IS 'Дата изменения записи'; \ No newline at end of file +COMMENT ON TABLE items IS 'Данные (пароли, логины, тексты и пр.)'; +COMMENT ON COLUMN items.guid IS 'GUID'; +COMMENT ON COLUMN items.user_guid IS 'Владелец'; +COMMENT ON COLUMN items.encrypted_data IS 'Сами данные в зашифрованном виде'; +COMMENT ON COLUMN items.created_at IS 'Дата создания записи'; +COMMENT ON COLUMN items.updated_at IS 'Дата изменения записи'; \ No newline at end of file diff --git a/proto/keeper.proto b/proto/keeper.proto index 8aff47c..7a5aff1 100644 --- a/proto/keeper.proto +++ b/proto/keeper.proto @@ -13,7 +13,7 @@ service Keeper { rpc RefreshTokens(RefreshTokensIn) returns (RefreshTokensOut); // data - rpc GetAllData(GetAllDataIn) returns (GetAllDataOut); + rpc GetAllItems(GetAllItemsIn) returns (GetAllItemsOut); } // auth @@ -47,12 +47,12 @@ message RefreshTokensOut { } // data -message GetAllDataIn { +message GetAllItemsIn { uint32 limit = 1; uint32 offset = 2; } -message GetAllDataOut { +message GetAllItemsOut { repeated Item items = 1; } From 6cd058eb9cf414f3ca0d230425551dd92e965f88 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Wed, 5 Mar 2025 00:49:48 +0300 Subject: [PATCH 34/94] client: get/save items --- internal/app/client/app.go | 10 ++- internal/cli/model/list/model.go | 42 +++--------- internal/domain/client/item.go | 23 +++++++ internal/domain/data/{data.go => item.go} | 0 .../{get_all_data.go => get_all_items.go} | 10 ++- .../infrastructure/store/client/item/store.go | 56 +++++++++++++++ internal/usecase/client/sync/contract.go | 16 +++++ internal/usecase/client/sync/usecase.go | 68 +++++++++++++++++++ .../client/001_create_data_table.up.sql | 8 +-- 9 files changed, 191 insertions(+), 42 deletions(-) create mode 100644 internal/domain/client/item.go rename internal/domain/data/{data.go => item.go} (100%) rename internal/infrastructure/rpc/client/{get_all_data.go => get_all_items.go} (84%) create mode 100644 internal/infrastructure/store/client/item/store.go create mode 100644 internal/usecase/client/sync/contract.go create mode 100644 internal/usecase/client/sync/usecase.go diff --git a/internal/app/client/app.go b/internal/app/client/app.go index 4acbb94..8a85495 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -5,7 +5,6 @@ import ( "fmt" tea "github.com/charmbracelet/bubbletea" - _ "github.com/mattn/go-sqlite3" "go.uber.org/zap" "github.com/bjlag/go-keeper/internal/cli/model/item" @@ -15,9 +14,11 @@ import ( formRegister "github.com/bjlag/go-keeper/internal/cli/model/register" "github.com/bjlag/go-keeper/internal/infrastructure/db/sqlite" rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" + sItem "github.com/bjlag/go-keeper/internal/infrastructure/store/client/item" "github.com/bjlag/go-keeper/internal/infrastructure/store/client/token" "github.com/bjlag/go-keeper/internal/usecase/client/login" "github.com/bjlag/go-keeper/internal/usecase/client/register" + "github.com/bjlag/go-keeper/internal/usecase/client/sync" ) type App struct { @@ -46,23 +47,26 @@ func (a *App) Run(ctx context.Context) error { _ = rpcClient.Close() }() + // todo базу создавать и подключаться после успешного логин + // todo название файла базы должно быть уникальным под каждую учетку под которой авторизовались db, err := sqlite.New("./client.db").Connect() if err != nil { a.log.Error("failed to open db", zap.Error(err)) return fmt.Errorf("%s: %w", op, err) } - _ = db + storeItem := sItem.NewStore(db) ucLogin := login.NewUsecase(rpcClient) ucRegister := register.NewUsecase(rpcClient) + ucSync := sync.NewUsecase(rpcClient, storeItem) m := master.InitModel( master.WithStoreTokens(storeTokens), master.WithLoginForm(formLogin.InitModel(ucLogin)), master.WithRegisterForm(formRegister.InitModel(ucRegister)), - master.WithListFormForm(list.InitModel(rpcClient)), + master.WithListFormForm(list.InitModel(ucSync)), master.WithShowPasswordForm(item.InitModel()), ) diff --git a/internal/cli/model/list/model.go b/internal/cli/model/list/model.go index 9270861..fd5a342 100644 --- a/internal/cli/model/list/model.go +++ b/internal/cli/model/list/model.go @@ -4,19 +4,15 @@ import ( "context" "strings" - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/list" - tea "github.com/charmbracelet/bubbletea" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/element" "github.com/bjlag/go-keeper/internal/cli/model/item" - "github.com/bjlag/go-keeper/internal/cli/model/login" "github.com/bjlag/go-keeper/internal/cli/style" - "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" + "github.com/bjlag/go-keeper/internal/usecase/client/sync" + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" ) const ( @@ -38,19 +34,17 @@ type Model struct { passwords list.Model err error - rpcClient *client.RPCClient - //usecase *login.Usecase + usecase *sync.Usecase } -func InitModel(rpcClient *client.RPCClient) *Model { +func InitModel(usecase *sync.Usecase) *Model { f := &Model{ help: help.New(), header: "Категории", categories: element.CreateDefaultList("Категории:", defaultWidth, listHeight), passwords: element.CreateDefaultList("Пароли:", defaultWidth, listHeight), - rpcClient: rpcClient, - //usecase: usecase, + usecase: usecase, } return f @@ -80,25 +74,7 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case GetAllDataMessage: f.state = stateCategoryList - - in := &client.GetAllItemsIn{ - Limit: 10, - Offset: 0, - } - out, err := f.rpcClient.GetAllItems(context.TODO(), in) - if err != nil { - if s, ok := status.FromError(err); ok { - if s.Code() == codes.PermissionDenied { - return f.main.Update(login.OpenMessage{}) - } - return f, nil - } - - f.err = err - return f, nil - } - - _ = out + f.err = f.usecase.Do(context.TODO()) return f.Update(OpenCategoryListMessage{}) case OpenCategoryListMessage: diff --git a/internal/domain/client/item.go b/internal/domain/client/item.go new file mode 100644 index 0000000..49f9f72 --- /dev/null +++ b/internal/domain/client/item.go @@ -0,0 +1,23 @@ +package client + +import ( + "time" + + "github.com/google/uuid" +) + +type Category int + +const ( + CategoryLogin Category = iota + CategoryText + CategoryFile + CategoryBankCard +) + +type Item struct { + GUID uuid.UUID + Data []byte + CreatedAt time.Time + UpdatedAt time.Time +} diff --git a/internal/domain/data/data.go b/internal/domain/data/item.go similarity index 100% rename from internal/domain/data/data.go rename to internal/domain/data/item.go diff --git a/internal/infrastructure/rpc/client/get_all_data.go b/internal/infrastructure/rpc/client/get_all_items.go similarity index 84% rename from internal/infrastructure/rpc/client/get_all_data.go rename to internal/infrastructure/rpc/client/get_all_items.go index d47b8c8..31bbea0 100644 --- a/internal/infrastructure/rpc/client/get_all_data.go +++ b/internal/infrastructure/rpc/client/get_all_items.go @@ -3,6 +3,7 @@ package client import ( "context" "fmt" + "github.com/google/uuid" "time" "github.com/bjlag/go-keeper/internal/generated/rpc" @@ -18,7 +19,7 @@ type GetAllItemsOut struct { } type GetAllDataItem struct { - GUID string + GUID uuid.UUID EncryptedData []byte CreatedAt time.Time UpdatedAt time.Time @@ -43,8 +44,13 @@ func (c RPCClient) GetAllItems(ctx context.Context, in *GetAllItemsIn) (*GetAllI items := make([]GetAllDataItem, len(out.GetItems())) for i, item := range out.GetItems() { + guid, err := uuid.Parse(item.GetGuid()) + if err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + items[i] = GetAllDataItem{ - GUID: item.GetGuid(), + GUID: guid, EncryptedData: item.GetEncryptedData(), CreatedAt: item.GetCreatedAt().AsTime(), UpdatedAt: item.GetUpdatedAt().AsTime(), diff --git a/internal/infrastructure/store/client/item/store.go b/internal/infrastructure/store/client/item/store.go new file mode 100644 index 0000000..3514006 --- /dev/null +++ b/internal/infrastructure/store/client/item/store.go @@ -0,0 +1,56 @@ +package item + +import ( + "context" + "fmt" + + "github.com/jmoiron/sqlx" + + model "github.com/bjlag/go-keeper/internal/domain/client" +) + +const prefixOp = "store.item." + +type Store struct { + db *sqlx.DB +} + +func NewStore(db *sqlx.DB) *Store { + return &Store{ + db: db, + } +} + +func (s *Store) SaveItems(ctx context.Context, items []model.Item) error { + const op = prefixOp + "SaveItems" + + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + defer func() { + _ = tx.Rollback() + }() + + for _, i := range items { + query := ` + INSERT INTO items (guid, data, created_at, updated_at) + VALUES ($1, $2, $3, $4) + ON CONFLICT (guid) DO UPDATE SET + data = excluded.data, + updated_at = excluded.updated_at; + ` + + _, err := tx.ExecContext(ctx, query, i.GUID, i.Data, i.CreatedAt, i.UpdatedAt) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + } + + err = tx.Commit() + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + return nil +} diff --git a/internal/usecase/client/sync/contract.go b/internal/usecase/client/sync/contract.go new file mode 100644 index 0000000..6f5fcd5 --- /dev/null +++ b/internal/usecase/client/sync/contract.go @@ -0,0 +1,16 @@ +package sync + +import ( + "context" + + model "github.com/bjlag/go-keeper/internal/domain/client" + rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" +) + +type client interface { + GetAllItems(ctx context.Context, in *rpc.GetAllItemsIn) (*rpc.GetAllItemsOut, error) +} + +type store interface { + SaveItems(ctx context.Context, items []model.Item) error +} diff --git a/internal/usecase/client/sync/usecase.go b/internal/usecase/client/sync/usecase.go new file mode 100644 index 0000000..3649c89 --- /dev/null +++ b/internal/usecase/client/sync/usecase.go @@ -0,0 +1,68 @@ +package sync + +import ( + "context" + "fmt" + + model "github.com/bjlag/go-keeper/internal/domain/client" + rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" +) + +const ( + prefixOp = "usecase.sync." + + limit = 1 +) + +type Usecase struct { + client client + store store +} + +func NewUsecase(client client, store store) *Usecase { + return &Usecase{ + client: client, + store: store, + } +} + +func (u *Usecase) Do(ctx context.Context) error { + const op = prefixOp + "Do" + + var offset uint32 + + for { + in := &rpc.GetAllItemsIn{ + Limit: limit, + Offset: offset, + } + out, err := u.client.GetAllItems(ctx, in) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + if out == nil || len(out.Items) == 0 { + break + } + + items := make([]model.Item, 0, len(out.Items)) + for _, item := range out.Items { + // todo расшифровка + // todo общие данные в отдельных полях + items = append(items, model.Item{ + GUID: item.GUID, + Data: item.EncryptedData, + CreatedAt: item.CreatedAt, + UpdatedAt: item.UpdatedAt, + }) + } + + err = u.store.SaveItems(ctx, items) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + offset += limit + } + + return nil +} diff --git a/migrations/client/001_create_data_table.up.sql b/migrations/client/001_create_data_table.up.sql index 8da0304..a733318 100644 --- a/migrations/client/001_create_data_table.up.sql +++ b/migrations/client/001_create_data_table.up.sql @@ -1,6 +1,6 @@ CREATE TABLE IF NOT EXISTS items ( - guid text PRIMARY KEY, - encrypted_data text NOT NULL, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL + guid text PRIMARY KEY NOT NULL, + data text NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL ); From 20be89af5c52a33ec55eda193b17f2390a37d381 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Wed, 5 Mar 2025 20:03:13 +0300 Subject: [PATCH 35/94] client: save item values --- internal/domain/client/item.go | 13 +++-- .../infrastructure/store/client/item/store.go | 10 ++-- .../infrastructure/store/server/item/store.go | 1 + internal/usecase/client/sync/usecase.go | 48 +++++++++++++++++-- .../client/001_create_data_table.up.sql | 5 +- 5 files changed, 63 insertions(+), 14 deletions(-) diff --git a/internal/domain/client/item.go b/internal/domain/client/item.go index 49f9f72..1817519 100644 --- a/internal/domain/client/item.go +++ b/internal/domain/client/item.go @@ -11,13 +11,16 @@ type Category int const ( CategoryLogin Category = iota CategoryText - CategoryFile + CategoryBlob CategoryBankCard ) type Item struct { - GUID uuid.UUID - Data []byte - CreatedAt time.Time - UpdatedAt time.Time + GUID uuid.UUID + CategoryID Category + Title string + Value *[]byte + Notes string + CreatedAt time.Time + UpdatedAt time.Time } diff --git a/internal/infrastructure/store/client/item/store.go b/internal/infrastructure/store/client/item/store.go index 3514006..645f24c 100644 --- a/internal/infrastructure/store/client/item/store.go +++ b/internal/infrastructure/store/client/item/store.go @@ -34,14 +34,16 @@ func (s *Store) SaveItems(ctx context.Context, items []model.Item) error { for _, i := range items { query := ` - INSERT INTO items (guid, data, created_at, updated_at) - VALUES ($1, $2, $3, $4) + INSERT INTO items (guid, categoryid, title, value, notes, created_at, updated_at) + VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT (guid) DO UPDATE SET - data = excluded.data, + title = excluded.title, + value = excluded.value, + notes = excluded.notes, updated_at = excluded.updated_at; ` - _, err := tx.ExecContext(ctx, query, i.GUID, i.Data, i.CreatedAt, i.UpdatedAt) + _, err := tx.ExecContext(ctx, query, i.GUID, i.CategoryID, i.Title, i.Value, i.Notes, i.CreatedAt, i.UpdatedAt) if err != nil { return fmt.Errorf("%s: %w", op, err) } diff --git a/internal/infrastructure/store/server/item/store.go b/internal/infrastructure/store/server/item/store.go index 698c332..8ca6774 100644 --- a/internal/infrastructure/store/server/item/store.go +++ b/internal/infrastructure/store/server/item/store.go @@ -27,6 +27,7 @@ func (s *Store) GetAllByUser(ctx context.Context, userGUID uuid.UUID, limit, off SELECT guid, user_guid, encrypted_data, created_at, updated_at FROM items WHERE user_guid = $1 + ORDER BY guid LIMIT $2 OFFSET $3 ` diff --git a/internal/usecase/client/sync/usecase.go b/internal/usecase/client/sync/usecase.go index 3649c89..6a050fc 100644 --- a/internal/usecase/client/sync/usecase.go +++ b/internal/usecase/client/sync/usecase.go @@ -2,6 +2,7 @@ package sync import ( "context" + "encoding/json" "fmt" model "github.com/bjlag/go-keeper/internal/domain/client" @@ -14,6 +15,35 @@ const ( limit = 1 ) +type Data struct { + Title string `json:"title"` + CategoryID model.Category `json:"category_id"` + Value *[]byte `json:"value,omitempty"` + Notes string `json:"notes"` +} + +func (d *Data) UnmarshalJSON(data []byte) error { + type Alias Data + + alias := &struct { + *Alias + Value *json.RawMessage `json:"value,omitempty"` + }{ + Alias: (*Alias)(d), + } + + if err := json.Unmarshal(data, alias); err != nil { + return fmt.Errorf("unmarshal data: %w", err) + } + + if alias.Value != nil { + value := []byte(*alias.Value) + d.Value = &value + } + + return nil +} + type Usecase struct { client client store store @@ -48,11 +78,21 @@ func (u *Usecase) Do(ctx context.Context) error { for _, item := range out.Items { // todo расшифровка // todo общие данные в отдельных полях + + var data Data + err = json.Unmarshal(item.EncryptedData, &data) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + items = append(items, model.Item{ - GUID: item.GUID, - Data: item.EncryptedData, - CreatedAt: item.CreatedAt, - UpdatedAt: item.UpdatedAt, + GUID: item.GUID, + CategoryID: data.CategoryID, + Title: data.Title, + Value: data.Value, + Notes: data.Notes, + CreatedAt: item.CreatedAt, + UpdatedAt: item.UpdatedAt, }) } diff --git a/migrations/client/001_create_data_table.up.sql b/migrations/client/001_create_data_table.up.sql index a733318..e34551d 100644 --- a/migrations/client/001_create_data_table.up.sql +++ b/migrations/client/001_create_data_table.up.sql @@ -1,6 +1,9 @@ CREATE TABLE IF NOT EXISTS items ( guid text PRIMARY KEY NOT NULL, - data text NOT NULL, + categoryId INT NOT NULL, + title text NOT NULL, + value text, + notes text NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL ); From d3980a6dc76a12a6bb11f78003844c8686d553ac Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Thu, 6 Mar 2025 00:59:30 +0300 Subject: [PATCH 36/94] client: list items --- .golangci.yml | 4 +- internal/app/client/app.go | 8 +- internal/cli/element/list.go | 48 +++++++++- internal/cli/model/list/message.go | 8 +- internal/cli/model/list/model.go | 91 ++++++++++--------- internal/cli/model/master/model.go | 2 +- internal/domain/client/item.go | 29 +++++- internal/domain/data/item.go | 9 -- .../infrastructure/store/client/item/model.go | 39 ++++++++ .../infrastructure/store/client/item/store.go | 26 +++++- internal/usecase/client/item/contract.go | 11 +++ internal/usecase/client/item/usecase.go | 64 +++++++++++++ internal/usecase/client/sync/contract.go | 2 +- internal/usecase/client/sync/usecase.go | 8 +- .../client/001_create_data_table.up.sql | 2 +- 15 files changed, 279 insertions(+), 72 deletions(-) create mode 100644 internal/infrastructure/store/client/item/model.go create mode 100644 internal/usecase/client/item/contract.go create mode 100644 internal/usecase/client/item/usecase.go diff --git a/.golangci.yml b/.golangci.yml index 933155c..e95ac97 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -20,6 +20,7 @@ linters: - wrapcheck - gocognit - cyclop + - exhaustive linters-settings: revive: @@ -45,5 +46,4 @@ linters-settings: - error - empty - stdlib - - github\.com\/charmbracelet\/bubbletea\.Model -# - github\.com\/charmbracelet\/bubbletea\.Cmd \ No newline at end of file + - github\.com\/charmbracelet\/bubbletea\.Model \ No newline at end of file diff --git a/internal/app/client/app.go b/internal/app/client/app.go index 8a85495..a7a596e 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -7,7 +7,7 @@ import ( tea "github.com/charmbracelet/bubbletea" "go.uber.org/zap" - "github.com/bjlag/go-keeper/internal/cli/model/item" + modelItem "github.com/bjlag/go-keeper/internal/cli/model/item" "github.com/bjlag/go-keeper/internal/cli/model/list" formLogin "github.com/bjlag/go-keeper/internal/cli/model/login" "github.com/bjlag/go-keeper/internal/cli/model/master" @@ -16,6 +16,7 @@ import ( rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" sItem "github.com/bjlag/go-keeper/internal/infrastructure/store/client/item" "github.com/bjlag/go-keeper/internal/infrastructure/store/client/token" + item2 "github.com/bjlag/go-keeper/internal/usecase/client/item" "github.com/bjlag/go-keeper/internal/usecase/client/login" "github.com/bjlag/go-keeper/internal/usecase/client/register" "github.com/bjlag/go-keeper/internal/usecase/client/sync" @@ -60,14 +61,15 @@ func (a *App) Run(ctx context.Context) error { ucLogin := login.NewUsecase(rpcClient) ucRegister := register.NewUsecase(rpcClient) ucSync := sync.NewUsecase(rpcClient, storeItem) + ucItem := item2.NewUsecase(storeItem) m := master.InitModel( master.WithStoreTokens(storeTokens), master.WithLoginForm(formLogin.InitModel(ucLogin)), master.WithRegisterForm(formRegister.InitModel(ucRegister)), - master.WithListFormForm(list.InitModel(ucSync)), - master.WithShowPasswordForm(item.InitModel()), + master.WithListFormForm(list.InitModel(ucSync, ucItem)), + master.WithShowPasswordForm(modelItem.InitModel()), ) f, err := tea.LogToFile("debug.log", "debug") diff --git a/internal/cli/element/list.go b/internal/cli/element/list.go index 8436546..d860724 100644 --- a/internal/cli/element/list.go +++ b/internal/cli/element/list.go @@ -2,21 +2,22 @@ package element import ( "fmt" + "github.com/google/uuid" "io" "strings" "github.com/bjlag/go-keeper/internal/cli/style" + "github.com/bjlag/go-keeper/internal/domain/client" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" ) -func CreateDefaultList(title string, with, height int, items ...list.Item) list.Model { - l := list.New(items, ItemDelegate{}, with, height) +func CreateDefaultList(title string, with, height int, itemDelegate list.ItemDelegate, items ...list.Item) list.Model { + l := list.New(items, itemDelegate, with, height) l.Title = title l.SetShowStatusBar(false) l.SetFilteringEnabled(false) - //l.SetShowTitle(false) l.Styles.Title = style.ListTitleStyle l.Styles.PaginationStyle = style.ListPaginationStyle l.Styles.HelpStyle = style.ListHelpStyle @@ -24,8 +25,47 @@ func CreateDefaultList(title string, with, height int, items ...list.Item) list. return l } +type Category struct { + ID client.Category + Name string +} + +func (i Category) FilterValue() string { return "" } + +type CategoryDelegate struct{} + +func (d CategoryDelegate) Height() int { + return 1 +} + +func (d CategoryDelegate) Spacing() int { + return 0 +} + +func (d CategoryDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { + return nil +} + +func (d CategoryDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { + i, ok := listItem.(Category) + if !ok { + return + } + + str := fmt.Sprintf("%d. %s", index+1, i.Name) + + fn := style.ListItemStyle.Render + if index == m.Index() { + fn = func(s ...string) string { + return style.SelectedListItemStyle.Render("> " + strings.Join(s, " ")) + } + } + + _, _ = fmt.Fprint(w, fn(str)) +} + type Item struct { - ID string + GUID uuid.UUID Name string } diff --git a/internal/cli/model/list/message.go b/internal/cli/model/list/message.go index 096fb97..caed1dc 100644 --- a/internal/cli/model/list/message.go +++ b/internal/cli/model/list/message.go @@ -1,11 +1,13 @@ package list -import "github.com/bjlag/go-keeper/internal/cli/element" +import ( + "github.com/bjlag/go-keeper/internal/domain/client" +) type GetAllDataMessage struct{} type OpenCategoryListMessage struct{} -type OpenPasswordListMessage struct { - Category element.Item +type OpenItemListMessage struct { + Category client.Category } diff --git a/internal/cli/model/list/model.go b/internal/cli/model/list/model.go index fd5a342..dc78388 100644 --- a/internal/cli/model/list/model.go +++ b/internal/cli/model/list/model.go @@ -4,20 +4,23 @@ import ( "context" "strings" - "github.com/bjlag/go-keeper/internal/cli/common" - "github.com/bjlag/go-keeper/internal/cli/element" - "github.com/bjlag/go-keeper/internal/cli/model/item" - "github.com/bjlag/go-keeper/internal/cli/style" - "github.com/bjlag/go-keeper/internal/usecase/client/sync" "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" + + "github.com/bjlag/go-keeper/internal/cli/common" + "github.com/bjlag/go-keeper/internal/cli/element" + itemModel "github.com/bjlag/go-keeper/internal/cli/model/item" + "github.com/bjlag/go-keeper/internal/cli/style" + "github.com/bjlag/go-keeper/internal/domain/client" + "github.com/bjlag/go-keeper/internal/usecase/client/item" + "github.com/bjlag/go-keeper/internal/usecase/client/sync" ) const ( stateCategoryList int = iota - statePasswordList + stateItemList ) const ( @@ -31,20 +34,22 @@ type Model struct { state int header string categories list.Model - passwords list.Model + items list.Model err error - usecase *sync.Usecase + usecaseSync *sync.Usecase + usecaseItem *item.Usecase } -func InitModel(usecase *sync.Usecase) *Model { +func InitModel(usecaseSync *sync.Usecase, usecaseItem *item.Usecase) *Model { f := &Model{ help: help.New(), header: "Категории", - categories: element.CreateDefaultList("Категории:", defaultWidth, listHeight), - passwords: element.CreateDefaultList("Пароли:", defaultWidth, listHeight), + categories: element.CreateDefaultList("Категории:", defaultWidth, listHeight, element.CategoryDelegate{}), + items: element.CreateDefaultList("Пароли:", defaultWidth, listHeight, element.ItemDelegate{}), - usecase: usecase, + usecaseSync: usecaseSync, + usecaseItem: usecaseItem, } return f @@ -62,44 +67,46 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: f.categories.SetWidth(msg.Width) - f.passwords.SetWidth(msg.Width) + f.items.SetWidth(msg.Width) return f, nil case common.BackMessage: switch msg.State { case stateCategoryList: return f.Update(OpenCategoryListMessage{}) - case statePasswordList: - return f.Update(OpenPasswordListMessage{}) + case stateItemList: + return f.Update(OpenItemListMessage{}) } case GetAllDataMessage: f.state = stateCategoryList - f.err = f.usecase.Do(context.TODO()) + f.err = f.usecaseSync.Do(context.TODO()) return f.Update(OpenCategoryListMessage{}) case OpenCategoryListMessage: f.state = stateCategoryList - // todo получаем данные из базы - f.categories.SetItems(nil) - f.categories.InsertItem(len(f.categories.Items()), element.Item{Name: "Логины"}) - f.categories.InsertItem(len(f.categories.Items()), element.Item{Name: "Тексты"}) - f.categories.InsertItem(len(f.categories.Items()), element.Item{Name: "Файлы"}) - f.categories.InsertItem(len(f.categories.Items()), element.Item{Name: "Банковские карты"}) + f.categories.InsertItem(len(f.categories.Items()), element.Category{ID: client.CategoryPassword, Name: "Пароли"}) + f.categories.InsertItem(len(f.categories.Items()), element.Category{ID: client.CategoryText, Name: "Тексты"}) + f.categories.InsertItem(len(f.categories.Items()), element.Category{ID: client.CategoryBlob, Name: "Файлы"}) + f.categories.InsertItem(len(f.categories.Items()), element.Category{ID: client.CategoryBankCard, Name: "Банковские карты"}) return f, nil - case OpenPasswordListMessage: - f.state = statePasswordList + case OpenItemListMessage: + f.state = stateItemList - // todo получаем данные из базы + items, err := f.usecaseItem.ItemsByCategory(context.TODO(), msg.Category) + if err != nil { + f.err = err + return f, nil + } + + f.items.SetItems(nil) + f.items.Title = f.categories.SelectedItem().(element.Category).Name + ":" - f.passwords.SetItems(nil) - f.passwords.Title = f.categories.SelectedItem().(element.Item).Name + ":" - f.passwords.InsertItem(len(f.categories.Items()), element.Item{Name: "Пароль 1"}) - f.passwords.InsertItem(len(f.categories.Items()), element.Item{Name: "Пароль 2"}) - f.passwords.InsertItem(len(f.categories.Items()), element.Item{Name: "Пароль 3"}) - f.passwords.InsertItem(len(f.categories.Items()), element.Item{Name: "Пароль 4"}) + for _, p := range items { + f.items.InsertItem(len(f.categories.Items()), element.Item{Name: p.Title}) + } return f, nil case tea.KeyMsg: @@ -109,14 +116,14 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, common.Keys.Enter): switch f.state { case stateCategoryList: - if i, ok := f.categories.SelectedItem().(element.Item); ok { - return f.Update(OpenPasswordListMessage{ - Category: i, + if c, ok := f.categories.SelectedItem().(element.Category); ok { + return f.Update(OpenItemListMessage{ + Category: c.ID, }) } - case statePasswordList: - if i, ok := f.passwords.SelectedItem().(element.Item); ok { - return f.main.Update(item.OpenMessage{ + case stateItemList: + if i, ok := f.items.SelectedItem().(element.Item); ok { + return f.main.Update(itemModel.OpenMessage{ BackModel: f, BackState: f.state, Item: i, @@ -127,7 +134,7 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return f, nil case key.Matches(msg, common.Keys.Back): switch f.state { - case statePasswordList: + case stateItemList: return f.Update(OpenCategoryListMessage{}) } } @@ -137,8 +144,8 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch f.state { case stateCategoryList: f.categories, cmd = f.categories.Update(msg) - case statePasswordList: - f.passwords, cmd = f.passwords.Update(msg) + case stateItemList: + f.items, cmd = f.items.Update(msg) } return f, cmd @@ -153,8 +160,8 @@ func (f *Model) View() string { switch f.state { case stateCategoryList: b.WriteString(f.categories.View()) - case statePasswordList: - b.WriteString(f.passwords.View()) + case stateItemList: + b.WriteString(f.items.View()) } //b.WriteRune('\n') diff --git a/internal/cli/model/master/model.go b/internal/cli/model/master/model.go index f983cc6..a484105 100644 --- a/internal/cli/model/master/model.go +++ b/internal/cli/model/master/model.go @@ -65,7 +65,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m.formRegister.Update(msg) case listf.OpenCategoryListMessage: return m.formList.Update(msg) - case listf.OpenPasswordListMessage: + case listf.OpenItemListMessage: return m.formList.Update(msg) case item.OpenMessage: return m.formPassword.Update(msg) diff --git a/internal/domain/client/item.go b/internal/domain/client/item.go index 1817519..69a0bb9 100644 --- a/internal/domain/client/item.go +++ b/internal/domain/client/item.go @@ -9,13 +9,13 @@ import ( type Category int const ( - CategoryLogin Category = iota + CategoryPassword Category = iota CategoryText CategoryBlob CategoryBankCard ) -type Item struct { +type RawItem struct { GUID uuid.UUID CategoryID Category Title string @@ -24,3 +24,28 @@ type Item struct { CreatedAt time.Time UpdatedAt time.Time } + +type Item struct { + GUID uuid.UUID + CategoryID Category + Title string + Value interface{} + Notes string + CreatedAt time.Time + UpdatedAt time.Time +} + +type Password struct { + Login string `json:"login"` + Password string `json:"password"` +} + +type Blob struct { + Data string `json:"data"` +} + +type BankCard struct { + Number string `json:"number"` + CVV string `json:"cvv"` + Expiry time.Time `json:"exp"` +} diff --git a/internal/domain/data/item.go b/internal/domain/data/item.go index a8dd3bf..7004725 100644 --- a/internal/domain/data/item.go +++ b/internal/domain/data/item.go @@ -6,15 +6,6 @@ import ( "github.com/google/uuid" ) -type Category int - -const ( - CategoryLogin Category = iota - CategoryText - CategoryFile - CategoryBankCard -) - type Item struct { GUID uuid.UUID UserGUID uuid.UUID diff --git a/internal/infrastructure/store/client/item/model.go b/internal/infrastructure/store/client/item/model.go new file mode 100644 index 0000000..57a30cb --- /dev/null +++ b/internal/infrastructure/store/client/item/model.go @@ -0,0 +1,39 @@ +package item + +import ( + "time" + + "github.com/google/uuid" + + model "github.com/bjlag/go-keeper/internal/domain/client" +) + +type row struct { + GUID uuid.UUID `db:"guid"` + CategoryID model.Category `db:"category_id"` + Title string `db:"title"` + Value *[]byte `db:"value"` + Notes string `db:"notes"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` +} + +func toModels(rows []row) []model.RawItem { + items := make([]model.RawItem, len(rows)) + for i, r := range rows { + items[i] = r.toModel() + } + return items +} + +func (r *row) toModel() model.RawItem { + return model.RawItem{ + GUID: r.GUID, + CategoryID: r.CategoryID, + Title: r.Title, + Value: r.Value, + Notes: r.Notes, + CreatedAt: r.CreatedAt, + UpdatedAt: r.UpdatedAt, + } +} diff --git a/internal/infrastructure/store/client/item/store.go b/internal/infrastructure/store/client/item/store.go index 645f24c..415734f 100644 --- a/internal/infrastructure/store/client/item/store.go +++ b/internal/infrastructure/store/client/item/store.go @@ -2,6 +2,8 @@ package item import ( "context" + "database/sql" + "errors" "fmt" "github.com/jmoiron/sqlx" @@ -21,7 +23,7 @@ func NewStore(db *sqlx.DB) *Store { } } -func (s *Store) SaveItems(ctx context.Context, items []model.Item) error { +func (s *Store) SaveItems(ctx context.Context, items []model.RawItem) error { const op = prefixOp + "SaveItems" tx, err := s.db.BeginTx(ctx, nil) @@ -34,7 +36,7 @@ func (s *Store) SaveItems(ctx context.Context, items []model.Item) error { for _, i := range items { query := ` - INSERT INTO items (guid, categoryid, title, value, notes, created_at, updated_at) + INSERT INTO items (guid, category_id, title, value, notes, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT (guid) DO UPDATE SET title = excluded.title, @@ -56,3 +58,23 @@ func (s *Store) SaveItems(ctx context.Context, items []model.Item) error { return nil } + +func (s *Store) ItemsByCategory(ctx context.Context, category model.Category) ([]model.RawItem, error) { + const op = prefixOp + "Passwords" + + query := ` + SELECT guid, category_id, title, value, notes, created_at, updated_at + FROM items + WHERE category_id = $1; + ` + var rows []row + err := s.db.SelectContext(ctx, &rows, query, category) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, nil + } + return nil, fmt.Errorf("%s: %w", op, err) + } + + return toModels(rows), nil +} diff --git a/internal/usecase/client/item/contract.go b/internal/usecase/client/item/contract.go new file mode 100644 index 0000000..21c0de1 --- /dev/null +++ b/internal/usecase/client/item/contract.go @@ -0,0 +1,11 @@ +package item + +import ( + "context" + + model "github.com/bjlag/go-keeper/internal/domain/client" +) + +type itemStore interface { + ItemsByCategory(ctx context.Context, category model.Category) ([]model.RawItem, error) +} diff --git a/internal/usecase/client/item/usecase.go b/internal/usecase/client/item/usecase.go new file mode 100644 index 0000000..fbf9ad5 --- /dev/null +++ b/internal/usecase/client/item/usecase.go @@ -0,0 +1,64 @@ +package item + +import ( + "context" + "encoding/json" + "fmt" + + model "github.com/bjlag/go-keeper/internal/domain/client" +) + +const prefixOp = "usecase.item" + +type Usecase struct { + itemStore itemStore +} + +func NewUsecase(itemStore itemStore) *Usecase { + return &Usecase{ + itemStore: itemStore, + } +} + +func (u *Usecase) ItemsByCategory(ctx context.Context, category model.Category) ([]model.Item, error) { + const op = prefixOp + ".ItemsByCategory" + + rawItems, err := u.itemStore.ItemsByCategory(ctx, category) + if err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + + items := make([]model.Item, len(rawItems)) + for i, item := range rawItems { + var v interface{} + if item.Value != nil { + switch item.CategoryID { + case model.CategoryPassword: + v = &model.Password{} + case model.CategoryBlob: + v = &model.Blob{} + case model.CategoryBankCard: + v = &model.BankCard{} + default: + return nil, fmt.Errorf("unknown item category: %d", item.CategoryID) + } + + err = json.Unmarshal(*item.Value, &v) + if err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + } + + items[i] = model.Item{ + GUID: item.GUID, + CategoryID: item.CategoryID, + Title: item.Title, + Value: v, + Notes: item.Notes, + CreatedAt: item.CreatedAt, + UpdatedAt: item.UpdatedAt, + } + } + + return items, nil +} diff --git a/internal/usecase/client/sync/contract.go b/internal/usecase/client/sync/contract.go index 6f5fcd5..1b20e83 100644 --- a/internal/usecase/client/sync/contract.go +++ b/internal/usecase/client/sync/contract.go @@ -12,5 +12,5 @@ type client interface { } type store interface { - SaveItems(ctx context.Context, items []model.Item) error + SaveItems(ctx context.Context, items []model.RawItem) error } diff --git a/internal/usecase/client/sync/usecase.go b/internal/usecase/client/sync/usecase.go index 6a050fc..fbd319e 100644 --- a/internal/usecase/client/sync/usecase.go +++ b/internal/usecase/client/sync/usecase.go @@ -38,6 +38,10 @@ func (d *Data) UnmarshalJSON(data []byte) error { if alias.Value != nil { value := []byte(*alias.Value) + + if alias.CategoryID == model.CategoryPassword { + + } d.Value = &value } @@ -74,7 +78,7 @@ func (u *Usecase) Do(ctx context.Context) error { break } - items := make([]model.Item, 0, len(out.Items)) + items := make([]model.RawItem, 0, len(out.Items)) for _, item := range out.Items { // todo расшифровка // todo общие данные в отдельных полях @@ -85,7 +89,7 @@ func (u *Usecase) Do(ctx context.Context) error { return fmt.Errorf("%s: %w", op, err) } - items = append(items, model.Item{ + items = append(items, model.RawItem{ GUID: item.GUID, CategoryID: data.CategoryID, Title: data.Title, diff --git a/migrations/client/001_create_data_table.up.sql b/migrations/client/001_create_data_table.up.sql index e34551d..fce931e 100644 --- a/migrations/client/001_create_data_table.up.sql +++ b/migrations/client/001_create_data_table.up.sql @@ -1,6 +1,6 @@ CREATE TABLE IF NOT EXISTS items ( guid text PRIMARY KEY NOT NULL, - categoryId INT NOT NULL, + category_id INT NOT NULL, title text NOT NULL, value text, notes text NOT NULL, From 9a4f225c3f44336449720288593aa30f465768e0 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Thu, 6 Mar 2025 01:19:41 +0300 Subject: [PATCH 37/94] client: view password item --- internal/app/client/app.go | 2 +- internal/cli/element/list.go | 22 ++--- internal/cli/element/textarea.go | 10 +++ internal/cli/element/textinput.go | 14 +++- .../cli/model/item/{ => password}/message.go | 2 +- .../cli/model/item/{ => password}/model.go | 80 ++++++++++++++----- internal/cli/model/list/model.go | 26 +++--- internal/cli/model/master/model.go | 6 +- internal/cli/model/master/option.go | 4 +- internal/domain/client/item.go | 14 ++++ internal/usecase/client/item/usecase.go | 5 +- 11 files changed, 136 insertions(+), 49 deletions(-) create mode 100644 internal/cli/element/textarea.go rename internal/cli/model/item/{ => password}/message.go (91%) rename internal/cli/model/item/{ => password}/model.go (71%) diff --git a/internal/app/client/app.go b/internal/app/client/app.go index a7a596e..07466d6 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -3,11 +3,11 @@ package client import ( "context" "fmt" + modelItem "github.com/bjlag/go-keeper/internal/cli/model/item/password" tea "github.com/charmbracelet/bubbletea" "go.uber.org/zap" - modelItem "github.com/bjlag/go-keeper/internal/cli/model/item" "github.com/bjlag/go-keeper/internal/cli/model/list" formLogin "github.com/bjlag/go-keeper/internal/cli/model/login" "github.com/bjlag/go-keeper/internal/cli/model/master" diff --git a/internal/cli/element/list.go b/internal/cli/element/list.go index d860724..0ffdeba 100644 --- a/internal/cli/element/list.go +++ b/internal/cli/element/list.go @@ -2,14 +2,15 @@ package element import ( "fmt" - "github.com/google/uuid" "io" "strings" - "github.com/bjlag/go-keeper/internal/cli/style" - "github.com/bjlag/go-keeper/internal/domain/client" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" + "github.com/google/uuid" + + "github.com/bjlag/go-keeper/internal/cli/style" + "github.com/bjlag/go-keeper/internal/domain/client" ) func CreateDefaultList(title string, with, height int, itemDelegate list.ItemDelegate, items ...list.Item) list.Model { @@ -26,8 +27,8 @@ func CreateDefaultList(title string, with, height int, itemDelegate list.ItemDel } type Category struct { - ID client.Category - Name string + ID client.Category + Title string } func (i Category) FilterValue() string { return "" } @@ -52,7 +53,7 @@ func (d CategoryDelegate) Render(w io.Writer, m list.Model, index int, listItem return } - str := fmt.Sprintf("%d. %s", index+1, i.Name) + str := fmt.Sprintf("%d. %s", index+1, i.Title) fn := style.ListItemStyle.Render if index == m.Index() { @@ -65,8 +66,11 @@ func (d CategoryDelegate) Render(w io.Writer, m list.Model, index int, listItem } type Item struct { - GUID uuid.UUID - Name string + GUID uuid.UUID + Category client.Category + Title string + Value interface{} + Notes string } func (i Item) FilterValue() string { return "" } @@ -91,7 +95,7 @@ func (d ItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list return } - str := fmt.Sprintf("%d. %s", index+1, i.Name) + str := fmt.Sprintf("%d. %s", index+1, i.Title) fn := style.ListItemStyle.Render if index == m.Index() { diff --git a/internal/cli/element/textarea.go b/internal/cli/element/textarea.go new file mode 100644 index 0000000..f46489c --- /dev/null +++ b/internal/cli/element/textarea.go @@ -0,0 +1,10 @@ +package element + +import "github.com/charmbracelet/bubbles/textarea" + +func CreateDefaultTextArea(placeholder string) textarea.Model { + m := textarea.New() + m.Placeholder = placeholder + + return m +} diff --git a/internal/cli/element/textinput.go b/internal/cli/element/textinput.go index e263747..606efa9 100644 --- a/internal/cli/element/textinput.go +++ b/internal/cli/element/textinput.go @@ -6,7 +6,15 @@ import ( "github.com/bjlag/go-keeper/internal/cli/style" ) -func CreateDefaultTextInput(placeholder string, limit int) textinput.Model { +type TextInputOption func(m *textinput.Model) + +func WithFocused() TextInputOption { + return func(m *textinput.Model) { + m.Focus() + } +} + +func CreateDefaultTextInput(placeholder string, limit int, opts ...TextInputOption) textinput.Model { m := textinput.New() m.Cursor.Style = style.CursorStyle @@ -14,5 +22,9 @@ func CreateDefaultTextInput(placeholder string, limit int) textinput.Model { m.CharLimit = limit m.Placeholder = placeholder + for _, o := range opts { + o(&m) + } + return m } diff --git a/internal/cli/model/item/message.go b/internal/cli/model/item/password/message.go similarity index 91% rename from internal/cli/model/item/message.go rename to internal/cli/model/item/password/message.go index 3d0a1c0..cfbe734 100644 --- a/internal/cli/model/item/message.go +++ b/internal/cli/model/item/password/message.go @@ -1,4 +1,4 @@ -package item +package password import ( tea "github.com/charmbracelet/bubbletea" diff --git a/internal/cli/model/item/model.go b/internal/cli/model/item/password/model.go similarity index 71% rename from internal/cli/model/item/model.go rename to internal/cli/model/item/password/model.go index 2d0bcbc..7adecef 100644 --- a/internal/cli/model/item/model.go +++ b/internal/cli/model/item/password/model.go @@ -1,4 +1,4 @@ -package item +package password import ( "errors" @@ -12,11 +12,15 @@ import ( "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/element" "github.com/bjlag/go-keeper/internal/cli/style" + "github.com/bjlag/go-keeper/internal/domain/client" + "github.com/charmbracelet/bubbles/textarea" ) const ( - posLogin int = iota + posTitle int = iota + posLogin posPassword + posNotes posEditBtn posDeleteBtn posBackBtn @@ -41,8 +45,10 @@ func InitModel() *Model { help: help.New(), header: "Регистрация", elements: []interface{}{ - posLogin: element.CreateDefaultTextInput("Login", 50), - posPassword: element.CreateDefaultTextInput("Password", 50), + posTitle: element.CreateDefaultTextInput("Название", 50, element.WithFocused()), + posLogin: element.CreateDefaultTextInput("Логин", 50), + posPassword: element.CreateDefaultTextInput("Пароль", 50), + posNotes: element.CreateDefaultTextArea("Заметки"), posEditBtn: element.CreateDefaultButton("Изменить"), posDeleteBtn: element.CreateDefaultButton("Удалить"), posBackBtn: element.CreateDefaultButton("Назад"), @@ -50,11 +56,6 @@ func InitModel() *Model { //usecase: usecase, } - if e, ok := f.elements[posLogin].(textinput.Model); ok { - e.Focus() - f.elements[posLogin] = style.SetFocusStyle(e) - } - return f } @@ -77,22 +78,38 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } return f, nil case OpenMessage: - // todo получаем данные из базы - - f.header = msg.Item.Name - f.category = "Категория" - f.backState = msg.BackState f.backModel = msg.BackModel - if input, ok := f.elements[posLogin].(textinput.Model); ok { - input.SetValue("login") - f.elements[posLogin] = input + f.header = msg.Item.Title + f.category = msg.Item.Category.String() + + value, ok := msg.Item.Value.(*client.Password) + if !ok { + f.err = errors.New("it is not password") + return f, nil } - if input, ok := f.elements[posPassword].(textinput.Model); ok { - input.SetValue("password") - f.elements[posPassword] = input + for i, e := range f.elements { + switch input := e.(type) { + case textinput.Model: + switch i { + case posTitle: + input.SetValue(msg.Item.Title) + f.elements[i] = input + case posLogin: + input.SetValue(value.Login) + f.elements[i] = input + case posPassword: + input.SetValue(value.Password) + f.elements[i] = input + default: + continue + } + case textarea.Model: + input.SetValue(msg.Item.Notes) + f.elements[i] = input + } } return f, nil @@ -124,6 +141,15 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { e.Blur() f.elements[i] = style.SetNoStyle(e) + case textarea.Model: + if i == f.pos { + e.Focus() + f.elements[i] = e + continue + } + + e.Blur() + f.elements[i] = e case element.Button: if i == f.pos { e.Focus() @@ -168,7 +194,14 @@ func (f *Model) View() string { b.WriteRune('\n') for i := range f.elements { - if e, ok := f.elements[i].(textinput.Model); ok { + switch e := f.elements[i].(type) { + case textinput.Model: + b.WriteString(e.Placeholder) + b.WriteRune('\n') + b.WriteString(e.View()) + b.WriteRune('\n') + b.WriteRune('\n') + case textarea.Model: b.WriteString(e.Placeholder) b.WriteRune('\n') b.WriteString(e.View()) @@ -213,7 +246,10 @@ func (f *Model) updateInputs(msg tea.Msg) tea.Cmd { cmds := make([]tea.Cmd, len(f.elements)) for i := range f.elements { - if m, ok := f.elements[i].(textinput.Model); ok { + switch m := f.elements[i].(type) { + case textinput.Model: + f.elements[i], cmds[i] = m.Update(msg) + case textarea.Model: f.elements[i], cmds[i] = m.Update(msg) } } diff --git a/internal/cli/model/list/model.go b/internal/cli/model/list/model.go index dc78388..5114d6d 100644 --- a/internal/cli/model/list/model.go +++ b/internal/cli/model/list/model.go @@ -11,7 +11,7 @@ import ( "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/element" - itemModel "github.com/bjlag/go-keeper/internal/cli/model/item" + "github.com/bjlag/go-keeper/internal/cli/model/item/password" "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/domain/client" "github.com/bjlag/go-keeper/internal/usecase/client/item" @@ -86,10 +86,10 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { f.state = stateCategoryList f.categories.SetItems(nil) - f.categories.InsertItem(len(f.categories.Items()), element.Category{ID: client.CategoryPassword, Name: "Пароли"}) - f.categories.InsertItem(len(f.categories.Items()), element.Category{ID: client.CategoryText, Name: "Тексты"}) - f.categories.InsertItem(len(f.categories.Items()), element.Category{ID: client.CategoryBlob, Name: "Файлы"}) - f.categories.InsertItem(len(f.categories.Items()), element.Category{ID: client.CategoryBankCard, Name: "Банковские карты"}) + f.categories.InsertItem(len(f.categories.Items()), element.Category{ID: client.CategoryPassword, Title: client.CategoryPassword.String()}) + f.categories.InsertItem(len(f.categories.Items()), element.Category{ID: client.CategoryText, Title: client.CategoryText.String()}) + f.categories.InsertItem(len(f.categories.Items()), element.Category{ID: client.CategoryBlob, Title: client.CategoryBlob.String()}) + f.categories.InsertItem(len(f.categories.Items()), element.Category{ID: client.CategoryBankCard, Title: client.CategoryBankCard.String()}) return f, nil case OpenItemListMessage: @@ -101,11 +101,19 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return f, nil } - f.items.SetItems(nil) - f.items.Title = f.categories.SelectedItem().(element.Category).Name + ":" + if c, ok := f.categories.SelectedItem().(element.Category); ok { + f.items.Title = c.Title + ":" + } + f.items.SetItems(nil) for _, p := range items { - f.items.InsertItem(len(f.categories.Items()), element.Item{Name: p.Title}) + f.items.InsertItem(len(f.categories.Items()), element.Item{ + GUID: p.GUID, + Category: p.CategoryID, + Title: p.Title, + Value: p.Value, + Notes: p.Notes, + }) } return f, nil @@ -123,7 +131,7 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } case stateItemList: if i, ok := f.items.SelectedItem().(element.Item); ok { - return f.main.Update(itemModel.OpenMessage{ + return f.main.Update(password.OpenMessage{ BackModel: f, BackState: f.state, Item: i, diff --git a/internal/cli/model/master/model.go b/internal/cli/model/master/model.go index a484105..7494ab6 100644 --- a/internal/cli/model/master/model.go +++ b/internal/cli/model/master/model.go @@ -8,7 +8,7 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/bjlag/go-keeper/internal/cli/common" - "github.com/bjlag/go-keeper/internal/cli/model/item" + "github.com/bjlag/go-keeper/internal/cli/model/item/password" listf "github.com/bjlag/go-keeper/internal/cli/model/list" "github.com/bjlag/go-keeper/internal/cli/model/login" "github.com/bjlag/go-keeper/internal/cli/model/register" @@ -23,7 +23,7 @@ type Model struct { formLogin *login.Model formRegister *register.Model formList *listf.Model - formPassword *item.Model + formPassword *password.Model storeTokens *token.Store } @@ -67,7 +67,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m.formList.Update(msg) case listf.OpenItemListMessage: return m.formList.Update(msg) - case item.OpenMessage: + case password.OpenMessage: return m.formPassword.Update(msg) // Success diff --git a/internal/cli/model/master/option.go b/internal/cli/model/master/option.go index ff04e41..25e2efd 100644 --- a/internal/cli/model/master/option.go +++ b/internal/cli/model/master/option.go @@ -1,7 +1,7 @@ package master import ( - "github.com/bjlag/go-keeper/internal/cli/model/item" + "github.com/bjlag/go-keeper/internal/cli/model/item/password" "github.com/bjlag/go-keeper/internal/cli/model/list" "github.com/bjlag/go-keeper/internal/cli/model/login" "github.com/bjlag/go-keeper/internal/cli/model/register" @@ -37,7 +37,7 @@ func WithListFormForm(form *list.Model) Option { } } -func WithShowPasswordForm(form *item.Model) Option { +func WithShowPasswordForm(form *password.Model) Option { return func(m *Model) { form.SetMainModel(m) m.formPassword = form diff --git a/internal/domain/client/item.go b/internal/domain/client/item.go index 69a0bb9..509a784 100644 --- a/internal/domain/client/item.go +++ b/internal/domain/client/item.go @@ -15,6 +15,20 @@ const ( CategoryBankCard ) +func (c Category) String() string { + switch c { + case CategoryPassword: + return "Пароль" + case CategoryText: + return "Текст" + case CategoryBlob: + return "Файл" + case CategoryBankCard: + return "Банковская карта" + } + return "" +} + type RawItem struct { GUID uuid.UUID CategoryID Category diff --git a/internal/usecase/client/item/usecase.go b/internal/usecase/client/item/usecase.go index fbf9ad5..da4982b 100644 --- a/internal/usecase/client/item/usecase.go +++ b/internal/usecase/client/item/usecase.go @@ -3,6 +3,7 @@ package item import ( "context" "encoding/json" + "errors" "fmt" model "github.com/bjlag/go-keeper/internal/domain/client" @@ -10,6 +11,8 @@ import ( const prefixOp = "usecase.item" +var ErrUnknownCategory = errors.New("unknown category") + type Usecase struct { itemStore itemStore } @@ -40,7 +43,7 @@ func (u *Usecase) ItemsByCategory(ctx context.Context, category model.Category) case model.CategoryBankCard: v = &model.BankCard{} default: - return nil, fmt.Errorf("unknown item category: %d", item.CategoryID) + return nil, fmt.Errorf("%w: %d", ErrUnknownCategory, item.CategoryID) } err = json.Unmarshal(*item.Value, &v) From 3f297ae736083d92b419ec58ce860de9081f58f4 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Fri, 7 Mar 2025 00:28:43 +0300 Subject: [PATCH 38/94] client: view text item --- internal/app/client/app.go | 6 +- internal/cli/common/message.go | 12 + internal/cli/element/textarea.go | 10 - internal/cli/element/textarea/textarea.go | 30 +++ .../cli/element/{ => textinput}/textinput.go | 16 +- internal/cli/model/item/password/model.go | 42 +-- internal/cli/model/item/text/message.go | 13 + internal/cli/model/item/text/model.go | 244 ++++++++++++++++++ internal/cli/model/list/model.go | 3 +- internal/cli/model/login/model.go | 8 +- internal/cli/model/master/model.go | 24 +- internal/cli/model/master/option.go | 10 +- internal/cli/model/register/model.go | 8 +- 13 files changed, 364 insertions(+), 62 deletions(-) delete mode 100644 internal/cli/element/textarea.go create mode 100644 internal/cli/element/textarea/textarea.go rename internal/cli/element/{ => textinput}/textinput.go (58%) create mode 100644 internal/cli/model/item/text/message.go create mode 100644 internal/cli/model/item/text/model.go diff --git a/internal/app/client/app.go b/internal/app/client/app.go index 07466d6..a7ce4b9 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -3,11 +3,12 @@ package client import ( "context" "fmt" - modelItem "github.com/bjlag/go-keeper/internal/cli/model/item/password" tea "github.com/charmbracelet/bubbletea" "go.uber.org/zap" + "github.com/bjlag/go-keeper/internal/cli/model/item/password" + "github.com/bjlag/go-keeper/internal/cli/model/item/text" "github.com/bjlag/go-keeper/internal/cli/model/list" formLogin "github.com/bjlag/go-keeper/internal/cli/model/login" "github.com/bjlag/go-keeper/internal/cli/model/master" @@ -69,7 +70,8 @@ func (a *App) Run(ctx context.Context) error { master.WithLoginForm(formLogin.InitModel(ucLogin)), master.WithRegisterForm(formRegister.InitModel(ucRegister)), master.WithListFormForm(list.InitModel(ucSync, ucItem)), - master.WithShowPasswordForm(modelItem.InitModel()), + master.WithPasswordItemForm(password.InitModel()), + master.WithTextItemForm(text.InitModel()), ) f, err := tea.LogToFile("debug.log", "debug") diff --git a/internal/cli/common/message.go b/internal/cli/common/message.go index 604af77..e7b1dfc 100644 --- a/internal/cli/common/message.go +++ b/internal/cli/common/message.go @@ -1,5 +1,17 @@ package common +import ( + tea "github.com/charmbracelet/bubbletea" + + "github.com/bjlag/go-keeper/internal/cli/element" +) + type BackMessage struct { State int } + +type OpenItemMessage struct { + BackModel tea.Model + BackState int + Item element.Item +} diff --git a/internal/cli/element/textarea.go b/internal/cli/element/textarea.go deleted file mode 100644 index f46489c..0000000 --- a/internal/cli/element/textarea.go +++ /dev/null @@ -1,10 +0,0 @@ -package element - -import "github.com/charmbracelet/bubbles/textarea" - -func CreateDefaultTextArea(placeholder string) textarea.Model { - m := textarea.New() - m.Placeholder = placeholder - - return m -} diff --git a/internal/cli/element/textarea/textarea.go b/internal/cli/element/textarea/textarea.go new file mode 100644 index 0000000..d6999a9 --- /dev/null +++ b/internal/cli/element/textarea/textarea.go @@ -0,0 +1,30 @@ +package textarea + +import ( + "github.com/charmbracelet/bubbles/textarea" +) + +type Option func(m *textarea.Model) + +func WithFocused() Option { + return func(m *textarea.Model) { + m.Focus() + } +} + +func WithValue(value string) Option { + return func(m *textarea.Model) { + m.SetValue(value) + } +} + +func CreateDefaultTextArea(placeholder string, opts ...Option) textarea.Model { + m := textarea.New() + m.Placeholder = placeholder + + for _, opt := range opts { + opt(&m) + } + + return m +} diff --git a/internal/cli/element/textinput.go b/internal/cli/element/textinput/textinput.go similarity index 58% rename from internal/cli/element/textinput.go rename to internal/cli/element/textinput/textinput.go index 606efa9..e56fe52 100644 --- a/internal/cli/element/textinput.go +++ b/internal/cli/element/textinput/textinput.go @@ -1,4 +1,4 @@ -package element +package textinput import ( "github.com/charmbracelet/bubbles/textinput" @@ -14,16 +14,22 @@ func WithFocused() TextInputOption { } } -func CreateDefaultTextInput(placeholder string, limit int, opts ...TextInputOption) textinput.Model { +func WithValue(value string) TextInputOption { + return func(m *textinput.Model) { + m.SetValue(value) + } +} + +func CreateDefaultTextInput(placeholder string, opts ...TextInputOption) textinput.Model { m := textinput.New() m.Cursor.Style = style.CursorStyle m.PlaceholderStyle = style.BlurredStyle - m.CharLimit = limit + m.CharLimit = 50 m.Placeholder = placeholder - for _, o := range opts { - o(&m) + for _, opt := range opts { + opt(&m) } return m diff --git a/internal/cli/model/item/password/model.go b/internal/cli/model/item/password/model.go index 7adecef..f41b4a1 100644 --- a/internal/cli/model/item/password/model.go +++ b/internal/cli/model/item/password/model.go @@ -14,6 +14,9 @@ import ( "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/domain/client" "github.com/charmbracelet/bubbles/textarea" + + tarea "github.com/bjlag/go-keeper/internal/cli/element/textarea" + tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" ) const ( @@ -44,15 +47,7 @@ func InitModel() *Model { f := &Model{ help: help.New(), header: "Регистрация", - elements: []interface{}{ - posTitle: element.CreateDefaultTextInput("Название", 50, element.WithFocused()), - posLogin: element.CreateDefaultTextInput("Логин", 50), - posPassword: element.CreateDefaultTextInput("Пароль", 50), - posNotes: element.CreateDefaultTextArea("Заметки"), - posEditBtn: element.CreateDefaultButton("Изменить"), - posDeleteBtn: element.CreateDefaultButton("Удалить"), - posBackBtn: element.CreateDefaultButton("Назад"), - }, + //usecase: usecase, } @@ -80,7 +75,6 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case OpenMessage: f.backState = msg.BackState f.backModel = msg.BackModel - f.header = msg.Item.Title f.category = msg.Item.Category.String() @@ -90,26 +84,14 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return f, nil } - for i, e := range f.elements { - switch input := e.(type) { - case textinput.Model: - switch i { - case posTitle: - input.SetValue(msg.Item.Title) - f.elements[i] = input - case posLogin: - input.SetValue(value.Login) - f.elements[i] = input - case posPassword: - input.SetValue(value.Password) - f.elements[i] = input - default: - continue - } - case textarea.Model: - input.SetValue(msg.Item.Notes) - f.elements[i] = input - } + f.elements = []interface{}{ + posTitle: tinput.CreateDefaultTextInput("Название", tinput.WithValue(msg.Item.Title), tinput.WithFocused()), + posLogin: tinput.CreateDefaultTextInput("Логин", tinput.WithValue(value.Login)), + posPassword: tinput.CreateDefaultTextInput("Пароль", tinput.WithValue(value.Password)), + posNotes: tarea.CreateDefaultTextArea("Заметки", tarea.WithValue(msg.Item.Notes)), + posEditBtn: element.CreateDefaultButton("Изменить"), + posDeleteBtn: element.CreateDefaultButton("Удалить"), + posBackBtn: element.CreateDefaultButton("Назад"), } return f, nil diff --git a/internal/cli/model/item/text/message.go b/internal/cli/model/item/text/message.go new file mode 100644 index 0000000..405d3d6 --- /dev/null +++ b/internal/cli/model/item/text/message.go @@ -0,0 +1,13 @@ +package text + +import ( + tea "github.com/charmbracelet/bubbletea" + + "github.com/bjlag/go-keeper/internal/cli/element" +) + +type OpenMessage struct { + BackModel tea.Model + BackState int + Item element.Item +} diff --git a/internal/cli/model/item/text/model.go b/internal/cli/model/item/text/model.go new file mode 100644 index 0000000..72b8558 --- /dev/null +++ b/internal/cli/model/item/text/model.go @@ -0,0 +1,244 @@ +package text + +import ( + "errors" + "strings" + + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/textinput" + tea "github.com/charmbracelet/bubbletea" + + "github.com/bjlag/go-keeper/internal/cli/common" + "github.com/bjlag/go-keeper/internal/cli/element" + tarea "github.com/bjlag/go-keeper/internal/cli/element/textarea" + tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" + "github.com/bjlag/go-keeper/internal/cli/style" + "github.com/charmbracelet/bubbles/textarea" +) + +const ( + posTitle int = iota + posNotes + posEditBtn + posDeleteBtn + posBackBtn +) + +type Model struct { + main tea.Model + help help.Model + header string + elements []interface{} + pos int + err error + + backModel tea.Model + backState int + + category string +} + +func InitModel() *Model { + f := &Model{ + help: help.New(), + header: "Регистрация", + //elements: make([]interface{}, 5), + //usecase: usecase, + } + + return f +} + +func (f *Model) SetMainModel(m tea.Model) { + f.main = m +} + +func (f *Model) Init() tea.Cmd { + return nil +} + +func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + for i := range f.elements { + switch e := f.elements[i].(type) { + case textinput.Model: + e.Width = msg.Width + } + } + return f, nil + case OpenMessage: + f.backState = msg.BackState + f.backModel = msg.BackModel + f.header = msg.Item.Title + f.category = msg.Item.Category.String() + + f.elements = []interface{}{ + posTitle: tinput.CreateDefaultTextInput("Название", tinput.WithValue(msg.Item.Title), tinput.WithFocused()), + posNotes: tarea.CreateDefaultTextArea("Текст", tarea.WithValue(msg.Item.Notes)), + posEditBtn: element.CreateDefaultButton("Изменить"), + posDeleteBtn: element.CreateDefaultButton("Удалить"), + posBackBtn: element.CreateDefaultButton("Назад"), + } + + //for i, e := range f.elements { + // switch input := e.(type) { + // case textinput.Model: + // switch i { + // case posTitle: + // input.SetValue(msg.Item.Title) + // f.elements[i] = input + // default: + // continue + // } + // case textarea.Model: + // input.SetValue(msg.Item.Notes) + // f.elements[i] = input + // } + //} + + return f, nil + case tea.KeyMsg: + switch { + case key.Matches(msg, common.Keys.Quit): + return f, tea.Quit + case key.Matches(msg, common.Keys.Navigation): + if key.Matches(msg, common.Keys.Down, common.Keys.Tab) { + f.pos++ + } else { + f.pos-- + } + + if f.pos > len(f.elements)-1 { + f.pos = 0 + } else if f.pos < 0 { + f.pos = len(f.elements) - 1 + } + + for i := range f.elements { + switch e := f.elements[i].(type) { + case textinput.Model: + if i == f.pos { + e.Focus() + f.elements[i] = style.SetFocusStyle(e) + continue + } + + e.Blur() + f.elements[i] = style.SetNoStyle(e) + case textarea.Model: + if i == f.pos { + e.Focus() + f.elements[i] = e + continue + } + + e.Blur() + f.elements[i] = e + case element.Button: + if i == f.pos { + e.Focus() + f.elements[i] = e + continue + } + e.Blur() + f.elements[i] = e + } + } + + return f, nil + case key.Matches(msg, common.Keys.Enter): + f.err = nil + + switch { + case f.pos == posBackBtn: + return f.backModel.Update(common.BackMessage{ + State: f.backState, + }) + } + + return f, nil + case key.Matches(msg, common.Keys.Back): + return f.backModel.Update(common.BackMessage{ + State: f.backState, + }) + } + } + + return f, f.updateInputs(msg) +} + +func (f *Model) View() string { + var b strings.Builder + + b.WriteString(style.TitleStyle.Render(f.header)) + b.WriteRune('\n') + + b.WriteString("Категория: ") + b.WriteString(f.category) + b.WriteRune('\n') + + for i := range f.elements { + switch e := f.elements[i].(type) { + case textinput.Model: + b.WriteString(e.Placeholder) + b.WriteRune('\n') + b.WriteString(e.View()) + b.WriteRune('\n') + b.WriteRune('\n') + case textarea.Model: + b.WriteString(e.Placeholder) + b.WriteRune('\n') + b.WriteString(e.View()) + b.WriteRune('\n') + b.WriteRune('\n') + } + } + + b.WriteRune('\n') + + for i := range f.elements { + if e, ok := f.elements[i].(element.Button); ok { + b.WriteString(e.String()) + b.WriteRune('\n') + } + } + + var ( + errValidate *common.ValidateError + errForm *common.FormError + ) + + // выводим ошибки валидации + if f.err != nil && (errors.As(f.err, &errValidate) || errors.As(f.err, &errForm)) { + b.WriteString(style.ErrorBlockStyle.Render(f.err.Error())) + b.WriteRune('\n') + } + + b.WriteRune('\n') + b.WriteString(f.help.View(common.Keys)) + + // выводим прочие ошибки + if f.err != nil && !(errors.As(f.err, &errValidate) || errors.As(f.err, &errForm)) { + b.WriteRune('\n') + b.WriteString(style.ErrorBlockStyle.Render(f.err.Error())) + } + + return b.String() +} + +func (f *Model) updateInputs(msg tea.Msg) tea.Cmd { + cmds := make([]tea.Cmd, len(f.elements)) + + for i := range f.elements { + switch m := f.elements[i].(type) { + case textinput.Model: + f.elements[i], cmds[i] = m.Update(msg) + case textarea.Model: + f.elements[i], cmds[i] = m.Update(msg) + } + } + + return tea.Batch(cmds...) +} diff --git a/internal/cli/model/list/model.go b/internal/cli/model/list/model.go index 5114d6d..5764f53 100644 --- a/internal/cli/model/list/model.go +++ b/internal/cli/model/list/model.go @@ -11,7 +11,6 @@ import ( "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/element" - "github.com/bjlag/go-keeper/internal/cli/model/item/password" "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/domain/client" "github.com/bjlag/go-keeper/internal/usecase/client/item" @@ -131,7 +130,7 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } case stateItemList: if i, ok := f.items.SelectedItem().(element.Item); ok { - return f.main.Update(password.OpenMessage{ + return f.main.Update(common.OpenItemMessage{ BackModel: f, BackState: f.state, Item: i, diff --git a/internal/cli/model/login/model.go b/internal/cli/model/login/model.go index b364fca..160a589 100644 --- a/internal/cli/model/login/model.go +++ b/internal/cli/model/login/model.go @@ -14,6 +14,7 @@ import ( "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/element" + tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" "github.com/bjlag/go-keeper/internal/cli/model/register" "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/infrastructure/validator" @@ -26,9 +27,6 @@ const ( posSubmitBtn posRegisterBtn posCloseBtn - - emailCharLimit = 20 - passwordCharLimit = 20 ) var errPasswordInvalid = common.NewFormError("Неверный email или пароль") @@ -49,8 +47,8 @@ func InitModel(usecase *login.Usecase) *Model { help: help.New(), header: "Авторизация", elements: []interface{}{ - posEmail: element.CreateDefaultTextInput("Email", emailCharLimit), - posPassword: element.CreateDefaultTextInput("Password", passwordCharLimit), + posEmail: tinput.CreateDefaultTextInput("Email"), + posPassword: tinput.CreateDefaultTextInput("Пароль"), posSubmitBtn: element.CreateDefaultButton("Вход"), posRegisterBtn: element.CreateDefaultButton("Регистрация"), posCloseBtn: element.CreateDefaultButton("Закрыть"), diff --git a/internal/cli/model/master/model.go b/internal/cli/model/master/model.go index 7494ab6..f673876 100644 --- a/internal/cli/model/master/model.go +++ b/internal/cli/model/master/model.go @@ -1,6 +1,7 @@ package master import ( + "github.com/bjlag/go-keeper/internal/domain/client" "strings" "github.com/charmbracelet/bubbles/help" @@ -9,6 +10,7 @@ import ( "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/model/item/password" + "github.com/bjlag/go-keeper/internal/cli/model/item/text" listf "github.com/bjlag/go-keeper/internal/cli/model/list" "github.com/bjlag/go-keeper/internal/cli/model/login" "github.com/bjlag/go-keeper/internal/cli/model/register" @@ -24,6 +26,7 @@ type Model struct { formRegister *register.Model formList *listf.Model formPassword *password.Model + formText *text.Model storeTokens *token.Store } @@ -67,8 +70,25 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m.formList.Update(msg) case listf.OpenItemListMessage: return m.formList.Update(msg) - case password.OpenMessage: - return m.formPassword.Update(msg) + //case password.OpenMessage: + // return m.formPassword.Update(msg) + //case text.OpenMessage: + // return m.formText.Update(msg) + case common.OpenItemMessage: + switch msg.Item.Category { + case client.CategoryPassword: + return m.formPassword.Update(password.OpenMessage{ + BackModel: msg.BackModel, + BackState: msg.BackState, + Item: msg.Item, + }) + case client.CategoryText: + return m.formText.Update(text.OpenMessage{ + BackModel: msg.BackModel, + BackState: msg.BackState, + Item: msg.Item, + }) + } // Success case login.SuccessMessage: diff --git a/internal/cli/model/master/option.go b/internal/cli/model/master/option.go index 25e2efd..cc12637 100644 --- a/internal/cli/model/master/option.go +++ b/internal/cli/model/master/option.go @@ -2,6 +2,7 @@ package master import ( "github.com/bjlag/go-keeper/internal/cli/model/item/password" + "github.com/bjlag/go-keeper/internal/cli/model/item/text" "github.com/bjlag/go-keeper/internal/cli/model/list" "github.com/bjlag/go-keeper/internal/cli/model/login" "github.com/bjlag/go-keeper/internal/cli/model/register" @@ -37,9 +38,16 @@ func WithListFormForm(form *list.Model) Option { } } -func WithShowPasswordForm(form *password.Model) Option { +func WithPasswordItemForm(form *password.Model) Option { return func(m *Model) { form.SetMainModel(m) m.formPassword = form } } + +func WithTextItemForm(form *text.Model) Option { + return func(m *Model) { + form.SetMainModel(m) + m.formText = form + } +} diff --git a/internal/cli/model/register/model.go b/internal/cli/model/register/model.go index 5a8435d..fd2b995 100644 --- a/internal/cli/model/register/model.go +++ b/internal/cli/model/register/model.go @@ -14,6 +14,7 @@ import ( "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/element" + tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/infrastructure/validator" "github.com/bjlag/go-keeper/internal/usecase/client/register" @@ -24,9 +25,6 @@ const ( posPassword posSubmitBtn posBackBtn - - emailCharLimit = 20 - passwordCharLimit = 20 ) var errUserAlreadyRegistered = common.NewFormError("Пользователь уже зарегистрирован") @@ -48,8 +46,8 @@ func InitModel(usecase *register.Usecase) *Model { help: help.New(), header: "Регистрация", elements: []interface{}{ - posEmail: element.CreateDefaultTextInput("Email", emailCharLimit), - posPassword: element.CreateDefaultTextInput("Password", passwordCharLimit), + posEmail: tinput.CreateDefaultTextInput("Email"), + posPassword: tinput.CreateDefaultTextInput("Пароль"), posSubmitBtn: element.CreateDefaultButton("Регистрация"), posBackBtn: element.CreateDefaultButton("Назад"), }, From 818bc6a9644d74d18216ff5b13f49983d320bcd0 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Fri, 7 Mar 2025 01:24:56 +0300 Subject: [PATCH 39/94] client: save items --- internal/app/client/app.go | 2 +- internal/cli/common/message.go | 4 +- internal/cli/element/{ => button}/button.go | 6 ++- internal/cli/element/{ => list}/list.go | 2 +- internal/cli/element/util.go | 17 ++++++ internal/cli/model/item/password/message.go | 4 +- internal/cli/model/item/password/model.go | 54 ++++++++++++++----- internal/cli/model/item/text/message.go | 4 +- internal/cli/model/item/text/model.go | 12 ++--- internal/cli/model/list/model.go | 24 ++++----- internal/cli/model/login/model.go | 12 ++--- internal/cli/model/register/model.go | 10 ++-- internal/domain/client/item.go | 14 ++--- .../infrastructure/store/client/item/model.go | 18 +++++++ .../infrastructure/store/client/item/store.go | 26 +++++++++ internal/usecase/client/item/usecase.go | 14 ++--- internal/usecase/client/sync/usecase.go | 4 -- 17 files changed, 158 insertions(+), 69 deletions(-) rename internal/cli/element/{ => button}/button.go (98%) rename internal/cli/element/{ => list}/list.go (99%) create mode 100644 internal/cli/element/util.go diff --git a/internal/app/client/app.go b/internal/app/client/app.go index a7ce4b9..f1b04ba 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -70,7 +70,7 @@ func (a *App) Run(ctx context.Context) error { master.WithLoginForm(formLogin.InitModel(ucLogin)), master.WithRegisterForm(formRegister.InitModel(ucRegister)), master.WithListFormForm(list.InitModel(ucSync, ucItem)), - master.WithPasswordItemForm(password.InitModel()), + master.WithPasswordItemForm(password.InitModel(storeItem)), master.WithTextItemForm(text.InitModel()), ) diff --git a/internal/cli/common/message.go b/internal/cli/common/message.go index e7b1dfc..5752bd5 100644 --- a/internal/cli/common/message.go +++ b/internal/cli/common/message.go @@ -3,7 +3,7 @@ package common import ( tea "github.com/charmbracelet/bubbletea" - "github.com/bjlag/go-keeper/internal/cli/element" + "github.com/bjlag/go-keeper/internal/cli/element/list" ) type BackMessage struct { @@ -13,5 +13,5 @@ type BackMessage struct { type OpenItemMessage struct { BackModel tea.Model BackState int - Item element.Item + Item list.Item } diff --git a/internal/cli/element/button.go b/internal/cli/element/button/button.go similarity index 98% rename from internal/cli/element/button.go rename to internal/cli/element/button/button.go index 87a9ff0..2eb51d1 100644 --- a/internal/cli/element/button.go +++ b/internal/cli/element/button/button.go @@ -1,9 +1,11 @@ -package element +package button import ( "fmt" - "github.com/bjlag/go-keeper/internal/cli/style" + "github.com/charmbracelet/lipgloss" + + "github.com/bjlag/go-keeper/internal/cli/style" ) type Button struct { diff --git a/internal/cli/element/list.go b/internal/cli/element/list/list.go similarity index 99% rename from internal/cli/element/list.go rename to internal/cli/element/list/list.go index 0ffdeba..2fbc406 100644 --- a/internal/cli/element/list.go +++ b/internal/cli/element/list/list.go @@ -1,4 +1,4 @@ -package element +package list import ( "fmt" diff --git a/internal/cli/element/util.go b/internal/cli/element/util.go new file mode 100644 index 0000000..4c9d72f --- /dev/null +++ b/internal/cli/element/util.go @@ -0,0 +1,17 @@ +package element + +import ( + "github.com/charmbracelet/bubbles/textarea" + "github.com/charmbracelet/bubbles/textinput" +) + +func GetValue(elements []interface{}, pos int) string { + switch e := elements[pos].(type) { + case textinput.Model: + return e.Value() + case textarea.Model: + return e.Value() + } + + return "" +} diff --git a/internal/cli/model/item/password/message.go b/internal/cli/model/item/password/message.go index cfbe734..b8e9a32 100644 --- a/internal/cli/model/item/password/message.go +++ b/internal/cli/model/item/password/message.go @@ -3,11 +3,11 @@ package password import ( tea "github.com/charmbracelet/bubbletea" - "github.com/bjlag/go-keeper/internal/cli/element" + "github.com/bjlag/go-keeper/internal/cli/element/list" ) type OpenMessage struct { BackModel tea.Model BackState int - Item element.Item + Item list.Item } diff --git a/internal/cli/model/item/password/model.go b/internal/cli/model/item/password/model.go index f41b4a1..4078407 100644 --- a/internal/cli/model/item/password/model.go +++ b/internal/cli/model/item/password/model.go @@ -1,6 +1,7 @@ package password import ( + "context" "errors" "strings" @@ -8,6 +9,7 @@ import ( "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" + "github.com/google/uuid" "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/element" @@ -15,8 +17,10 @@ import ( "github.com/bjlag/go-keeper/internal/domain/client" "github.com/charmbracelet/bubbles/textarea" + "github.com/bjlag/go-keeper/internal/cli/element/button" tarea "github.com/bjlag/go-keeper/internal/cli/element/textarea" tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" + "github.com/bjlag/go-keeper/internal/infrastructure/store/client/item" ) const ( @@ -40,15 +44,18 @@ type Model struct { backModel tea.Model backState int - category string + guid uuid.UUID + category client.Category + + storeItem *item.Store } -func InitModel() *Model { +func InitModel(storeItem *item.Store) *Model { f := &Model{ help: help.New(), header: "Регистрация", - //usecase: usecase, + storeItem: storeItem, } return f @@ -76,7 +83,8 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { f.backState = msg.BackState f.backModel = msg.BackModel f.header = msg.Item.Title - f.category = msg.Item.Category.String() + f.guid = msg.Item.GUID + f.category = msg.Item.Category value, ok := msg.Item.Value.(*client.Password) if !ok { @@ -89,9 +97,9 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { posLogin: tinput.CreateDefaultTextInput("Логин", tinput.WithValue(value.Login)), posPassword: tinput.CreateDefaultTextInput("Пароль", tinput.WithValue(value.Password)), posNotes: tarea.CreateDefaultTextArea("Заметки", tarea.WithValue(msg.Item.Notes)), - posEditBtn: element.CreateDefaultButton("Изменить"), - posDeleteBtn: element.CreateDefaultButton("Удалить"), - posBackBtn: element.CreateDefaultButton("Назад"), + posEditBtn: button.CreateDefaultButton("Изменить"), + posDeleteBtn: button.CreateDefaultButton("Удалить"), + posBackBtn: button.CreateDefaultButton("Назад"), } return f, nil @@ -132,7 +140,7 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { e.Blur() f.elements[i] = e - case element.Button: + case button.Button: if i == f.pos { e.Focus() f.elements[i] = e @@ -147,8 +155,15 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, common.Keys.Enter): f.err = nil - switch { - case f.pos == posBackBtn: + switch f.pos { + case posEditBtn: + err := f.edit() + if err != nil { + f.err = err + return f, nil + } + return f, nil + case posBackBtn: return f.backModel.Update(common.BackMessage{ State: f.backState, }) @@ -172,7 +187,7 @@ func (f *Model) View() string { b.WriteRune('\n') b.WriteString("Категория: ") - b.WriteString(f.category) + b.WriteString(f.category.String()) b.WriteRune('\n') for i := range f.elements { @@ -195,7 +210,7 @@ func (f *Model) View() string { b.WriteRune('\n') for i := range f.elements { - if e, ok := f.elements[i].(element.Button); ok { + if e, ok := f.elements[i].(button.Button); ok { b.WriteString(e.String()) b.WriteRune('\n') } @@ -238,3 +253,18 @@ func (f *Model) updateInputs(msg tea.Msg) tea.Cmd { return tea.Batch(cmds...) } + +func (f *Model) edit() error { + i := client.Item{ + GUID: f.guid, + Category: f.category, + Title: element.GetValue(f.elements, posTitle), + Value: client.Password{ + Password: element.GetValue(f.elements, posPassword), + Login: element.GetValue(f.elements, posLogin), + }, + Notes: element.GetValue(f.elements, posNotes), + } + + return f.storeItem.SaveItem(context.TODO(), i) +} diff --git a/internal/cli/model/item/text/message.go b/internal/cli/model/item/text/message.go index 405d3d6..8abdf8d 100644 --- a/internal/cli/model/item/text/message.go +++ b/internal/cli/model/item/text/message.go @@ -3,11 +3,11 @@ package text import ( tea "github.com/charmbracelet/bubbletea" - "github.com/bjlag/go-keeper/internal/cli/element" + "github.com/bjlag/go-keeper/internal/cli/element/list" ) type OpenMessage struct { BackModel tea.Model BackState int - Item element.Item + Item list.Item } diff --git a/internal/cli/model/item/text/model.go b/internal/cli/model/item/text/model.go index 72b8558..f2b6f08 100644 --- a/internal/cli/model/item/text/model.go +++ b/internal/cli/model/item/text/model.go @@ -10,7 +10,7 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/bjlag/go-keeper/internal/cli/common" - "github.com/bjlag/go-keeper/internal/cli/element" + "github.com/bjlag/go-keeper/internal/cli/element/button" tarea "github.com/bjlag/go-keeper/internal/cli/element/textarea" tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" "github.com/bjlag/go-keeper/internal/cli/style" @@ -77,9 +77,9 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { f.elements = []interface{}{ posTitle: tinput.CreateDefaultTextInput("Название", tinput.WithValue(msg.Item.Title), tinput.WithFocused()), posNotes: tarea.CreateDefaultTextArea("Текст", tarea.WithValue(msg.Item.Notes)), - posEditBtn: element.CreateDefaultButton("Изменить"), - posDeleteBtn: element.CreateDefaultButton("Удалить"), - posBackBtn: element.CreateDefaultButton("Назад"), + posEditBtn: button.CreateDefaultButton("Изменить"), + posDeleteBtn: button.CreateDefaultButton("Удалить"), + posBackBtn: button.CreateDefaultButton("Назад"), } //for i, e := range f.elements { @@ -136,7 +136,7 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { e.Blur() f.elements[i] = e - case element.Button: + case button.Button: if i == f.pos { e.Focus() f.elements[i] = e @@ -199,7 +199,7 @@ func (f *Model) View() string { b.WriteRune('\n') for i := range f.elements { - if e, ok := f.elements[i].(element.Button); ok { + if e, ok := f.elements[i].(button.Button); ok { b.WriteString(e.String()) b.WriteRune('\n') } diff --git a/internal/cli/model/list/model.go b/internal/cli/model/list/model.go index 5764f53..69d0828 100644 --- a/internal/cli/model/list/model.go +++ b/internal/cli/model/list/model.go @@ -10,7 +10,7 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/bjlag/go-keeper/internal/cli/common" - "github.com/bjlag/go-keeper/internal/cli/element" + elist "github.com/bjlag/go-keeper/internal/cli/element/list" "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/domain/client" "github.com/bjlag/go-keeper/internal/usecase/client/item" @@ -44,8 +44,8 @@ func InitModel(usecaseSync *sync.Usecase, usecaseItem *item.Usecase) *Model { f := &Model{ help: help.New(), header: "Категории", - categories: element.CreateDefaultList("Категории:", defaultWidth, listHeight, element.CategoryDelegate{}), - items: element.CreateDefaultList("Пароли:", defaultWidth, listHeight, element.ItemDelegate{}), + categories: elist.CreateDefaultList("Категории:", defaultWidth, listHeight, elist.CategoryDelegate{}), + items: elist.CreateDefaultList("Пароли:", defaultWidth, listHeight, elist.ItemDelegate{}), usecaseSync: usecaseSync, usecaseItem: usecaseItem, @@ -85,10 +85,10 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { f.state = stateCategoryList f.categories.SetItems(nil) - f.categories.InsertItem(len(f.categories.Items()), element.Category{ID: client.CategoryPassword, Title: client.CategoryPassword.String()}) - f.categories.InsertItem(len(f.categories.Items()), element.Category{ID: client.CategoryText, Title: client.CategoryText.String()}) - f.categories.InsertItem(len(f.categories.Items()), element.Category{ID: client.CategoryBlob, Title: client.CategoryBlob.String()}) - f.categories.InsertItem(len(f.categories.Items()), element.Category{ID: client.CategoryBankCard, Title: client.CategoryBankCard.String()}) + f.categories.InsertItem(len(f.categories.Items()), elist.Category{ID: client.CategoryPassword, Title: client.CategoryPassword.String()}) + f.categories.InsertItem(len(f.categories.Items()), elist.Category{ID: client.CategoryText, Title: client.CategoryText.String()}) + f.categories.InsertItem(len(f.categories.Items()), elist.Category{ID: client.CategoryBlob, Title: client.CategoryBlob.String()}) + f.categories.InsertItem(len(f.categories.Items()), elist.Category{ID: client.CategoryBankCard, Title: client.CategoryBankCard.String()}) return f, nil case OpenItemListMessage: @@ -100,15 +100,15 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return f, nil } - if c, ok := f.categories.SelectedItem().(element.Category); ok { + if c, ok := f.categories.SelectedItem().(elist.Category); ok { f.items.Title = c.Title + ":" } f.items.SetItems(nil) for _, p := range items { - f.items.InsertItem(len(f.categories.Items()), element.Item{ + f.items.InsertItem(len(f.categories.Items()), elist.Item{ GUID: p.GUID, - Category: p.CategoryID, + Category: p.Category, Title: p.Title, Value: p.Value, Notes: p.Notes, @@ -123,13 +123,13 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, common.Keys.Enter): switch f.state { case stateCategoryList: - if c, ok := f.categories.SelectedItem().(element.Category); ok { + if c, ok := f.categories.SelectedItem().(elist.Category); ok { return f.Update(OpenItemListMessage{ Category: c.ID, }) } case stateItemList: - if i, ok := f.items.SelectedItem().(element.Item); ok { + if i, ok := f.items.SelectedItem().(elist.Item); ok { return f.main.Update(common.OpenItemMessage{ BackModel: f, BackState: f.state, diff --git a/internal/cli/model/login/model.go b/internal/cli/model/login/model.go index 160a589..b650cb0 100644 --- a/internal/cli/model/login/model.go +++ b/internal/cli/model/login/model.go @@ -13,7 +13,7 @@ import ( "google.golang.org/grpc/status" "github.com/bjlag/go-keeper/internal/cli/common" - "github.com/bjlag/go-keeper/internal/cli/element" + "github.com/bjlag/go-keeper/internal/cli/element/button" tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" "github.com/bjlag/go-keeper/internal/cli/model/register" "github.com/bjlag/go-keeper/internal/cli/style" @@ -49,9 +49,9 @@ func InitModel(usecase *login.Usecase) *Model { elements: []interface{}{ posEmail: tinput.CreateDefaultTextInput("Email"), posPassword: tinput.CreateDefaultTextInput("Пароль"), - posSubmitBtn: element.CreateDefaultButton("Вход"), - posRegisterBtn: element.CreateDefaultButton("Регистрация"), - posCloseBtn: element.CreateDefaultButton("Закрыть"), + posSubmitBtn: button.CreateDefaultButton("Вход"), + posRegisterBtn: button.CreateDefaultButton("Регистрация"), + posCloseBtn: button.CreateDefaultButton("Закрыть"), }, usecase: usecase, @@ -126,7 +126,7 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { e.Blur() f.elements[i] = style.SetNoStyle(e) - case element.Button: + case button.Button: if i == f.pos { e.Focus() f.elements[i] = e @@ -175,7 +175,7 @@ func (f *Model) View() string { b.WriteRune('\n') for i := range f.elements { - if e, ok := f.elements[i].(element.Button); ok { + if e, ok := f.elements[i].(button.Button); ok { b.WriteString(e.String()) b.WriteRune('\n') } diff --git a/internal/cli/model/register/model.go b/internal/cli/model/register/model.go index fd2b995..3152588 100644 --- a/internal/cli/model/register/model.go +++ b/internal/cli/model/register/model.go @@ -13,7 +13,7 @@ import ( "google.golang.org/grpc/status" "github.com/bjlag/go-keeper/internal/cli/common" - "github.com/bjlag/go-keeper/internal/cli/element" + "github.com/bjlag/go-keeper/internal/cli/element/button" tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/infrastructure/validator" @@ -48,8 +48,8 @@ func InitModel(usecase *register.Usecase) *Model { elements: []interface{}{ posEmail: tinput.CreateDefaultTextInput("Email"), posPassword: tinput.CreateDefaultTextInput("Пароль"), - posSubmitBtn: element.CreateDefaultButton("Регистрация"), - posBackBtn: element.CreateDefaultButton("Назад"), + posSubmitBtn: button.CreateDefaultButton("Регистрация"), + posBackBtn: button.CreateDefaultButton("Назад"), }, usecase: usecase, } @@ -121,7 +121,7 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { e.Blur() f.elements[i] = style.SetNoStyle(e) - case element.Button: + case button.Button: if i == f.pos { e.Focus() f.elements[i] = e @@ -168,7 +168,7 @@ func (f *Model) View() string { b.WriteRune('\n') for i := range f.elements { - if e, ok := f.elements[i].(element.Button); ok { + if e, ok := f.elements[i].(button.Button); ok { b.WriteString(e.String()) b.WriteRune('\n') } diff --git a/internal/domain/client/item.go b/internal/domain/client/item.go index 509a784..f3f2a90 100644 --- a/internal/domain/client/item.go +++ b/internal/domain/client/item.go @@ -40,13 +40,13 @@ type RawItem struct { } type Item struct { - GUID uuid.UUID - CategoryID Category - Title string - Value interface{} - Notes string - CreatedAt time.Time - UpdatedAt time.Time + GUID uuid.UUID + Category Category + Title string + Value interface{} + Notes string + CreatedAt time.Time + UpdatedAt time.Time } type Password struct { diff --git a/internal/infrastructure/store/client/item/model.go b/internal/infrastructure/store/client/item/model.go index 57a30cb..da20142 100644 --- a/internal/infrastructure/store/client/item/model.go +++ b/internal/infrastructure/store/client/item/model.go @@ -1,6 +1,7 @@ package item import ( + "encoding/json" "time" "github.com/google/uuid" @@ -18,6 +19,23 @@ type row struct { UpdatedAt time.Time `db:"updated_at"` } +func toRow(model model.Item) (row, error) { + value, err := json.Marshal(model.Value) + if err != nil { + return row{}, err + } + + return row{ + GUID: model.GUID, + CategoryID: model.Category, + Title: model.Title, + Value: &value, + Notes: model.Notes, + CreatedAt: model.CreatedAt, + UpdatedAt: model.UpdatedAt, + }, nil +} + func toModels(rows []row) []model.RawItem { items := make([]model.RawItem, len(rows)) for i, r := range rows { diff --git a/internal/infrastructure/store/client/item/store.go b/internal/infrastructure/store/client/item/store.go index 415734f..7def592 100644 --- a/internal/infrastructure/store/client/item/store.go +++ b/internal/infrastructure/store/client/item/store.go @@ -23,6 +23,32 @@ func NewStore(db *sqlx.DB) *Store { } } +func (s *Store) SaveItem(ctx context.Context, item model.Item) error { + const op = prefixOp + "SaveItem" + + query := ` + UPDATE items + SET title = :title, + value = :value, + notes = :notes, + updated_at = datetime() + WHERE guid = :guid + ` + + r, err := toRow(item) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + _, err = s.db.NamedExecContext(ctx, query, r) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + return nil +} + +// todo пределать на модель model.Item func (s *Store) SaveItems(ctx context.Context, items []model.RawItem) error { const op = prefixOp + "SaveItems" diff --git a/internal/usecase/client/item/usecase.go b/internal/usecase/client/item/usecase.go index da4982b..a0ce672 100644 --- a/internal/usecase/client/item/usecase.go +++ b/internal/usecase/client/item/usecase.go @@ -53,13 +53,13 @@ func (u *Usecase) ItemsByCategory(ctx context.Context, category model.Category) } items[i] = model.Item{ - GUID: item.GUID, - CategoryID: item.CategoryID, - Title: item.Title, - Value: v, - Notes: item.Notes, - CreatedAt: item.CreatedAt, - UpdatedAt: item.UpdatedAt, + GUID: item.GUID, + Category: item.CategoryID, + Title: item.Title, + Value: v, + Notes: item.Notes, + CreatedAt: item.CreatedAt, + UpdatedAt: item.UpdatedAt, } } diff --git a/internal/usecase/client/sync/usecase.go b/internal/usecase/client/sync/usecase.go index fbd319e..877230b 100644 --- a/internal/usecase/client/sync/usecase.go +++ b/internal/usecase/client/sync/usecase.go @@ -38,10 +38,6 @@ func (d *Data) UnmarshalJSON(data []byte) error { if alias.Value != nil { value := []byte(*alias.Value) - - if alias.CategoryID == model.CategoryPassword { - - } d.Value = &value } From 4fcbb3a653bdf15117e118948c1c6053ab9b4765 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Fri, 7 Mar 2025 16:04:54 +0300 Subject: [PATCH 40/94] client: refactoring --- internal/app/client/app.go | 8 ++++---- internal/cli/model/list/model.go | 10 +++++----- internal/{usecase/client => fetcher}/item/contract.go | 0 .../client/item/usecase.go => fetcher/item/fetcher.go} | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) rename internal/{usecase/client => fetcher}/item/contract.go (100%) rename internal/{usecase/client/item/usecase.go => fetcher/item/fetcher.go} (89%) diff --git a/internal/app/client/app.go b/internal/app/client/app.go index f1b04ba..67503a5 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -3,7 +3,6 @@ package client import ( "context" "fmt" - tea "github.com/charmbracelet/bubbletea" "go.uber.org/zap" @@ -13,11 +12,11 @@ import ( formLogin "github.com/bjlag/go-keeper/internal/cli/model/login" "github.com/bjlag/go-keeper/internal/cli/model/master" formRegister "github.com/bjlag/go-keeper/internal/cli/model/register" + "github.com/bjlag/go-keeper/internal/fetcher/item" "github.com/bjlag/go-keeper/internal/infrastructure/db/sqlite" rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" sItem "github.com/bjlag/go-keeper/internal/infrastructure/store/client/item" "github.com/bjlag/go-keeper/internal/infrastructure/store/client/token" - item2 "github.com/bjlag/go-keeper/internal/usecase/client/item" "github.com/bjlag/go-keeper/internal/usecase/client/login" "github.com/bjlag/go-keeper/internal/usecase/client/register" "github.com/bjlag/go-keeper/internal/usecase/client/sync" @@ -62,14 +61,15 @@ func (a *App) Run(ctx context.Context) error { ucLogin := login.NewUsecase(rpcClient) ucRegister := register.NewUsecase(rpcClient) ucSync := sync.NewUsecase(rpcClient, storeItem) - ucItem := item2.NewUsecase(storeItem) + + fItem := item.NewFetcher(storeItem) m := master.InitModel( master.WithStoreTokens(storeTokens), master.WithLoginForm(formLogin.InitModel(ucLogin)), master.WithRegisterForm(formRegister.InitModel(ucRegister)), - master.WithListFormForm(list.InitModel(ucSync, ucItem)), + master.WithListFormForm(list.InitModel(ucSync, fItem)), master.WithPasswordItemForm(password.InitModel(storeItem)), master.WithTextItemForm(text.InitModel()), ) diff --git a/internal/cli/model/list/model.go b/internal/cli/model/list/model.go index 69d0828..43cce7d 100644 --- a/internal/cli/model/list/model.go +++ b/internal/cli/model/list/model.go @@ -13,7 +13,7 @@ import ( elist "github.com/bjlag/go-keeper/internal/cli/element/list" "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/domain/client" - "github.com/bjlag/go-keeper/internal/usecase/client/item" + "github.com/bjlag/go-keeper/internal/fetcher/item" "github.com/bjlag/go-keeper/internal/usecase/client/sync" ) @@ -37,10 +37,10 @@ type Model struct { err error usecaseSync *sync.Usecase - usecaseItem *item.Usecase + fetcherItem *item.Fetcher } -func InitModel(usecaseSync *sync.Usecase, usecaseItem *item.Usecase) *Model { +func InitModel(usecaseSync *sync.Usecase, fetcherItem *item.Fetcher) *Model { f := &Model{ help: help.New(), header: "Категории", @@ -48,7 +48,7 @@ func InitModel(usecaseSync *sync.Usecase, usecaseItem *item.Usecase) *Model { items: elist.CreateDefaultList("Пароли:", defaultWidth, listHeight, elist.ItemDelegate{}), usecaseSync: usecaseSync, - usecaseItem: usecaseItem, + fetcherItem: fetcherItem, } return f @@ -94,7 +94,7 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case OpenItemListMessage: f.state = stateItemList - items, err := f.usecaseItem.ItemsByCategory(context.TODO(), msg.Category) + items, err := f.fetcherItem.ItemsByCategory(context.TODO(), msg.Category) if err != nil { f.err = err return f, nil diff --git a/internal/usecase/client/item/contract.go b/internal/fetcher/item/contract.go similarity index 100% rename from internal/usecase/client/item/contract.go rename to internal/fetcher/item/contract.go diff --git a/internal/usecase/client/item/usecase.go b/internal/fetcher/item/fetcher.go similarity index 89% rename from internal/usecase/client/item/usecase.go rename to internal/fetcher/item/fetcher.go index a0ce672..04aab9e 100644 --- a/internal/usecase/client/item/usecase.go +++ b/internal/fetcher/item/fetcher.go @@ -13,17 +13,17 @@ const prefixOp = "usecase.item" var ErrUnknownCategory = errors.New("unknown category") -type Usecase struct { +type Fetcher struct { itemStore itemStore } -func NewUsecase(itemStore itemStore) *Usecase { - return &Usecase{ +func NewFetcher(itemStore itemStore) *Fetcher { + return &Fetcher{ itemStore: itemStore, } } -func (u *Usecase) ItemsByCategory(ctx context.Context, category model.Category) ([]model.Item, error) { +func (u *Fetcher) ItemsByCategory(ctx context.Context, category model.Category) ([]model.Item, error) { const op = prefixOp + ".ItemsByCategory" rawItems, err := u.itemStore.ItemsByCategory(ctx, category) From df4b96ef8a458d1e5f39d3343d7d65ddea59ba60 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Fri, 7 Mar 2025 16:19:32 +0300 Subject: [PATCH 41/94] client: save item usecase --- internal/app/client/app.go | 9 ++++-- internal/cli/model/item/password/model.go | 10 +++---- internal/usecase/client/item/save/contract.go | 11 +++++++ internal/usecase/client/item/save/usecase.go | 29 +++++++++++++++++++ 4 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 internal/usecase/client/item/save/contract.go create mode 100644 internal/usecase/client/item/save/usecase.go diff --git a/internal/app/client/app.go b/internal/app/client/app.go index 67503a5..d895c0d 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -3,6 +3,7 @@ package client import ( "context" "fmt" + tea "github.com/charmbracelet/bubbletea" "go.uber.org/zap" @@ -17,6 +18,7 @@ import ( rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" sItem "github.com/bjlag/go-keeper/internal/infrastructure/store/client/item" "github.com/bjlag/go-keeper/internal/infrastructure/store/client/token" + "github.com/bjlag/go-keeper/internal/usecase/client/item/save" "github.com/bjlag/go-keeper/internal/usecase/client/login" "github.com/bjlag/go-keeper/internal/usecase/client/register" "github.com/bjlag/go-keeper/internal/usecase/client/sync" @@ -61,16 +63,17 @@ func (a *App) Run(ctx context.Context) error { ucLogin := login.NewUsecase(rpcClient) ucRegister := register.NewUsecase(rpcClient) ucSync := sync.NewUsecase(rpcClient, storeItem) + ucSaveItem := save.NewUsecase(storeItem) - fItem := item.NewFetcher(storeItem) + fetchItem := item.NewFetcher(storeItem) m := master.InitModel( master.WithStoreTokens(storeTokens), master.WithLoginForm(formLogin.InitModel(ucLogin)), master.WithRegisterForm(formRegister.InitModel(ucRegister)), - master.WithListFormForm(list.InitModel(ucSync, fItem)), - master.WithPasswordItemForm(password.InitModel(storeItem)), + master.WithListFormForm(list.InitModel(ucSync, fetchItem)), + master.WithPasswordItemForm(password.InitModel(ucSaveItem)), master.WithTextItemForm(text.InitModel()), ) diff --git a/internal/cli/model/item/password/model.go b/internal/cli/model/item/password/model.go index 4078407..a46f0b8 100644 --- a/internal/cli/model/item/password/model.go +++ b/internal/cli/model/item/password/model.go @@ -20,7 +20,7 @@ import ( "github.com/bjlag/go-keeper/internal/cli/element/button" tarea "github.com/bjlag/go-keeper/internal/cli/element/textarea" tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" - "github.com/bjlag/go-keeper/internal/infrastructure/store/client/item" + "github.com/bjlag/go-keeper/internal/usecase/client/item/save" ) const ( @@ -47,15 +47,15 @@ type Model struct { guid uuid.UUID category client.Category - storeItem *item.Store + usecaseSave *save.Usecase } -func InitModel(storeItem *item.Store) *Model { +func InitModel(usecaseItem *save.Usecase) *Model { f := &Model{ help: help.New(), header: "Регистрация", - storeItem: storeItem, + usecaseSave: usecaseItem, } return f @@ -266,5 +266,5 @@ func (f *Model) edit() error { Notes: element.GetValue(f.elements, posNotes), } - return f.storeItem.SaveItem(context.TODO(), i) + return f.usecaseSave.Do(context.TODO(), i) } diff --git a/internal/usecase/client/item/save/contract.go b/internal/usecase/client/item/save/contract.go new file mode 100644 index 0000000..1b526b4 --- /dev/null +++ b/internal/usecase/client/item/save/contract.go @@ -0,0 +1,11 @@ +package save + +import ( + "context" + + model "github.com/bjlag/go-keeper/internal/domain/client" +) + +type store interface { + SaveItem(ctx context.Context, item model.Item) error +} diff --git a/internal/usecase/client/item/save/usecase.go b/internal/usecase/client/item/save/usecase.go new file mode 100644 index 0000000..958f9c3 --- /dev/null +++ b/internal/usecase/client/item/save/usecase.go @@ -0,0 +1,29 @@ +package save + +import ( + "context" + "fmt" + + model "github.com/bjlag/go-keeper/internal/domain/client" +) + +type Usecase struct { + store store +} + +func NewUsecase(store store) *Usecase { + return &Usecase{ + store: store, + } +} + +func (u *Usecase) Do(ctx context.Context, item model.Item) error { + const op = "usecase.saveItem.Do" + + err := u.store.SaveItem(ctx, item) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + return nil +} From c0bd12cd8dbef846efb2fa9d1bb1322545d7033d Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Fri, 7 Mar 2025 16:25:16 +0300 Subject: [PATCH 42/94] server: without response in server logs --- internal/infrastructure/rpc/interceptor/logger.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/infrastructure/rpc/interceptor/logger.go b/internal/infrastructure/rpc/interceptor/logger.go index 755313f..4165651 100644 --- a/internal/infrastructure/rpc/interceptor/logger.go +++ b/internal/infrastructure/rpc/interceptor/logger.go @@ -20,7 +20,6 @@ func LoggerServerInterceptor(log *zap.Logger) grpc.UnaryServerInterceptor { resp, err := handler(logger.WithCtx(ctx, hLog), req) hLog.Info("Got RPC request", - zap.Any("response", resp), zap.Error(err), zap.String("code", status.Code(err).String()), ) From b56c07bd63f38f4d1f52f8bd2c897fcf47466a6f Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Fri, 7 Mar 2025 16:25:32 +0300 Subject: [PATCH 43/94] client: limit --- internal/usecase/client/sync/usecase.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/usecase/client/sync/usecase.go b/internal/usecase/client/sync/usecase.go index 877230b..7680cbb 100644 --- a/internal/usecase/client/sync/usecase.go +++ b/internal/usecase/client/sync/usecase.go @@ -12,7 +12,7 @@ import ( const ( prefixOp = "usecase.sync." - limit = 1 + limit = 40 ) type Data struct { From 093c7f909d208c37845ea9f82ee83887169428f6 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Fri, 7 Mar 2025 16:30:15 +0300 Subject: [PATCH 44/94] client: save text item --- internal/app/client/app.go | 2 +- internal/cli/element/list/list.go | 4 +- internal/cli/model/item/password/model.go | 9 +-- internal/cli/model/item/text/model.go | 62 ++++++++++--------- internal/cli/model/list/model.go | 28 +++++---- internal/cli/model/master/model.go | 4 -- internal/domain/client/item.go | 14 ++--- internal/fetcher/item/fetcher.go | 8 ++- .../infrastructure/store/client/item/model.go | 14 ++--- .../infrastructure/store/client/item/store.go | 2 +- internal/usecase/client/sync/usecase.go | 14 ++--- 11 files changed, 81 insertions(+), 80 deletions(-) diff --git a/internal/app/client/app.go b/internal/app/client/app.go index d895c0d..2542906 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -74,7 +74,7 @@ func (a *App) Run(ctx context.Context) error { master.WithRegisterForm(formRegister.InitModel(ucRegister)), master.WithListFormForm(list.InitModel(ucSync, fetchItem)), master.WithPasswordItemForm(password.InitModel(ucSaveItem)), - master.WithTextItemForm(text.InitModel()), + master.WithTextItemForm(text.InitModel(ucSaveItem)), ) f, err := tea.LogToFile("debug.log", "debug") diff --git a/internal/cli/element/list/list.go b/internal/cli/element/list/list.go index 2fbc406..737d186 100644 --- a/internal/cli/element/list/list.go +++ b/internal/cli/element/list/list.go @@ -27,8 +27,8 @@ func CreateDefaultList(title string, with, height int, itemDelegate list.ItemDel } type Category struct { - ID client.Category - Title string + Category client.Category + Title string } func (i Category) FilterValue() string { return "" } diff --git a/internal/cli/model/item/password/model.go b/internal/cli/model/item/password/model.go index a46f0b8..c5bebde 100644 --- a/internal/cli/model/item/password/model.go +++ b/internal/cli/model/item/password/model.go @@ -51,14 +51,11 @@ type Model struct { } func InitModel(usecaseItem *save.Usecase) *Model { - f := &Model{ - help: help.New(), - header: "Регистрация", - + return &Model{ + help: help.New(), + header: "Регистрация", usecaseSave: usecaseItem, } - - return f } func (f *Model) SetMainModel(m tea.Model) { diff --git a/internal/cli/model/item/text/model.go b/internal/cli/model/item/text/model.go index f2b6f08..c241139 100644 --- a/internal/cli/model/item/text/model.go +++ b/internal/cli/model/item/text/model.go @@ -1,7 +1,9 @@ package text import ( + "context" "errors" + "github.com/google/uuid" "strings" "github.com/charmbracelet/bubbles/help" @@ -10,10 +12,13 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/bjlag/go-keeper/internal/cli/common" + "github.com/bjlag/go-keeper/internal/cli/element" "github.com/bjlag/go-keeper/internal/cli/element/button" tarea "github.com/bjlag/go-keeper/internal/cli/element/textarea" tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" "github.com/bjlag/go-keeper/internal/cli/style" + "github.com/bjlag/go-keeper/internal/domain/client" + "github.com/bjlag/go-keeper/internal/usecase/client/item/save" "github.com/charmbracelet/bubbles/textarea" ) @@ -36,18 +41,18 @@ type Model struct { backModel tea.Model backState int - category string + guid uuid.UUID + category client.Category + + usecaseSave *save.Usecase } -func InitModel() *Model { - f := &Model{ - help: help.New(), - header: "Регистрация", - //elements: make([]interface{}, 5), - //usecase: usecase, +func InitModel(usecaseItem *save.Usecase) *Model { + return &Model{ + help: help.New(), + header: "Регистрация", + usecaseSave: usecaseItem, } - - return f } func (f *Model) SetMainModel(m tea.Model) { @@ -72,7 +77,8 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { f.backState = msg.BackState f.backModel = msg.BackModel f.header = msg.Item.Title - f.category = msg.Item.Category.String() + f.guid = msg.Item.GUID + f.category = msg.Item.Category f.elements = []interface{}{ posTitle: tinput.CreateDefaultTextInput("Название", tinput.WithValue(msg.Item.Title), tinput.WithFocused()), @@ -82,22 +88,6 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { posBackBtn: button.CreateDefaultButton("Назад"), } - //for i, e := range f.elements { - // switch input := e.(type) { - // case textinput.Model: - // switch i { - // case posTitle: - // input.SetValue(msg.Item.Title) - // f.elements[i] = input - // default: - // continue - // } - // case textarea.Model: - // input.SetValue(msg.Item.Notes) - // f.elements[i] = input - // } - //} - return f, nil case tea.KeyMsg: switch { @@ -151,8 +141,11 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, common.Keys.Enter): f.err = nil - switch { - case f.pos == posBackBtn: + switch f.pos { + case posEditBtn: + f.err = f.edit() + return f, nil + case posBackBtn: return f.backModel.Update(common.BackMessage{ State: f.backState, }) @@ -176,7 +169,7 @@ func (f *Model) View() string { b.WriteRune('\n') b.WriteString("Категория: ") - b.WriteString(f.category) + b.WriteString(f.category.String()) b.WriteRune('\n') for i := range f.elements { @@ -242,3 +235,14 @@ func (f *Model) updateInputs(msg tea.Msg) tea.Cmd { return tea.Batch(cmds...) } + +func (f *Model) edit() error { + i := client.Item{ + GUID: f.guid, + Category: f.category, + Title: element.GetValue(f.elements, posTitle), + Notes: element.GetValue(f.elements, posNotes), + } + + return f.usecaseSave.Do(context.TODO(), i) +} diff --git a/internal/cli/model/list/model.go b/internal/cli/model/list/model.go index 43cce7d..7f774b7 100644 --- a/internal/cli/model/list/model.go +++ b/internal/cli/model/list/model.go @@ -36,6 +36,8 @@ type Model struct { items list.Model err error + selectedCategory client.Category + usecaseSync *sync.Usecase fetcherItem *item.Fetcher } @@ -85,25 +87,25 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { f.state = stateCategoryList f.categories.SetItems(nil) - f.categories.InsertItem(len(f.categories.Items()), elist.Category{ID: client.CategoryPassword, Title: client.CategoryPassword.String()}) - f.categories.InsertItem(len(f.categories.Items()), elist.Category{ID: client.CategoryText, Title: client.CategoryText.String()}) - f.categories.InsertItem(len(f.categories.Items()), elist.Category{ID: client.CategoryBlob, Title: client.CategoryBlob.String()}) - f.categories.InsertItem(len(f.categories.Items()), elist.Category{ID: client.CategoryBankCard, Title: client.CategoryBankCard.String()}) + f.categories.InsertItem(len(f.categories.Items()), elist.Category{Category: client.CategoryPassword, Title: client.CategoryPassword.String()}) + f.categories.InsertItem(len(f.categories.Items()), elist.Category{Category: client.CategoryText, Title: client.CategoryText.String()}) + f.categories.InsertItem(len(f.categories.Items()), elist.Category{Category: client.CategoryBlob, Title: client.CategoryBlob.String()}) + f.categories.InsertItem(len(f.categories.Items()), elist.Category{Category: client.CategoryBankCard, Title: client.CategoryBankCard.String()}) return f, nil case OpenItemListMessage: f.state = stateItemList - items, err := f.fetcherItem.ItemsByCategory(context.TODO(), msg.Category) + if c, ok := f.categories.SelectedItem().(elist.Category); ok { + f.items.Title = c.Title + ":" + } + + items, err := f.fetcherItem.ItemsByCategory(context.TODO(), f.selectedCategory) if err != nil { f.err = err return f, nil } - if c, ok := f.categories.SelectedItem().(elist.Category); ok { - f.items.Title = c.Title + ":" - } - f.items.SetItems(nil) for _, p := range items { f.items.InsertItem(len(f.categories.Items()), elist.Item{ @@ -124,12 +126,15 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch f.state { case stateCategoryList: if c, ok := f.categories.SelectedItem().(elist.Category); ok { + f.selectedCategory = c.Category return f.Update(OpenItemListMessage{ - Category: c.ID, + Category: c.Category, }) } case stateItemList: if i, ok := f.items.SelectedItem().(elist.Item); ok { + f.selectedCategory = i.Category + return f.main.Update(common.OpenItemMessage{ BackModel: f, BackState: f.state, @@ -171,9 +176,6 @@ func (f *Model) View() string { b.WriteString(f.items.View()) } - //b.WriteRune('\n') - //b.WriteString(f.help.View(common.Keys)) - // выводим прочие ошибки if f.err != nil { b.WriteRune('\n') diff --git a/internal/cli/model/master/model.go b/internal/cli/model/master/model.go index f673876..955ed26 100644 --- a/internal/cli/model/master/model.go +++ b/internal/cli/model/master/model.go @@ -70,10 +70,6 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m.formList.Update(msg) case listf.OpenItemListMessage: return m.formList.Update(msg) - //case password.OpenMessage: - // return m.formPassword.Update(msg) - //case text.OpenMessage: - // return m.formText.Update(msg) case common.OpenItemMessage: switch msg.Item.Category { case client.CategoryPassword: diff --git a/internal/domain/client/item.go b/internal/domain/client/item.go index f3f2a90..c7a1611 100644 --- a/internal/domain/client/item.go +++ b/internal/domain/client/item.go @@ -30,13 +30,13 @@ func (c Category) String() string { } type RawItem struct { - GUID uuid.UUID - CategoryID Category - Title string - Value *[]byte - Notes string - CreatedAt time.Time - UpdatedAt time.Time + GUID uuid.UUID + Category Category + Title string + Value *[]byte + Notes string + CreatedAt time.Time + UpdatedAt time.Time } type Item struct { diff --git a/internal/fetcher/item/fetcher.go b/internal/fetcher/item/fetcher.go index 04aab9e..43dc032 100644 --- a/internal/fetcher/item/fetcher.go +++ b/internal/fetcher/item/fetcher.go @@ -35,15 +35,17 @@ func (u *Fetcher) ItemsByCategory(ctx context.Context, category model.Category) for i, item := range rawItems { var v interface{} if item.Value != nil { - switch item.CategoryID { + switch item.Category { case model.CategoryPassword: v = &model.Password{} + case model.CategoryText: + break case model.CategoryBlob: v = &model.Blob{} case model.CategoryBankCard: v = &model.BankCard{} default: - return nil, fmt.Errorf("%w: %d", ErrUnknownCategory, item.CategoryID) + return nil, fmt.Errorf("%w: %d", ErrUnknownCategory, item.Category) } err = json.Unmarshal(*item.Value, &v) @@ -54,7 +56,7 @@ func (u *Fetcher) ItemsByCategory(ctx context.Context, category model.Category) items[i] = model.Item{ GUID: item.GUID, - Category: item.CategoryID, + Category: item.Category, Title: item.Title, Value: v, Notes: item.Notes, diff --git a/internal/infrastructure/store/client/item/model.go b/internal/infrastructure/store/client/item/model.go index da20142..7d4f4d9 100644 --- a/internal/infrastructure/store/client/item/model.go +++ b/internal/infrastructure/store/client/item/model.go @@ -46,12 +46,12 @@ func toModels(rows []row) []model.RawItem { func (r *row) toModel() model.RawItem { return model.RawItem{ - GUID: r.GUID, - CategoryID: r.CategoryID, - Title: r.Title, - Value: r.Value, - Notes: r.Notes, - CreatedAt: r.CreatedAt, - UpdatedAt: r.UpdatedAt, + GUID: r.GUID, + Category: r.CategoryID, + Title: r.Title, + Value: r.Value, + Notes: r.Notes, + CreatedAt: r.CreatedAt, + UpdatedAt: r.UpdatedAt, } } diff --git a/internal/infrastructure/store/client/item/store.go b/internal/infrastructure/store/client/item/store.go index 7def592..248393a 100644 --- a/internal/infrastructure/store/client/item/store.go +++ b/internal/infrastructure/store/client/item/store.go @@ -71,7 +71,7 @@ func (s *Store) SaveItems(ctx context.Context, items []model.RawItem) error { updated_at = excluded.updated_at; ` - _, err := tx.ExecContext(ctx, query, i.GUID, i.CategoryID, i.Title, i.Value, i.Notes, i.CreatedAt, i.UpdatedAt) + _, err := tx.ExecContext(ctx, query, i.GUID, i.Category, i.Title, i.Value, i.Notes, i.CreatedAt, i.UpdatedAt) if err != nil { return fmt.Errorf("%s: %w", op, err) } diff --git a/internal/usecase/client/sync/usecase.go b/internal/usecase/client/sync/usecase.go index 7680cbb..8f13054 100644 --- a/internal/usecase/client/sync/usecase.go +++ b/internal/usecase/client/sync/usecase.go @@ -86,13 +86,13 @@ func (u *Usecase) Do(ctx context.Context) error { } items = append(items, model.RawItem{ - GUID: item.GUID, - CategoryID: data.CategoryID, - Title: data.Title, - Value: data.Value, - Notes: data.Notes, - CreatedAt: item.CreatedAt, - UpdatedAt: item.UpdatedAt, + GUID: item.GUID, + Category: data.CategoryID, + Title: data.Title, + Value: data.Value, + Notes: data.Notes, + CreatedAt: item.CreatedAt, + UpdatedAt: item.UpdatedAt, }) } From e3f7c2e33199fadda9d7257a765980040d579d6e Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Fri, 7 Mar 2025 19:06:04 +0300 Subject: [PATCH 45/94] server: rpc update item --- internal/app/server/app.go | 2 + internal/domain/data/item.go | 5 + internal/generated/rpc/keeper.pb.go | 233 +++++++++++------- internal/generated/rpc/keeper_grpc.pb.go | 39 +++ internal/infrastructure/rpc/server/method.go | 16 ++ .../infrastructure/store/server/item/model.go | 13 +- .../infrastructure/store/server/item/store.go | 37 ++- internal/rpc/update_item/contract.go | 11 + internal/rpc/update_item/handler.go | 61 +++++ .../usecase/server/data/update/contract.go | 13 + internal/usecase/server/data/update/model.go | 11 + .../usecase/server/data/update/usecase.go | 42 ++++ proto/keeper.proto | 9 +- 13 files changed, 404 insertions(+), 88 deletions(-) create mode 100644 internal/rpc/update_item/contract.go create mode 100644 internal/rpc/update_item/handler.go create mode 100644 internal/usecase/server/data/update/contract.go create mode 100644 internal/usecase/server/data/update/model.go create mode 100644 internal/usecase/server/data/update/usecase.go diff --git a/internal/app/server/app.go b/internal/app/server/app.go index 9c6d6f8..af79148 100644 --- a/internal/app/server/app.go +++ b/internal/app/server/app.go @@ -15,6 +15,7 @@ import ( rpcLogin "github.com/bjlag/go-keeper/internal/rpc/login" rpcRefreshTokens "github.com/bjlag/go-keeper/internal/rpc/refresh_tokens" rpcRegister "github.com/bjlag/go-keeper/internal/rpc/register" + rpcUpdateItem "github.com/bjlag/go-keeper/internal/rpc/update_item" "github.com/bjlag/go-keeper/internal/usecase/server/data/get_all" "github.com/bjlag/go-keeper/internal/usecase/server/user/login" rt "github.com/bjlag/go-keeper/internal/usecase/server/user/refresh_tokens" @@ -64,6 +65,7 @@ func (a *App) Run(ctx context.Context) error { server.WithHandler(server.LoginMethod, rpcLogin.New(ucLogin).Handle), server.WithHandler(server.RefreshTokensMethod, rpcRefreshTokens.New(ucRefreshTokens).Handle), server.WithHandler(server.GetAllItemsMethod, rpcGetAllItems.New(ucGetAllData).Handle), + server.WithHandler(server.UpdateItemMethod, rpcUpdateItem.New(ucGetAllData).Handle), ) err = s.Start(ctx) diff --git a/internal/domain/data/item.go b/internal/domain/data/item.go index 7004725..1b4029d 100644 --- a/internal/domain/data/item.go +++ b/internal/domain/data/item.go @@ -13,3 +13,8 @@ type Item struct { CreatedAt time.Time UpdatedAt time.Time } + +type UpdatedItem struct { + EncryptedData []byte + UpdatedAt time.Time +} diff --git a/internal/generated/rpc/keeper.pb.go b/internal/generated/rpc/keeper.pb.go index 2481325..a6817d4 100644 --- a/internal/generated/rpc/keeper.pb.go +++ b/internal/generated/rpc/keeper.pb.go @@ -9,6 +9,7 @@ package rpc 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" @@ -492,77 +493,139 @@ func (x *Item) GetUpdatedAt() *timestamppb.Timestamp { return nil } +type UpdateItemIn struct { + state protoimpl.MessageState `protogen:"open.v1"` + Guid string `protobuf:"bytes,1,opt,name=guid,proto3" json:"guid,omitempty"` + EncryptedData []byte `protobuf:"bytes,2,opt,name=encryptedData,proto3" json:"encryptedData,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateItemIn) Reset() { + *x = UpdateItemIn{} + mi := &file_proto_keeper_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateItemIn) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateItemIn) ProtoMessage() {} + +func (x *UpdateItemIn) ProtoReflect() protoreflect.Message { + mi := &file_proto_keeper_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateItemIn.ProtoReflect.Descriptor instead. +func (*UpdateItemIn) Descriptor() ([]byte, []int) { + return file_proto_keeper_proto_rawDescGZIP(), []int{9} +} + +func (x *UpdateItemIn) GetGuid() string { + if x != nil { + return x.Guid + } + return "" +} + +func (x *UpdateItemIn) GetEncryptedData() []byte { + if x != nil { + return x.EncryptedData + } + return nil +} + var File_proto_keeper_proto protoreflect.FileDescriptor var file_proto_keeper_proto_rawDesc = string([]byte{ 0x0a, 0x12, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 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, 0x3e, 0x0a, - 0x0a, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x65, - 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, - 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x55, 0x0a, - 0x0b, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x12, 0x21, 0x0a, 0x0c, - 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, - 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x3b, 0x0a, 0x07, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x12, - 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, - 0x64, 0x22, 0x52, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x4f, 0x75, 0x74, 0x12, 0x21, 0x0a, - 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, - 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x36, 0x0a, 0x0f, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x49, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, - 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x5a, 0x0a, - 0x10, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x4f, 0x75, - 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, - 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, - 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, - 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x3d, 0x0a, 0x0d, 0x47, 0x65, 0x74, - 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x49, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, - 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, - 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x22, 0x34, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x41, - 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x4f, 0x75, 0x74, 0x12, 0x22, 0x0a, 0x05, 0x69, 0x74, - 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6b, 0x65, 0x65, 0x70, - 0x65, 0x72, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0xb4, - 0x01, 0x0a, 0x04, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x65, - 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, - 0x61, 0x12, 0x38, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x03, - 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, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 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, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x64, 0x41, 0x74, 0x32, 0xeb, 0x01, 0x0a, 0x06, 0x4b, 0x65, 0x65, 0x70, 0x65, 0x72, - 0x12, 0x33, 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x12, 0x2e, 0x6b, - 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, - 0x1a, 0x13, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, - 0x65, 0x72, 0x4f, 0x75, 0x74, 0x12, 0x2a, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x0f, - 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x1a, - 0x10, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x4f, 0x75, - 0x74, 0x12, 0x42, 0x0a, 0x0d, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x73, 0x12, 0x17, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, - 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x49, 0x6e, 0x1a, 0x18, 0x2e, 0x6b, 0x65, - 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x73, 0x4f, 0x75, 0x74, 0x12, 0x3c, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, - 0x74, 0x65, 0x6d, 0x73, 0x12, 0x15, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x47, 0x65, - 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x49, 0x6e, 0x1a, 0x16, 0x2e, 0x6b, 0x65, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 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, 0x22, 0x3e, 0x0a, 0x0a, 0x52, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, + 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1a, + 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x55, 0x0a, 0x0b, 0x52, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x0a, 0x0d, + 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x22, 0x3b, 0x0a, 0x07, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x12, 0x14, 0x0a, 0x05, + 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, + 0x69, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x52, + 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x4f, 0x75, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x0a, + 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x22, 0x36, 0x0a, 0x0f, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x73, 0x49, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, + 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, + 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x5a, 0x0a, 0x10, 0x52, 0x65, + 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x4f, 0x75, 0x74, 0x12, 0x21, + 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, + 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, + 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x3d, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, + 0x49, 0x74, 0x65, 0x6d, 0x73, 0x49, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, + 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6f, + 0x66, 0x66, 0x73, 0x65, 0x74, 0x22, 0x34, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, + 0x74, 0x65, 0x6d, 0x73, 0x4f, 0x75, 0x74, 0x12, 0x22, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, + 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0xb4, 0x01, 0x0a, 0x04, + 0x49, 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x65, 0x6e, 0x63, 0x72, + 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x12, 0x38, + 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x03, 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, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x64, 0x41, 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, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, + 0x41, 0x74, 0x22, 0x48, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, + 0x49, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, + 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x65, + 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x32, 0xa7, 0x02, 0x0a, + 0x06, 0x4b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x12, 0x33, 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x65, 0x72, 0x12, 0x12, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x1a, 0x13, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, + 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x12, 0x2a, 0x0a, 0x05, + 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x0f, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, + 0x6f, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x1a, 0x10, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, + 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x4f, 0x75, 0x74, 0x12, 0x42, 0x0a, 0x0d, 0x52, 0x65, 0x66, 0x72, + 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x17, 0x2e, 0x6b, 0x65, 0x65, 0x70, + 0x65, 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, + 0x49, 0x6e, 0x1a, 0x18, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, + 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x4f, 0x75, 0x74, 0x12, 0x3c, 0x0a, 0x0b, + 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x15, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, - 0x4f, 0x75, 0x74, 0x42, 0x18, 0x5a, 0x16, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, - 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x49, 0x6e, 0x1a, 0x16, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x41, + 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x4f, 0x75, 0x74, 0x12, 0x3a, 0x0a, 0x0a, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x14, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, + 0x72, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x1a, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x18, 0x5a, 0x16, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x72, 0x70, 0x63, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -577,7 +640,7 @@ func file_proto_keeper_proto_rawDescGZIP() []byte { return file_proto_keeper_proto_rawDescData } -var file_proto_keeper_proto_msgTypes = make([]protoimpl.MessageInfo, 9) +var file_proto_keeper_proto_msgTypes = make([]protoimpl.MessageInfo, 10) var file_proto_keeper_proto_goTypes = []any{ (*RegisterIn)(nil), // 0: keeper.RegisterIn (*RegisterOut)(nil), // 1: keeper.RegisterOut @@ -588,25 +651,29 @@ var file_proto_keeper_proto_goTypes = []any{ (*GetAllItemsIn)(nil), // 6: keeper.GetAllItemsIn (*GetAllItemsOut)(nil), // 7: keeper.GetAllItemsOut (*Item)(nil), // 8: keeper.Item - (*timestamppb.Timestamp)(nil), // 9: google.protobuf.Timestamp + (*UpdateItemIn)(nil), // 9: keeper.UpdateItemIn + (*timestamppb.Timestamp)(nil), // 10: google.protobuf.Timestamp + (*emptypb.Empty)(nil), // 11: google.protobuf.Empty } var file_proto_keeper_proto_depIdxs = []int32{ - 8, // 0: keeper.GetAllItemsOut.items:type_name -> keeper.Item - 9, // 1: keeper.Item.CreatedAt:type_name -> google.protobuf.Timestamp - 9, // 2: keeper.Item.UpdatedAt:type_name -> google.protobuf.Timestamp - 0, // 3: keeper.Keeper.Register:input_type -> keeper.RegisterIn - 2, // 4: keeper.Keeper.Login:input_type -> keeper.LoginIn - 4, // 5: keeper.Keeper.RefreshTokens:input_type -> keeper.RefreshTokensIn - 6, // 6: keeper.Keeper.GetAllItems:input_type -> keeper.GetAllItemsIn - 1, // 7: keeper.Keeper.Register:output_type -> keeper.RegisterOut - 3, // 8: keeper.Keeper.Login:output_type -> keeper.LoginOut - 5, // 9: keeper.Keeper.RefreshTokens:output_type -> keeper.RefreshTokensOut - 7, // 10: keeper.Keeper.GetAllItems:output_type -> keeper.GetAllItemsOut - 7, // [7:11] is the sub-list for method output_type - 3, // [3:7] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 8, // 0: keeper.GetAllItemsOut.items:type_name -> keeper.Item + 10, // 1: keeper.Item.CreatedAt:type_name -> google.protobuf.Timestamp + 10, // 2: keeper.Item.UpdatedAt:type_name -> google.protobuf.Timestamp + 0, // 3: keeper.Keeper.Register:input_type -> keeper.RegisterIn + 2, // 4: keeper.Keeper.Login:input_type -> keeper.LoginIn + 4, // 5: keeper.Keeper.RefreshTokens:input_type -> keeper.RefreshTokensIn + 6, // 6: keeper.Keeper.GetAllItems:input_type -> keeper.GetAllItemsIn + 9, // 7: keeper.Keeper.UpdateItem:input_type -> keeper.UpdateItemIn + 1, // 8: keeper.Keeper.Register:output_type -> keeper.RegisterOut + 3, // 9: keeper.Keeper.Login:output_type -> keeper.LoginOut + 5, // 10: keeper.Keeper.RefreshTokens:output_type -> keeper.RefreshTokensOut + 7, // 11: keeper.Keeper.GetAllItems:output_type -> keeper.GetAllItemsOut + 11, // 12: keeper.Keeper.UpdateItem:output_type -> google.protobuf.Empty + 8, // [8:13] is the sub-list for method output_type + 3, // [3:8] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name } func init() { file_proto_keeper_proto_init() } @@ -620,7 +687,7 @@ func file_proto_keeper_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_keeper_proto_rawDesc), len(file_proto_keeper_proto_rawDesc)), NumEnums: 0, - NumMessages: 9, + NumMessages: 10, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/generated/rpc/keeper_grpc.pb.go b/internal/generated/rpc/keeper_grpc.pb.go index 7b284f9..71bf98e 100644 --- a/internal/generated/rpc/keeper_grpc.pb.go +++ b/internal/generated/rpc/keeper_grpc.pb.go @@ -11,6 +11,7 @@ import ( 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 @@ -23,6 +24,7 @@ const ( Keeper_Login_FullMethodName = "/keeper.Keeper/Login" Keeper_RefreshTokens_FullMethodName = "/keeper.Keeper/RefreshTokens" Keeper_GetAllItems_FullMethodName = "/keeper.Keeper/GetAllItems" + Keeper_UpdateItem_FullMethodName = "/keeper.Keeper/UpdateItem" ) // KeeperClient is the client API for Keeper service. @@ -35,6 +37,7 @@ type KeeperClient interface { RefreshTokens(ctx context.Context, in *RefreshTokensIn, opts ...grpc.CallOption) (*RefreshTokensOut, error) // data GetAllItems(ctx context.Context, in *GetAllItemsIn, opts ...grpc.CallOption) (*GetAllItemsOut, error) + UpdateItem(ctx context.Context, in *UpdateItemIn, opts ...grpc.CallOption) (*emptypb.Empty, error) } type keeperClient struct { @@ -85,6 +88,16 @@ func (c *keeperClient) GetAllItems(ctx context.Context, in *GetAllItemsIn, opts return out, nil } +func (c *keeperClient) UpdateItem(ctx context.Context, in *UpdateItemIn, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, Keeper_UpdateItem_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // KeeperServer is the server API for Keeper service. // All implementations should embed UnimplementedKeeperServer // for forward compatibility. @@ -95,6 +108,7 @@ type KeeperServer interface { RefreshTokens(context.Context, *RefreshTokensIn) (*RefreshTokensOut, error) // data GetAllItems(context.Context, *GetAllItemsIn) (*GetAllItemsOut, error) + UpdateItem(context.Context, *UpdateItemIn) (*emptypb.Empty, error) } // UnimplementedKeeperServer should be embedded to have @@ -116,6 +130,9 @@ func (UnimplementedKeeperServer) RefreshTokens(context.Context, *RefreshTokensIn func (UnimplementedKeeperServer) GetAllItems(context.Context, *GetAllItemsIn) (*GetAllItemsOut, error) { return nil, status.Errorf(codes.Unimplemented, "method GetAllItems not implemented") } +func (UnimplementedKeeperServer) UpdateItem(context.Context, *UpdateItemIn) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateItem not implemented") +} func (UnimplementedKeeperServer) testEmbeddedByValue() {} // UnsafeKeeperServer may be embedded to opt out of forward compatibility for this service. @@ -208,6 +225,24 @@ func _Keeper_GetAllItems_Handler(srv interface{}, ctx context.Context, dec func( return interceptor(ctx, in, info, handler) } +func _Keeper_UpdateItem_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateItemIn) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(KeeperServer).UpdateItem(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Keeper_UpdateItem_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(KeeperServer).UpdateItem(ctx, req.(*UpdateItemIn)) + } + return interceptor(ctx, in, info, handler) +} + // Keeper_ServiceDesc is the grpc.ServiceDesc for Keeper service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -231,6 +266,10 @@ var Keeper_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetAllItems", Handler: _Keeper_GetAllItems_Handler, }, + { + MethodName: "UpdateItem", + Handler: _Keeper_UpdateItem_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "proto/keeper.proto", diff --git a/internal/infrastructure/rpc/server/method.go b/internal/infrastructure/rpc/server/method.go index 444b069..80e8a0b 100644 --- a/internal/infrastructure/rpc/server/method.go +++ b/internal/infrastructure/rpc/server/method.go @@ -5,6 +5,7 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" pb "github.com/bjlag/go-keeper/internal/generated/rpc" ) @@ -14,6 +15,7 @@ const ( LoginMethod = "Login" RefreshTokensMethod = "RefreshTokens" GetAllItemsMethod = "GetAllItems" + UpdateItemMethod = "UpdateItem" ) func (s RPCServer) Register(ctx context.Context, in *pb.RegisterIn) (*pb.RegisterOut, error) { @@ -72,6 +74,20 @@ func (s RPCServer) GetAllItems(ctx context.Context, in *pb.GetAllItemsIn) (*pb.G return h(ctx, in) } +func (s RPCServer) UpdateItem(ctx context.Context, in *pb.UpdateItemIn) (*emptypb.Empty, error) { + handler, err := s.getHandler(UpdateItemMethod) + if err != nil { + return nil, err + } + + h, ok := handler.(func(context.Context, *pb.UpdateItemIn) (*emptypb.Empty, error)) + if !ok { + return nil, status.Errorf(codes.Internal, "handler for %s method not found", GetAllItemsMethod) + } + + return h(ctx, in) +} + func (s RPCServer) getHandler(name string) (any, error) { handler, ok := s.handlers[name] if !ok { diff --git a/internal/infrastructure/store/server/item/model.go b/internal/infrastructure/store/server/item/model.go index 94d88ac..26fdac6 100644 --- a/internal/infrastructure/store/server/item/model.go +++ b/internal/infrastructure/store/server/item/model.go @@ -8,7 +8,7 @@ import ( "github.com/bjlag/go-keeper/internal/domain/data" ) -type Row struct { +type row struct { GUID uuid.UUID `db:"guid"` UserGUID uuid.UUID `db:"user_guid"` EncryptedData []byte `db:"encrypted_data"` @@ -16,7 +16,7 @@ type Row struct { UpdatedAt time.Time `db:"updated_at"` } -func (r *Row) convertToModel() data.Item { +func (r *row) convertToModel() data.Item { return data.Item{ GUID: r.GUID, UserGUID: r.UserGUID, @@ -26,10 +26,17 @@ func (r *Row) convertToModel() data.Item { } } -func convertToModels(rows []Row) []data.Item { +func convertToModels(rows []row) []data.Item { result := make([]data.Item, 0, len(rows)) for _, row := range rows { result = append(result, row.convertToModel()) } return result } + +type updated struct { + GUID uuid.UUID `db:"guid"` + UserGUID uuid.UUID `db:"user_guid"` + EncryptedData []byte `db:"encrypted_data"` + UpdatedAt time.Time `db:"updated_at"` +} diff --git a/internal/infrastructure/store/server/item/store.go b/internal/infrastructure/store/server/item/store.go index 8ca6774..85f68e9 100644 --- a/internal/infrastructure/store/server/item/store.go +++ b/internal/infrastructure/store/server/item/store.go @@ -2,6 +2,7 @@ package item import ( "context" + "errors" "fmt" "github.com/google/uuid" @@ -10,6 +11,8 @@ import ( model "github.com/bjlag/go-keeper/internal/domain/data" ) +var ErrNotAffectedRows = errors.New("not affected") + type Store struct { db *sqlx.DB } @@ -32,10 +35,42 @@ func (s *Store) GetAllByUser(ctx context.Context, userGUID uuid.UUID, limit, off OFFSET $3 ` - var rows []Row + var rows []row if err := s.db.SelectContext(ctx, &rows, query, userGUID, limit, offset); err != nil { return nil, fmt.Errorf("%s: %w", op, err) } return convertToModels(rows), nil } + +func (s *Store) Update(ctx context.Context, guid uuid.UUID, userGUID uuid.UUID, data model.UpdatedItem) error { + const op = "store.item.Update" + + query := ` + UPDATE items + SET encrypted_data = :encrypted_data, updated_at = :updated_at + WHERE guid = :guid AND user_guid = :user_guid + ` + + arg := updated{ + GUID: guid, + UserGUID: userGUID, + EncryptedData: data.EncryptedData, + UpdatedAt: data.UpdatedAt, + } + + result, err := s.db.NamedExecContext(ctx, query, arg) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + rows, err := result.RowsAffected() + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + if rows == 0 { + return ErrNotAffectedRows + } + + return nil +} diff --git a/internal/rpc/update_item/contract.go b/internal/rpc/update_item/contract.go new file mode 100644 index 0000000..9002403 --- /dev/null +++ b/internal/rpc/update_item/contract.go @@ -0,0 +1,11 @@ +package update_item + +import ( + "context" + + "github.com/bjlag/go-keeper/internal/usecase/server/data/update" +) + +type usecase interface { + Do(ctx context.Context, data update.In) error +} diff --git a/internal/rpc/update_item/handler.go b/internal/rpc/update_item/handler.go new file mode 100644 index 0000000..096ec6e --- /dev/null +++ b/internal/rpc/update_item/handler.go @@ -0,0 +1,61 @@ +package update_item + +import ( + "context" + "errors" + + "github.com/google/uuid" + "go.uber.org/zap" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" + + pb "github.com/bjlag/go-keeper/internal/generated/rpc" + "github.com/bjlag/go-keeper/internal/infrastructure/auth" + "github.com/bjlag/go-keeper/internal/infrastructure/logger" + "github.com/bjlag/go-keeper/internal/usecase/server/data/update" +) + +type Handler struct { + usecase usecase +} + +func New(usecase usecase) *Handler { + return &Handler{ + usecase: usecase, + } +} + +func (h *Handler) Handle(ctx context.Context, in *pb.UpdateItemIn) (*emptypb.Empty, error) { + log := logger.FromCtx(ctx) + + userGUID := auth.UserGUIDFromCtx(ctx) + if userGUID == uuid.Nil { + return nil, status.Error(codes.PermissionDenied, "permission denied") + } + + if len(in.GetEncryptedData()) == 0 { + return nil, status.Error(codes.InvalidArgument, "encrypted data is empty") + } + + itemGUID, err := uuid.Parse(in.GetGuid()) + if err != nil { + return nil, status.Error(codes.InvalidArgument, "invalid item guid") + } + + err = h.usecase.Do(ctx, update.In{ + UserGUID: userGUID, + ItemGUID: itemGUID, + EncryptedData: in.GetEncryptedData(), + }) + if err != nil { + if errors.Is(err, update.ErrNotFoundUpdatedData) { + return nil, status.Error(codes.NotFound, "item not found") + } + + log.Error("Failed to update item", zap.Error(err)) + return nil, status.Error(codes.Internal, "internal error") + } + + return &emptypb.Empty{}, nil +} diff --git a/internal/usecase/server/data/update/contract.go b/internal/usecase/server/data/update/contract.go new file mode 100644 index 0000000..6c9052d --- /dev/null +++ b/internal/usecase/server/data/update/contract.go @@ -0,0 +1,13 @@ +package update + +import ( + "context" + + "github.com/google/uuid" + + model "github.com/bjlag/go-keeper/internal/domain/data" +) + +type store interface { + Update(ctx context.Context, guid uuid.UUID, userGUID uuid.UUID, updatedData model.UpdatedItem) error +} diff --git a/internal/usecase/server/data/update/model.go b/internal/usecase/server/data/update/model.go new file mode 100644 index 0000000..74411dd --- /dev/null +++ b/internal/usecase/server/data/update/model.go @@ -0,0 +1,11 @@ +package update + +import ( + "github.com/google/uuid" +) + +type In struct { + UserGUID uuid.UUID + ItemGUID uuid.UUID + EncryptedData []byte +} diff --git a/internal/usecase/server/data/update/usecase.go b/internal/usecase/server/data/update/usecase.go new file mode 100644 index 0000000..851354b --- /dev/null +++ b/internal/usecase/server/data/update/usecase.go @@ -0,0 +1,42 @@ +package update + +import ( + "context" + "errors" + "fmt" + "time" + + model "github.com/bjlag/go-keeper/internal/domain/data" + "github.com/bjlag/go-keeper/internal/infrastructure/store/server/item" +) + +var ErrNotFoundUpdatedData = errors.New("not found updated data") + +type Usecase struct { + store store +} + +func NewUsecase(store store) *Usecase { + return &Usecase{ + store: store, + } +} + +func (u Usecase) Do(ctx context.Context, in In) error { + const op = "usecase.item.update.Do" + + data := model.UpdatedItem{ + EncryptedData: in.EncryptedData, + UpdatedAt: time.Now(), + } + + err := u.store.Update(ctx, in.ItemGUID, in.UserGUID, data) + if err != nil { + if errors.Is(err, item.ErrNotAffectedRows) { + return ErrNotFoundUpdatedData + } + return fmt.Errorf("%s: %w", op, err) + } + + return nil +} diff --git a/proto/keeper.proto b/proto/keeper.proto index 7a5aff1..9a828df 100644 --- a/proto/keeper.proto +++ b/proto/keeper.proto @@ -1,6 +1,7 @@ syntax = "proto3"; import "google/protobuf/timestamp.proto"; +import "google/protobuf/empty.proto"; package keeper; @@ -14,6 +15,7 @@ service Keeper { // data rpc GetAllItems(GetAllItemsIn) returns (GetAllItemsOut); + rpc UpdateItem(UpdateItemIn) returns (google.protobuf.Empty); } // auth @@ -61,4 +63,9 @@ message Item { bytes encryptedData = 2; google.protobuf.Timestamp CreatedAt = 3; google.protobuf.Timestamp UpdatedAt = 4; -} \ No newline at end of file +} + +message UpdateItemIn { + string guid = 1; + bytes encryptedData = 2; +} From 5446f3a6cc90b44a30609334690b14f7d746ba5f Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Fri, 7 Mar 2025 19:17:19 +0300 Subject: [PATCH 46/94] server: rpc delete item --- internal/app/server/app.go | 8 +- internal/generated/rpc/keeper.pb.go | 115 +++++++++++++----- internal/generated/rpc/keeper_grpc.pb.go | 38 ++++++ internal/infrastructure/rpc/server/method.go | 17 ++- .../infrastructure/store/server/item/model.go | 5 + .../infrastructure/store/server/item/store.go | 29 +++++ internal/rpc/delete_item/contract.go | 11 ++ internal/rpc/delete_item/handler.go | 56 +++++++++ .../usecase/server/data/delete/contract.go | 11 ++ internal/usecase/server/data/delete/model.go | 10 ++ .../usecase/server/data/delete/usecase.go | 34 ++++++ .../usecase/server/data/update/usecase.go | 2 +- proto/keeper.proto | 5 + 13 files changed, 307 insertions(+), 34 deletions(-) create mode 100644 internal/rpc/delete_item/contract.go create mode 100644 internal/rpc/delete_item/handler.go create mode 100644 internal/usecase/server/data/delete/contract.go create mode 100644 internal/usecase/server/data/delete/model.go create mode 100644 internal/usecase/server/data/delete/usecase.go diff --git a/internal/app/server/app.go b/internal/app/server/app.go index af79148..c8e193e 100644 --- a/internal/app/server/app.go +++ b/internal/app/server/app.go @@ -11,12 +11,15 @@ import ( "github.com/bjlag/go-keeper/internal/infrastructure/rpc/server" "github.com/bjlag/go-keeper/internal/infrastructure/store/server/item" "github.com/bjlag/go-keeper/internal/infrastructure/store/server/user" + rpcDeleteItem "github.com/bjlag/go-keeper/internal/rpc/delete_item" rpcGetAllItems "github.com/bjlag/go-keeper/internal/rpc/get_all_items" rpcLogin "github.com/bjlag/go-keeper/internal/rpc/login" rpcRefreshTokens "github.com/bjlag/go-keeper/internal/rpc/refresh_tokens" rpcRegister "github.com/bjlag/go-keeper/internal/rpc/register" rpcUpdateItem "github.com/bjlag/go-keeper/internal/rpc/update_item" + "github.com/bjlag/go-keeper/internal/usecase/server/data/delete" "github.com/bjlag/go-keeper/internal/usecase/server/data/get_all" + "github.com/bjlag/go-keeper/internal/usecase/server/data/update" "github.com/bjlag/go-keeper/internal/usecase/server/user/login" rt "github.com/bjlag/go-keeper/internal/usecase/server/user/refresh_tokens" "github.com/bjlag/go-keeper/internal/usecase/server/user/register" @@ -55,6 +58,8 @@ func (a *App) Run(ctx context.Context) error { ucLogin := login.NewUsecase(userStore, jwt) ucRefreshTokens := rt.NewUsecase(userStore, jwt) ucGetAllData := get_all.NewUsecase(dataStore) + ucUpdateItem := update.NewUsecase(dataStore) + ucDeleteItem := delete.NewUsecase(dataStore) s := server.NewRPCServer( server.WithAddress(a.cfg.Address.Host, a.cfg.Address.Port), @@ -65,7 +70,8 @@ func (a *App) Run(ctx context.Context) error { server.WithHandler(server.LoginMethod, rpcLogin.New(ucLogin).Handle), server.WithHandler(server.RefreshTokensMethod, rpcRefreshTokens.New(ucRefreshTokens).Handle), server.WithHandler(server.GetAllItemsMethod, rpcGetAllItems.New(ucGetAllData).Handle), - server.WithHandler(server.UpdateItemMethod, rpcUpdateItem.New(ucGetAllData).Handle), + server.WithHandler(server.UpdateItemMethod, rpcUpdateItem.New(ucUpdateItem).Handle), + server.WithHandler(server.DeleteItemMethod, rpcDeleteItem.New(ucDeleteItem).Handle), ) err = s.Start(ctx) diff --git a/internal/generated/rpc/keeper.pb.go b/internal/generated/rpc/keeper.pb.go index a6817d4..d4208b2 100644 --- a/internal/generated/rpc/keeper.pb.go +++ b/internal/generated/rpc/keeper.pb.go @@ -545,6 +545,50 @@ func (x *UpdateItemIn) GetEncryptedData() []byte { return nil } +type DeleteItemIn struct { + state protoimpl.MessageState `protogen:"open.v1"` + Guid string `protobuf:"bytes,1,opt,name=guid,proto3" json:"guid,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteItemIn) Reset() { + *x = DeleteItemIn{} + mi := &file_proto_keeper_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteItemIn) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteItemIn) ProtoMessage() {} + +func (x *DeleteItemIn) ProtoReflect() protoreflect.Message { + mi := &file_proto_keeper_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteItemIn.ProtoReflect.Descriptor instead. +func (*DeleteItemIn) Descriptor() ([]byte, []int) { + return file_proto_keeper_proto_rawDescGZIP(), []int{10} +} + +func (x *DeleteItemIn) GetGuid() string { + if x != nil { + return x.Guid + } + return "" +} + var File_proto_keeper_proto protoreflect.FileDescriptor var file_proto_keeper_proto_rawDesc = string([]byte{ @@ -604,24 +648,30 @@ var file_proto_keeper_proto_rawDesc = string([]byte{ 0x49, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x65, - 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x32, 0xa7, 0x02, 0x0a, - 0x06, 0x4b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x12, 0x33, 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, - 0x74, 0x65, 0x72, 0x12, 0x12, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, - 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x1a, 0x13, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, - 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x12, 0x2a, 0x0a, 0x05, - 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x0f, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, - 0x6f, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x1a, 0x10, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, - 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x4f, 0x75, 0x74, 0x12, 0x42, 0x0a, 0x0d, 0x52, 0x65, 0x66, 0x72, - 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x17, 0x2e, 0x6b, 0x65, 0x65, 0x70, - 0x65, 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, - 0x49, 0x6e, 0x1a, 0x18, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, - 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x4f, 0x75, 0x74, 0x12, 0x3c, 0x0a, 0x0b, - 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x15, 0x2e, 0x6b, 0x65, - 0x65, 0x70, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, - 0x49, 0x6e, 0x1a, 0x16, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x41, - 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x4f, 0x75, 0x74, 0x12, 0x3a, 0x0a, 0x0a, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x14, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, - 0x72, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x1a, 0x16, + 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x22, 0x22, 0x0a, 0x0c, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x12, 0x12, 0x0a, 0x04, + 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, + 0x32, 0xe3, 0x02, 0x0a, 0x06, 0x4b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x12, 0x33, 0x0a, 0x08, 0x52, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x12, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, + 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x1a, 0x13, 0x2e, 0x6b, 0x65, + 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, + 0x12, 0x2a, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x0f, 0x2e, 0x6b, 0x65, 0x65, 0x70, + 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x1a, 0x10, 0x2e, 0x6b, 0x65, 0x65, + 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x4f, 0x75, 0x74, 0x12, 0x42, 0x0a, 0x0d, + 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x17, 0x2e, + 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x73, 0x49, 0x6e, 0x1a, 0x18, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, + 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x4f, 0x75, 0x74, + 0x12, 0x3c, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x12, + 0x15, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, + 0x74, 0x65, 0x6d, 0x73, 0x49, 0x6e, 0x1a, 0x16, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, + 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x4f, 0x75, 0x74, 0x12, 0x3a, + 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x14, 0x2e, 0x6b, + 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, + 0x49, 0x6e, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3a, 0x0a, 0x0a, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x14, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, + 0x72, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x18, 0x5a, 0x16, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x72, 0x70, 0x63, @@ -640,7 +690,7 @@ func file_proto_keeper_proto_rawDescGZIP() []byte { return file_proto_keeper_proto_rawDescData } -var file_proto_keeper_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_proto_keeper_proto_msgTypes = make([]protoimpl.MessageInfo, 11) var file_proto_keeper_proto_goTypes = []any{ (*RegisterIn)(nil), // 0: keeper.RegisterIn (*RegisterOut)(nil), // 1: keeper.RegisterOut @@ -652,25 +702,28 @@ var file_proto_keeper_proto_goTypes = []any{ (*GetAllItemsOut)(nil), // 7: keeper.GetAllItemsOut (*Item)(nil), // 8: keeper.Item (*UpdateItemIn)(nil), // 9: keeper.UpdateItemIn - (*timestamppb.Timestamp)(nil), // 10: google.protobuf.Timestamp - (*emptypb.Empty)(nil), // 11: google.protobuf.Empty + (*DeleteItemIn)(nil), // 10: keeper.DeleteItemIn + (*timestamppb.Timestamp)(nil), // 11: google.protobuf.Timestamp + (*emptypb.Empty)(nil), // 12: google.protobuf.Empty } var file_proto_keeper_proto_depIdxs = []int32{ 8, // 0: keeper.GetAllItemsOut.items:type_name -> keeper.Item - 10, // 1: keeper.Item.CreatedAt:type_name -> google.protobuf.Timestamp - 10, // 2: keeper.Item.UpdatedAt:type_name -> google.protobuf.Timestamp + 11, // 1: keeper.Item.CreatedAt:type_name -> google.protobuf.Timestamp + 11, // 2: keeper.Item.UpdatedAt:type_name -> google.protobuf.Timestamp 0, // 3: keeper.Keeper.Register:input_type -> keeper.RegisterIn 2, // 4: keeper.Keeper.Login:input_type -> keeper.LoginIn 4, // 5: keeper.Keeper.RefreshTokens:input_type -> keeper.RefreshTokensIn 6, // 6: keeper.Keeper.GetAllItems:input_type -> keeper.GetAllItemsIn 9, // 7: keeper.Keeper.UpdateItem:input_type -> keeper.UpdateItemIn - 1, // 8: keeper.Keeper.Register:output_type -> keeper.RegisterOut - 3, // 9: keeper.Keeper.Login:output_type -> keeper.LoginOut - 5, // 10: keeper.Keeper.RefreshTokens:output_type -> keeper.RefreshTokensOut - 7, // 11: keeper.Keeper.GetAllItems:output_type -> keeper.GetAllItemsOut - 11, // 12: keeper.Keeper.UpdateItem:output_type -> google.protobuf.Empty - 8, // [8:13] is the sub-list for method output_type - 3, // [3:8] is the sub-list for method input_type + 10, // 8: keeper.Keeper.DeleteItem:input_type -> keeper.DeleteItemIn + 1, // 9: keeper.Keeper.Register:output_type -> keeper.RegisterOut + 3, // 10: keeper.Keeper.Login:output_type -> keeper.LoginOut + 5, // 11: keeper.Keeper.RefreshTokens:output_type -> keeper.RefreshTokensOut + 7, // 12: keeper.Keeper.GetAllItems:output_type -> keeper.GetAllItemsOut + 12, // 13: keeper.Keeper.UpdateItem:output_type -> google.protobuf.Empty + 12, // 14: keeper.Keeper.DeleteItem:output_type -> google.protobuf.Empty + 9, // [9:15] is the sub-list for method output_type + 3, // [3:9] is the sub-list for method input_type 3, // [3:3] is the sub-list for extension type_name 3, // [3:3] is the sub-list for extension extendee 0, // [0:3] is the sub-list for field type_name @@ -687,7 +740,7 @@ func file_proto_keeper_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_keeper_proto_rawDesc), len(file_proto_keeper_proto_rawDesc)), NumEnums: 0, - NumMessages: 10, + NumMessages: 11, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/generated/rpc/keeper_grpc.pb.go b/internal/generated/rpc/keeper_grpc.pb.go index 71bf98e..01131b6 100644 --- a/internal/generated/rpc/keeper_grpc.pb.go +++ b/internal/generated/rpc/keeper_grpc.pb.go @@ -25,6 +25,7 @@ const ( Keeper_RefreshTokens_FullMethodName = "/keeper.Keeper/RefreshTokens" Keeper_GetAllItems_FullMethodName = "/keeper.Keeper/GetAllItems" Keeper_UpdateItem_FullMethodName = "/keeper.Keeper/UpdateItem" + Keeper_DeleteItem_FullMethodName = "/keeper.Keeper/DeleteItem" ) // KeeperClient is the client API for Keeper service. @@ -38,6 +39,7 @@ type KeeperClient interface { // data GetAllItems(ctx context.Context, in *GetAllItemsIn, opts ...grpc.CallOption) (*GetAllItemsOut, error) UpdateItem(ctx context.Context, in *UpdateItemIn, opts ...grpc.CallOption) (*emptypb.Empty, error) + DeleteItem(ctx context.Context, in *DeleteItemIn, opts ...grpc.CallOption) (*emptypb.Empty, error) } type keeperClient struct { @@ -98,6 +100,16 @@ func (c *keeperClient) UpdateItem(ctx context.Context, in *UpdateItemIn, opts .. return out, nil } +func (c *keeperClient) DeleteItem(ctx context.Context, in *DeleteItemIn, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, Keeper_DeleteItem_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // KeeperServer is the server API for Keeper service. // All implementations should embed UnimplementedKeeperServer // for forward compatibility. @@ -109,6 +121,7 @@ type KeeperServer interface { // data GetAllItems(context.Context, *GetAllItemsIn) (*GetAllItemsOut, error) UpdateItem(context.Context, *UpdateItemIn) (*emptypb.Empty, error) + DeleteItem(context.Context, *DeleteItemIn) (*emptypb.Empty, error) } // UnimplementedKeeperServer should be embedded to have @@ -133,6 +146,9 @@ func (UnimplementedKeeperServer) GetAllItems(context.Context, *GetAllItemsIn) (* func (UnimplementedKeeperServer) UpdateItem(context.Context, *UpdateItemIn) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateItem not implemented") } +func (UnimplementedKeeperServer) DeleteItem(context.Context, *DeleteItemIn) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteItem not implemented") +} func (UnimplementedKeeperServer) testEmbeddedByValue() {} // UnsafeKeeperServer may be embedded to opt out of forward compatibility for this service. @@ -243,6 +259,24 @@ func _Keeper_UpdateItem_Handler(srv interface{}, ctx context.Context, dec func(i return interceptor(ctx, in, info, handler) } +func _Keeper_DeleteItem_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteItemIn) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(KeeperServer).DeleteItem(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Keeper_DeleteItem_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(KeeperServer).DeleteItem(ctx, req.(*DeleteItemIn)) + } + return interceptor(ctx, in, info, handler) +} + // Keeper_ServiceDesc is the grpc.ServiceDesc for Keeper service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -270,6 +304,10 @@ var Keeper_ServiceDesc = grpc.ServiceDesc{ MethodName: "UpdateItem", Handler: _Keeper_UpdateItem_Handler, }, + { + MethodName: "DeleteItem", + Handler: _Keeper_DeleteItem_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "proto/keeper.proto", diff --git a/internal/infrastructure/rpc/server/method.go b/internal/infrastructure/rpc/server/method.go index 80e8a0b..d3e5cdf 100644 --- a/internal/infrastructure/rpc/server/method.go +++ b/internal/infrastructure/rpc/server/method.go @@ -16,6 +16,7 @@ const ( RefreshTokensMethod = "RefreshTokens" GetAllItemsMethod = "GetAllItems" UpdateItemMethod = "UpdateItem" + DeleteItemMethod = "DeleteItem" ) func (s RPCServer) Register(ctx context.Context, in *pb.RegisterIn) (*pb.RegisterOut, error) { @@ -82,7 +83,21 @@ func (s RPCServer) UpdateItem(ctx context.Context, in *pb.UpdateItemIn) (*emptyp h, ok := handler.(func(context.Context, *pb.UpdateItemIn) (*emptypb.Empty, error)) if !ok { - return nil, status.Errorf(codes.Internal, "handler for %s method not found", GetAllItemsMethod) + return nil, status.Errorf(codes.Internal, "handler for %s method not found", UpdateItemMethod) + } + + return h(ctx, in) +} + +func (s RPCServer) DeleteItem(ctx context.Context, in *pb.DeleteItemIn) (*emptypb.Empty, error) { + handler, err := s.getHandler(DeleteItemMethod) + if err != nil { + return nil, err + } + + h, ok := handler.(func(context.Context, *pb.DeleteItemIn) (*emptypb.Empty, error)) + if !ok { + return nil, status.Errorf(codes.Internal, "handler for %s method not found", DeleteItemMethod) } return h(ctx, in) diff --git a/internal/infrastructure/store/server/item/model.go b/internal/infrastructure/store/server/item/model.go index 26fdac6..9bff4bc 100644 --- a/internal/infrastructure/store/server/item/model.go +++ b/internal/infrastructure/store/server/item/model.go @@ -40,3 +40,8 @@ type updated struct { EncryptedData []byte `db:"encrypted_data"` UpdatedAt time.Time `db:"updated_at"` } + +type deleted struct { + GUID uuid.UUID `db:"guid"` + UserGUID uuid.UUID `db:"user_guid"` +} diff --git a/internal/infrastructure/store/server/item/store.go b/internal/infrastructure/store/server/item/store.go index 85f68e9..eea236e 100644 --- a/internal/infrastructure/store/server/item/store.go +++ b/internal/infrastructure/store/server/item/store.go @@ -74,3 +74,32 @@ func (s *Store) Update(ctx context.Context, guid uuid.UUID, userGUID uuid.UUID, return nil } + +func (s *Store) Delete(ctx context.Context, guid uuid.UUID, userGUID uuid.UUID) error { + const op = "store.item.Delete" + + query := ` + DELETE FROM items + WHERE guid = :guid AND user_guid = :user_guid + ` + + arg := deleted{ + GUID: guid, + UserGUID: userGUID, + } + + result, err := s.db.NamedExecContext(ctx, query, arg) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + rows, err := result.RowsAffected() + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + if rows == 0 { + return ErrNotAffectedRows + } + + return nil +} diff --git a/internal/rpc/delete_item/contract.go b/internal/rpc/delete_item/contract.go new file mode 100644 index 0000000..94f108b --- /dev/null +++ b/internal/rpc/delete_item/contract.go @@ -0,0 +1,11 @@ +package delete_item + +import ( + "context" + + "github.com/bjlag/go-keeper/internal/usecase/server/data/delete" +) + +type usecase interface { + Do(ctx context.Context, data delete.In) error +} diff --git a/internal/rpc/delete_item/handler.go b/internal/rpc/delete_item/handler.go new file mode 100644 index 0000000..7df0519 --- /dev/null +++ b/internal/rpc/delete_item/handler.go @@ -0,0 +1,56 @@ +package delete_item + +import ( + "context" + "errors" + + "github.com/google/uuid" + "go.uber.org/zap" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" + + pb "github.com/bjlag/go-keeper/internal/generated/rpc" + "github.com/bjlag/go-keeper/internal/infrastructure/auth" + "github.com/bjlag/go-keeper/internal/infrastructure/logger" + "github.com/bjlag/go-keeper/internal/usecase/server/data/delete" +) + +type Handler struct { + usecase usecase +} + +func New(usecase usecase) *Handler { + return &Handler{ + usecase: usecase, + } +} + +func (h *Handler) Handle(ctx context.Context, in *pb.DeleteItemIn) (*emptypb.Empty, error) { + log := logger.FromCtx(ctx) + + userGUID := auth.UserGUIDFromCtx(ctx) + if userGUID == uuid.Nil { + return nil, status.Error(codes.PermissionDenied, "permission denied") + } + + itemGUID, err := uuid.Parse(in.GetGuid()) + if err != nil { + return nil, status.Error(codes.InvalidArgument, "invalid item guid") + } + + err = h.usecase.Do(ctx, delete.In{ + UserGUID: userGUID, + ItemGUID: itemGUID, + }) + if err != nil { + if errors.Is(err, delete.ErrNotFoundUpdatedData) { + return nil, status.Error(codes.NotFound, "item not found") + } + + log.Error("Failed to update item", zap.Error(err)) + return nil, status.Error(codes.Internal, "internal error") + } + + return &emptypb.Empty{}, nil +} diff --git a/internal/usecase/server/data/delete/contract.go b/internal/usecase/server/data/delete/contract.go new file mode 100644 index 0000000..f5f27e9 --- /dev/null +++ b/internal/usecase/server/data/delete/contract.go @@ -0,0 +1,11 @@ +package delete + +import ( + "context" + + "github.com/google/uuid" +) + +type store interface { + Delete(ctx context.Context, guid uuid.UUID, userGUID uuid.UUID) error +} diff --git a/internal/usecase/server/data/delete/model.go b/internal/usecase/server/data/delete/model.go new file mode 100644 index 0000000..9c04698 --- /dev/null +++ b/internal/usecase/server/data/delete/model.go @@ -0,0 +1,10 @@ +package delete + +import ( + "github.com/google/uuid" +) + +type In struct { + UserGUID uuid.UUID + ItemGUID uuid.UUID +} diff --git a/internal/usecase/server/data/delete/usecase.go b/internal/usecase/server/data/delete/usecase.go new file mode 100644 index 0000000..bcea1e3 --- /dev/null +++ b/internal/usecase/server/data/delete/usecase.go @@ -0,0 +1,34 @@ +package delete + +import ( + "context" + "errors" + "fmt" + "github.com/bjlag/go-keeper/internal/infrastructure/store/server/item" +) + +var ErrNotFoundUpdatedData = errors.New("deleted data is not found ") + +type Usecase struct { + store store +} + +func NewUsecase(store store) *Usecase { + return &Usecase{ + store: store, + } +} + +func (u Usecase) Do(ctx context.Context, in In) error { + const op = "usecase.item.delete.Do" + + err := u.store.Delete(ctx, in.ItemGUID, in.UserGUID) + if err != nil { + if errors.Is(err, item.ErrNotAffectedRows) { + return ErrNotFoundUpdatedData + } + return fmt.Errorf("%s: %w", op, err) + } + + return nil +} diff --git a/internal/usecase/server/data/update/usecase.go b/internal/usecase/server/data/update/usecase.go index 851354b..cbb72a2 100644 --- a/internal/usecase/server/data/update/usecase.go +++ b/internal/usecase/server/data/update/usecase.go @@ -10,7 +10,7 @@ import ( "github.com/bjlag/go-keeper/internal/infrastructure/store/server/item" ) -var ErrNotFoundUpdatedData = errors.New("not found updated data") +var ErrNotFoundUpdatedData = errors.New("updated data is not found") type Usecase struct { store store diff --git a/proto/keeper.proto b/proto/keeper.proto index 9a828df..11603da 100644 --- a/proto/keeper.proto +++ b/proto/keeper.proto @@ -16,6 +16,7 @@ service Keeper { // data rpc GetAllItems(GetAllItemsIn) returns (GetAllItemsOut); rpc UpdateItem(UpdateItemIn) returns (google.protobuf.Empty); + rpc DeleteItem(DeleteItemIn) returns (google.protobuf.Empty); } // auth @@ -69,3 +70,7 @@ message UpdateItemIn { string guid = 1; bytes encryptedData = 2; } + +message DeleteItemIn { + string guid = 1; +} \ No newline at end of file From e1a5a8e33ab78c070c7822b72d9da6c4fa98100b Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Fri, 7 Mar 2025 19:34:30 +0300 Subject: [PATCH 47/94] server: rpc create item --- internal/app/server/app.go | 4 + internal/generated/rpc/keeper.pb.go | 279 ++++++++++++------ internal/generated/rpc/keeper_grpc.pb.go | 38 +++ internal/infrastructure/rpc/server/method.go | 15 + .../infrastructure/store/server/item/store.go | 24 ++ internal/rpc/create_item/contract.go | 11 + internal/rpc/create_item/handler.go | 54 ++++ .../usecase/server/data/create/contract.go | 11 + internal/usecase/server/data/create/model.go | 11 + .../usecase/server/data/create/usecase.go | 38 +++ proto/keeper.proto | 9 + 11 files changed, 405 insertions(+), 89 deletions(-) create mode 100644 internal/rpc/create_item/contract.go create mode 100644 internal/rpc/create_item/handler.go create mode 100644 internal/usecase/server/data/create/contract.go create mode 100644 internal/usecase/server/data/create/model.go create mode 100644 internal/usecase/server/data/create/usecase.go diff --git a/internal/app/server/app.go b/internal/app/server/app.go index c8e193e..8bb7645 100644 --- a/internal/app/server/app.go +++ b/internal/app/server/app.go @@ -11,12 +11,14 @@ import ( "github.com/bjlag/go-keeper/internal/infrastructure/rpc/server" "github.com/bjlag/go-keeper/internal/infrastructure/store/server/item" "github.com/bjlag/go-keeper/internal/infrastructure/store/server/user" + rpcCreateItem "github.com/bjlag/go-keeper/internal/rpc/create_item" rpcDeleteItem "github.com/bjlag/go-keeper/internal/rpc/delete_item" rpcGetAllItems "github.com/bjlag/go-keeper/internal/rpc/get_all_items" rpcLogin "github.com/bjlag/go-keeper/internal/rpc/login" rpcRefreshTokens "github.com/bjlag/go-keeper/internal/rpc/refresh_tokens" rpcRegister "github.com/bjlag/go-keeper/internal/rpc/register" rpcUpdateItem "github.com/bjlag/go-keeper/internal/rpc/update_item" + "github.com/bjlag/go-keeper/internal/usecase/server/data/create" "github.com/bjlag/go-keeper/internal/usecase/server/data/delete" "github.com/bjlag/go-keeper/internal/usecase/server/data/get_all" "github.com/bjlag/go-keeper/internal/usecase/server/data/update" @@ -58,6 +60,7 @@ func (a *App) Run(ctx context.Context) error { ucLogin := login.NewUsecase(userStore, jwt) ucRefreshTokens := rt.NewUsecase(userStore, jwt) ucGetAllData := get_all.NewUsecase(dataStore) + ucCreateItem := create.NewUsecase(dataStore) ucUpdateItem := update.NewUsecase(dataStore) ucDeleteItem := delete.NewUsecase(dataStore) @@ -70,6 +73,7 @@ func (a *App) Run(ctx context.Context) error { server.WithHandler(server.LoginMethod, rpcLogin.New(ucLogin).Handle), server.WithHandler(server.RefreshTokensMethod, rpcRefreshTokens.New(ucRefreshTokens).Handle), server.WithHandler(server.GetAllItemsMethod, rpcGetAllItems.New(ucGetAllData).Handle), + server.WithHandler(server.CreateItemMethod, rpcCreateItem.New(ucCreateItem).Handle), server.WithHandler(server.UpdateItemMethod, rpcUpdateItem.New(ucUpdateItem).Handle), server.WithHandler(server.DeleteItemMethod, rpcDeleteItem.New(ucDeleteItem).Handle), ) diff --git a/internal/generated/rpc/keeper.pb.go b/internal/generated/rpc/keeper.pb.go index d4208b2..190578e 100644 --- a/internal/generated/rpc/keeper.pb.go +++ b/internal/generated/rpc/keeper.pb.go @@ -329,6 +329,94 @@ func (x *RefreshTokensOut) GetRefreshToken() string { } // data +type CreateItemIn struct { + state protoimpl.MessageState `protogen:"open.v1"` + EncryptedData []byte `protobuf:"bytes,1,opt,name=encryptedData,proto3" json:"encryptedData,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateItemIn) Reset() { + *x = CreateItemIn{} + mi := &file_proto_keeper_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateItemIn) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateItemIn) ProtoMessage() {} + +func (x *CreateItemIn) ProtoReflect() protoreflect.Message { + mi := &file_proto_keeper_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateItemIn.ProtoReflect.Descriptor instead. +func (*CreateItemIn) Descriptor() ([]byte, []int) { + return file_proto_keeper_proto_rawDescGZIP(), []int{6} +} + +func (x *CreateItemIn) GetEncryptedData() []byte { + if x != nil { + return x.EncryptedData + } + return nil +} + +type CreateItemOut struct { + state protoimpl.MessageState `protogen:"open.v1"` + Guid string `protobuf:"bytes,1,opt,name=guid,proto3" json:"guid,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateItemOut) Reset() { + *x = CreateItemOut{} + mi := &file_proto_keeper_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateItemOut) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateItemOut) ProtoMessage() {} + +func (x *CreateItemOut) ProtoReflect() protoreflect.Message { + mi := &file_proto_keeper_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateItemOut.ProtoReflect.Descriptor instead. +func (*CreateItemOut) Descriptor() ([]byte, []int) { + return file_proto_keeper_proto_rawDescGZIP(), []int{7} +} + +func (x *CreateItemOut) GetGuid() string { + if x != nil { + return x.Guid + } + return "" +} + type GetAllItemsIn struct { state protoimpl.MessageState `protogen:"open.v1"` Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` @@ -339,7 +427,7 @@ type GetAllItemsIn struct { func (x *GetAllItemsIn) Reset() { *x = GetAllItemsIn{} - mi := &file_proto_keeper_proto_msgTypes[6] + mi := &file_proto_keeper_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -351,7 +439,7 @@ func (x *GetAllItemsIn) String() string { func (*GetAllItemsIn) ProtoMessage() {} func (x *GetAllItemsIn) ProtoReflect() protoreflect.Message { - mi := &file_proto_keeper_proto_msgTypes[6] + mi := &file_proto_keeper_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -364,7 +452,7 @@ func (x *GetAllItemsIn) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAllItemsIn.ProtoReflect.Descriptor instead. func (*GetAllItemsIn) Descriptor() ([]byte, []int) { - return file_proto_keeper_proto_rawDescGZIP(), []int{6} + return file_proto_keeper_proto_rawDescGZIP(), []int{8} } func (x *GetAllItemsIn) GetLimit() uint32 { @@ -390,7 +478,7 @@ type GetAllItemsOut struct { func (x *GetAllItemsOut) Reset() { *x = GetAllItemsOut{} - mi := &file_proto_keeper_proto_msgTypes[7] + mi := &file_proto_keeper_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -402,7 +490,7 @@ func (x *GetAllItemsOut) String() string { func (*GetAllItemsOut) ProtoMessage() {} func (x *GetAllItemsOut) ProtoReflect() protoreflect.Message { - mi := &file_proto_keeper_proto_msgTypes[7] + mi := &file_proto_keeper_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -415,7 +503,7 @@ func (x *GetAllItemsOut) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAllItemsOut.ProtoReflect.Descriptor instead. func (*GetAllItemsOut) Descriptor() ([]byte, []int) { - return file_proto_keeper_proto_rawDescGZIP(), []int{7} + return file_proto_keeper_proto_rawDescGZIP(), []int{9} } func (x *GetAllItemsOut) GetItems() []*Item { @@ -437,7 +525,7 @@ type Item struct { func (x *Item) Reset() { *x = Item{} - mi := &file_proto_keeper_proto_msgTypes[8] + mi := &file_proto_keeper_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -449,7 +537,7 @@ func (x *Item) String() string { func (*Item) ProtoMessage() {} func (x *Item) ProtoReflect() protoreflect.Message { - mi := &file_proto_keeper_proto_msgTypes[8] + mi := &file_proto_keeper_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -462,7 +550,7 @@ func (x *Item) ProtoReflect() protoreflect.Message { // Deprecated: Use Item.ProtoReflect.Descriptor instead. func (*Item) Descriptor() ([]byte, []int) { - return file_proto_keeper_proto_rawDescGZIP(), []int{8} + return file_proto_keeper_proto_rawDescGZIP(), []int{10} } func (x *Item) GetGuid() string { @@ -503,7 +591,7 @@ type UpdateItemIn struct { func (x *UpdateItemIn) Reset() { *x = UpdateItemIn{} - mi := &file_proto_keeper_proto_msgTypes[9] + mi := &file_proto_keeper_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -515,7 +603,7 @@ func (x *UpdateItemIn) String() string { func (*UpdateItemIn) ProtoMessage() {} func (x *UpdateItemIn) ProtoReflect() protoreflect.Message { - mi := &file_proto_keeper_proto_msgTypes[9] + mi := &file_proto_keeper_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -528,7 +616,7 @@ func (x *UpdateItemIn) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateItemIn.ProtoReflect.Descriptor instead. func (*UpdateItemIn) Descriptor() ([]byte, []int) { - return file_proto_keeper_proto_rawDescGZIP(), []int{9} + return file_proto_keeper_proto_rawDescGZIP(), []int{11} } func (x *UpdateItemIn) GetGuid() string { @@ -554,7 +642,7 @@ type DeleteItemIn struct { func (x *DeleteItemIn) Reset() { *x = DeleteItemIn{} - mi := &file_proto_keeper_proto_msgTypes[10] + mi := &file_proto_keeper_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -566,7 +654,7 @@ func (x *DeleteItemIn) String() string { func (*DeleteItemIn) ProtoMessage() {} func (x *DeleteItemIn) ProtoReflect() protoreflect.Message { - mi := &file_proto_keeper_proto_msgTypes[10] + mi := &file_proto_keeper_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -579,7 +667,7 @@ func (x *DeleteItemIn) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteItemIn.ProtoReflect.Descriptor instead. func (*DeleteItemIn) Descriptor() ([]byte, []int) { - return file_proto_keeper_proto_rawDescGZIP(), []int{10} + return file_proto_keeper_proto_rawDescGZIP(), []int{12} } func (x *DeleteItemIn) GetGuid() string { @@ -625,57 +713,66 @@ var file_proto_keeper_proto_rawDesc = string([]byte{ 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, - 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x3d, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, - 0x49, 0x74, 0x65, 0x6d, 0x73, 0x49, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, - 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6f, - 0x66, 0x66, 0x73, 0x65, 0x74, 0x22, 0x34, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, - 0x74, 0x65, 0x6d, 0x73, 0x4f, 0x75, 0x74, 0x12, 0x22, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, - 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0xb4, 0x01, 0x0a, 0x04, - 0x49, 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x65, 0x6e, 0x63, 0x72, - 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x12, 0x38, - 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x03, 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, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x64, 0x41, 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, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, - 0x41, 0x74, 0x22, 0x48, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, - 0x49, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, - 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x65, - 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x22, 0x22, 0x0a, 0x0c, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x12, 0x12, 0x0a, 0x04, - 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, - 0x32, 0xe3, 0x02, 0x0a, 0x06, 0x4b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x12, 0x33, 0x0a, 0x08, 0x52, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x12, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, - 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x1a, 0x13, 0x2e, 0x6b, 0x65, - 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, - 0x12, 0x2a, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x0f, 0x2e, 0x6b, 0x65, 0x65, 0x70, - 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x1a, 0x10, 0x2e, 0x6b, 0x65, 0x65, - 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x4f, 0x75, 0x74, 0x12, 0x42, 0x0a, 0x0d, - 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x17, 0x2e, - 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x73, 0x49, 0x6e, 0x1a, 0x18, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, - 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x4f, 0x75, 0x74, - 0x12, 0x3c, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x12, - 0x15, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, - 0x74, 0x65, 0x6d, 0x73, 0x49, 0x6e, 0x1a, 0x16, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, - 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x4f, 0x75, 0x74, 0x12, 0x3a, - 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x14, 0x2e, 0x6b, - 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, - 0x49, 0x6e, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3a, 0x0a, 0x0a, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x14, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, - 0x72, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x1a, 0x16, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x18, 0x5a, 0x16, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x72, 0x70, 0x63, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x34, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x12, 0x24, 0x0a, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, + 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x65, + 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x22, 0x23, 0x0a, 0x0d, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x4f, 0x75, 0x74, 0x12, 0x12, 0x0a, + 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, + 0x64, 0x22, 0x3d, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, + 0x49, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, + 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, + 0x22, 0x34, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x4f, + 0x75, 0x74, 0x12, 0x22, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x0c, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x52, + 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0xb4, 0x01, 0x0a, 0x04, 0x49, 0x74, 0x65, 0x6d, 0x12, + 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, + 0x75, 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, + 0x44, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, + 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x12, 0x38, 0x0a, 0x09, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x03, 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, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x64, 0x41, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 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, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x48, 0x0a, + 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x12, 0x12, 0x0a, + 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, + 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, + 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, + 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x22, 0x22, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, 0x32, 0x9e, 0x03, 0x0a, 0x06, + 0x4b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x12, 0x33, 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x65, 0x72, 0x12, 0x12, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x1a, 0x13, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, + 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x12, 0x2a, 0x0a, 0x05, 0x4c, + 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x0f, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, + 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x1a, 0x10, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, + 0x6f, 0x67, 0x69, 0x6e, 0x4f, 0x75, 0x74, 0x12, 0x42, 0x0a, 0x0d, 0x52, 0x65, 0x66, 0x72, 0x65, + 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x17, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, + 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x49, + 0x6e, 0x1a, 0x18, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, + 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x4f, 0x75, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x14, 0x2e, 0x6b, 0x65, 0x65, 0x70, + 0x65, 0x72, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x1a, + 0x15, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, + 0x74, 0x65, 0x6d, 0x4f, 0x75, 0x74, 0x12, 0x3c, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, + 0x49, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x15, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x47, + 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x49, 0x6e, 0x1a, 0x16, 0x2e, 0x6b, + 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, + 0x73, 0x4f, 0x75, 0x74, 0x12, 0x3a, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x74, + 0x65, 0x6d, 0x12, 0x14, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x12, 0x3a, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x14, + 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x74, + 0x65, 0x6d, 0x49, 0x6e, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x18, 0x5a, 0x16, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, + 0x65, 0x64, 0x2f, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -690,7 +787,7 @@ func file_proto_keeper_proto_rawDescGZIP() []byte { return file_proto_keeper_proto_rawDescData } -var file_proto_keeper_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_proto_keeper_proto_msgTypes = make([]protoimpl.MessageInfo, 13) var file_proto_keeper_proto_goTypes = []any{ (*RegisterIn)(nil), // 0: keeper.RegisterIn (*RegisterOut)(nil), // 1: keeper.RegisterOut @@ -698,32 +795,36 @@ var file_proto_keeper_proto_goTypes = []any{ (*LoginOut)(nil), // 3: keeper.LoginOut (*RefreshTokensIn)(nil), // 4: keeper.RefreshTokensIn (*RefreshTokensOut)(nil), // 5: keeper.RefreshTokensOut - (*GetAllItemsIn)(nil), // 6: keeper.GetAllItemsIn - (*GetAllItemsOut)(nil), // 7: keeper.GetAllItemsOut - (*Item)(nil), // 8: keeper.Item - (*UpdateItemIn)(nil), // 9: keeper.UpdateItemIn - (*DeleteItemIn)(nil), // 10: keeper.DeleteItemIn - (*timestamppb.Timestamp)(nil), // 11: google.protobuf.Timestamp - (*emptypb.Empty)(nil), // 12: google.protobuf.Empty + (*CreateItemIn)(nil), // 6: keeper.CreateItemIn + (*CreateItemOut)(nil), // 7: keeper.CreateItemOut + (*GetAllItemsIn)(nil), // 8: keeper.GetAllItemsIn + (*GetAllItemsOut)(nil), // 9: keeper.GetAllItemsOut + (*Item)(nil), // 10: keeper.Item + (*UpdateItemIn)(nil), // 11: keeper.UpdateItemIn + (*DeleteItemIn)(nil), // 12: keeper.DeleteItemIn + (*timestamppb.Timestamp)(nil), // 13: google.protobuf.Timestamp + (*emptypb.Empty)(nil), // 14: google.protobuf.Empty } var file_proto_keeper_proto_depIdxs = []int32{ - 8, // 0: keeper.GetAllItemsOut.items:type_name -> keeper.Item - 11, // 1: keeper.Item.CreatedAt:type_name -> google.protobuf.Timestamp - 11, // 2: keeper.Item.UpdatedAt:type_name -> google.protobuf.Timestamp + 10, // 0: keeper.GetAllItemsOut.items:type_name -> keeper.Item + 13, // 1: keeper.Item.CreatedAt:type_name -> google.protobuf.Timestamp + 13, // 2: keeper.Item.UpdatedAt:type_name -> google.protobuf.Timestamp 0, // 3: keeper.Keeper.Register:input_type -> keeper.RegisterIn 2, // 4: keeper.Keeper.Login:input_type -> keeper.LoginIn 4, // 5: keeper.Keeper.RefreshTokens:input_type -> keeper.RefreshTokensIn - 6, // 6: keeper.Keeper.GetAllItems:input_type -> keeper.GetAllItemsIn - 9, // 7: keeper.Keeper.UpdateItem:input_type -> keeper.UpdateItemIn - 10, // 8: keeper.Keeper.DeleteItem:input_type -> keeper.DeleteItemIn - 1, // 9: keeper.Keeper.Register:output_type -> keeper.RegisterOut - 3, // 10: keeper.Keeper.Login:output_type -> keeper.LoginOut - 5, // 11: keeper.Keeper.RefreshTokens:output_type -> keeper.RefreshTokensOut - 7, // 12: keeper.Keeper.GetAllItems:output_type -> keeper.GetAllItemsOut - 12, // 13: keeper.Keeper.UpdateItem:output_type -> google.protobuf.Empty - 12, // 14: keeper.Keeper.DeleteItem:output_type -> google.protobuf.Empty - 9, // [9:15] is the sub-list for method output_type - 3, // [3:9] is the sub-list for method input_type + 6, // 6: keeper.Keeper.CreateItem:input_type -> keeper.CreateItemIn + 8, // 7: keeper.Keeper.GetAllItems:input_type -> keeper.GetAllItemsIn + 11, // 8: keeper.Keeper.UpdateItem:input_type -> keeper.UpdateItemIn + 12, // 9: keeper.Keeper.DeleteItem:input_type -> keeper.DeleteItemIn + 1, // 10: keeper.Keeper.Register:output_type -> keeper.RegisterOut + 3, // 11: keeper.Keeper.Login:output_type -> keeper.LoginOut + 5, // 12: keeper.Keeper.RefreshTokens:output_type -> keeper.RefreshTokensOut + 7, // 13: keeper.Keeper.CreateItem:output_type -> keeper.CreateItemOut + 9, // 14: keeper.Keeper.GetAllItems:output_type -> keeper.GetAllItemsOut + 14, // 15: keeper.Keeper.UpdateItem:output_type -> google.protobuf.Empty + 14, // 16: keeper.Keeper.DeleteItem:output_type -> google.protobuf.Empty + 10, // [10:17] is the sub-list for method output_type + 3, // [3:10] is the sub-list for method input_type 3, // [3:3] is the sub-list for extension type_name 3, // [3:3] is the sub-list for extension extendee 0, // [0:3] is the sub-list for field type_name @@ -740,7 +841,7 @@ func file_proto_keeper_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_keeper_proto_rawDesc), len(file_proto_keeper_proto_rawDesc)), NumEnums: 0, - NumMessages: 11, + NumMessages: 13, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/generated/rpc/keeper_grpc.pb.go b/internal/generated/rpc/keeper_grpc.pb.go index 01131b6..846aaf4 100644 --- a/internal/generated/rpc/keeper_grpc.pb.go +++ b/internal/generated/rpc/keeper_grpc.pb.go @@ -23,6 +23,7 @@ const ( Keeper_Register_FullMethodName = "/keeper.Keeper/Register" Keeper_Login_FullMethodName = "/keeper.Keeper/Login" Keeper_RefreshTokens_FullMethodName = "/keeper.Keeper/RefreshTokens" + Keeper_CreateItem_FullMethodName = "/keeper.Keeper/CreateItem" Keeper_GetAllItems_FullMethodName = "/keeper.Keeper/GetAllItems" Keeper_UpdateItem_FullMethodName = "/keeper.Keeper/UpdateItem" Keeper_DeleteItem_FullMethodName = "/keeper.Keeper/DeleteItem" @@ -37,6 +38,7 @@ type KeeperClient interface { Login(ctx context.Context, in *LoginIn, opts ...grpc.CallOption) (*LoginOut, error) RefreshTokens(ctx context.Context, in *RefreshTokensIn, opts ...grpc.CallOption) (*RefreshTokensOut, error) // data + CreateItem(ctx context.Context, in *CreateItemIn, opts ...grpc.CallOption) (*CreateItemOut, error) GetAllItems(ctx context.Context, in *GetAllItemsIn, opts ...grpc.CallOption) (*GetAllItemsOut, error) UpdateItem(ctx context.Context, in *UpdateItemIn, opts ...grpc.CallOption) (*emptypb.Empty, error) DeleteItem(ctx context.Context, in *DeleteItemIn, opts ...grpc.CallOption) (*emptypb.Empty, error) @@ -80,6 +82,16 @@ func (c *keeperClient) RefreshTokens(ctx context.Context, in *RefreshTokensIn, o return out, nil } +func (c *keeperClient) CreateItem(ctx context.Context, in *CreateItemIn, opts ...grpc.CallOption) (*CreateItemOut, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreateItemOut) + err := c.cc.Invoke(ctx, Keeper_CreateItem_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *keeperClient) GetAllItems(ctx context.Context, in *GetAllItemsIn, opts ...grpc.CallOption) (*GetAllItemsOut, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetAllItemsOut) @@ -119,6 +131,7 @@ type KeeperServer interface { Login(context.Context, *LoginIn) (*LoginOut, error) RefreshTokens(context.Context, *RefreshTokensIn) (*RefreshTokensOut, error) // data + CreateItem(context.Context, *CreateItemIn) (*CreateItemOut, error) GetAllItems(context.Context, *GetAllItemsIn) (*GetAllItemsOut, error) UpdateItem(context.Context, *UpdateItemIn) (*emptypb.Empty, error) DeleteItem(context.Context, *DeleteItemIn) (*emptypb.Empty, error) @@ -140,6 +153,9 @@ func (UnimplementedKeeperServer) Login(context.Context, *LoginIn) (*LoginOut, er func (UnimplementedKeeperServer) RefreshTokens(context.Context, *RefreshTokensIn) (*RefreshTokensOut, error) { return nil, status.Errorf(codes.Unimplemented, "method RefreshTokens not implemented") } +func (UnimplementedKeeperServer) CreateItem(context.Context, *CreateItemIn) (*CreateItemOut, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateItem not implemented") +} func (UnimplementedKeeperServer) GetAllItems(context.Context, *GetAllItemsIn) (*GetAllItemsOut, error) { return nil, status.Errorf(codes.Unimplemented, "method GetAllItems not implemented") } @@ -223,6 +239,24 @@ func _Keeper_RefreshTokens_Handler(srv interface{}, ctx context.Context, dec fun return interceptor(ctx, in, info, handler) } +func _Keeper_CreateItem_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateItemIn) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(KeeperServer).CreateItem(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Keeper_CreateItem_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(KeeperServer).CreateItem(ctx, req.(*CreateItemIn)) + } + return interceptor(ctx, in, info, handler) +} + func _Keeper_GetAllItems_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetAllItemsIn) if err := dec(in); err != nil { @@ -296,6 +330,10 @@ var Keeper_ServiceDesc = grpc.ServiceDesc{ MethodName: "RefreshTokens", Handler: _Keeper_RefreshTokens_Handler, }, + { + MethodName: "CreateItem", + Handler: _Keeper_CreateItem_Handler, + }, { MethodName: "GetAllItems", Handler: _Keeper_GetAllItems_Handler, diff --git a/internal/infrastructure/rpc/server/method.go b/internal/infrastructure/rpc/server/method.go index d3e5cdf..08ae401 100644 --- a/internal/infrastructure/rpc/server/method.go +++ b/internal/infrastructure/rpc/server/method.go @@ -15,6 +15,7 @@ const ( LoginMethod = "Login" RefreshTokensMethod = "RefreshTokens" GetAllItemsMethod = "GetAllItems" + CreateItemMethod = "CreateItem" UpdateItemMethod = "UpdateItem" DeleteItemMethod = "DeleteItem" ) @@ -75,6 +76,20 @@ func (s RPCServer) GetAllItems(ctx context.Context, in *pb.GetAllItemsIn) (*pb.G return h(ctx, in) } +func (s RPCServer) CreateItem(ctx context.Context, in *pb.CreateItemIn) (*pb.CreateItemOut, error) { + handler, err := s.getHandler(CreateItemMethod) + if err != nil { + return nil, err + } + + h, ok := handler.(func(context.Context, *pb.CreateItemIn) (*pb.CreateItemOut, error)) + if !ok { + return nil, status.Errorf(codes.Internal, "handler for %s method not found", CreateItemMethod) + } + + return h(ctx, in) +} + func (s RPCServer) UpdateItem(ctx context.Context, in *pb.UpdateItemIn) (*emptypb.Empty, error) { handler, err := s.getHandler(UpdateItemMethod) if err != nil { diff --git a/internal/infrastructure/store/server/item/store.go b/internal/infrastructure/store/server/item/store.go index eea236e..3698af2 100644 --- a/internal/infrastructure/store/server/item/store.go +++ b/internal/infrastructure/store/server/item/store.go @@ -43,6 +43,30 @@ func (s *Store) GetAllByUser(ctx context.Context, userGUID uuid.UUID, limit, off return convertToModels(rows), nil } +func (s *Store) Create(ctx context.Context, item model.Item) error { + const op = "store.item.Create" + + query := ` + INSERT INTO items(guid, user_guid, encrypted_data, created_at, updated_at) + VALUES(:quid, :user_guid, :encrypted_data, :created_at, :updated_at) + ` + + arg := row{ + GUID: item.GUID, + UserGUID: item.UserGUID, + EncryptedData: item.EncryptedData, + CreatedAt: item.CreatedAt, + UpdatedAt: item.UpdatedAt, + } + + _, err := s.db.NamedExecContext(ctx, query, arg) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + return nil +} + func (s *Store) Update(ctx context.Context, guid uuid.UUID, userGUID uuid.UUID, data model.UpdatedItem) error { const op = "store.item.Update" diff --git a/internal/rpc/create_item/contract.go b/internal/rpc/create_item/contract.go new file mode 100644 index 0000000..c6ced1d --- /dev/null +++ b/internal/rpc/create_item/contract.go @@ -0,0 +1,11 @@ +package create_item + +import ( + "context" + + "github.com/bjlag/go-keeper/internal/usecase/server/data/create" +) + +type usecase interface { + Do(ctx context.Context, data create.In) error +} diff --git a/internal/rpc/create_item/handler.go b/internal/rpc/create_item/handler.go new file mode 100644 index 0000000..70a8374 --- /dev/null +++ b/internal/rpc/create_item/handler.go @@ -0,0 +1,54 @@ +package create_item + +import ( + "context" + + "github.com/google/uuid" + "go.uber.org/zap" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + pb "github.com/bjlag/go-keeper/internal/generated/rpc" + "github.com/bjlag/go-keeper/internal/infrastructure/auth" + "github.com/bjlag/go-keeper/internal/infrastructure/logger" + "github.com/bjlag/go-keeper/internal/usecase/server/data/create" +) + +type Handler struct { + usecase usecase +} + +func New(usecase usecase) *Handler { + return &Handler{ + usecase: usecase, + } +} + +func (h *Handler) Handle(ctx context.Context, in *pb.CreateItemIn) (*pb.CreateItemOut, error) { + log := logger.FromCtx(ctx) + + userGUID := auth.UserGUIDFromCtx(ctx) + if userGUID == uuid.Nil { + return nil, status.Error(codes.PermissionDenied, "permission denied") + } + + if len(in.GetEncryptedData()) == 0 { + return nil, status.Error(codes.InvalidArgument, "encrypted data is empty") + } + + itemGUID := uuid.New() + + err := h.usecase.Do(ctx, create.In{ + ItemGUID: itemGUID, + UserGUID: userGUID, + EncryptedData: in.GetEncryptedData(), + }) + if err != nil { + log.Error("Failed to update item", zap.Error(err)) + return nil, status.Error(codes.Internal, "internal error") + } + + return &pb.CreateItemOut{ + Guid: itemGUID.String(), + }, nil +} diff --git a/internal/usecase/server/data/create/contract.go b/internal/usecase/server/data/create/contract.go new file mode 100644 index 0000000..5a59dc8 --- /dev/null +++ b/internal/usecase/server/data/create/contract.go @@ -0,0 +1,11 @@ +package create + +import ( + "context" + + model "github.com/bjlag/go-keeper/internal/domain/data" +) + +type store interface { + Create(ctx context.Context, item model.Item) error +} diff --git a/internal/usecase/server/data/create/model.go b/internal/usecase/server/data/create/model.go new file mode 100644 index 0000000..a553da1 --- /dev/null +++ b/internal/usecase/server/data/create/model.go @@ -0,0 +1,11 @@ +package create + +import ( + "github.com/google/uuid" +) + +type In struct { + ItemGUID uuid.UUID + UserGUID uuid.UUID + EncryptedData []byte +} diff --git a/internal/usecase/server/data/create/usecase.go b/internal/usecase/server/data/create/usecase.go new file mode 100644 index 0000000..ba5c769 --- /dev/null +++ b/internal/usecase/server/data/create/usecase.go @@ -0,0 +1,38 @@ +package create + +import ( + "context" + "fmt" + "time" + + model "github.com/bjlag/go-keeper/internal/domain/data" +) + +type Usecase struct { + store store +} + +func NewUsecase(store store) *Usecase { + return &Usecase{ + store: store, + } +} + +func (u Usecase) Do(ctx context.Context, in In) error { + const op = "usecase.item.update.Do" + + data := model.Item{ + GUID: in.ItemGUID, + UserGUID: in.UserGUID, + EncryptedData: in.EncryptedData, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + err := u.store.Create(ctx, data) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + return nil +} diff --git a/proto/keeper.proto b/proto/keeper.proto index 11603da..f8fe2d4 100644 --- a/proto/keeper.proto +++ b/proto/keeper.proto @@ -14,6 +14,7 @@ service Keeper { rpc RefreshTokens(RefreshTokensIn) returns (RefreshTokensOut); // data + rpc CreateItem(CreateItemIn) returns (CreateItemOut); rpc GetAllItems(GetAllItemsIn) returns (GetAllItemsOut); rpc UpdateItem(UpdateItemIn) returns (google.protobuf.Empty); rpc DeleteItem(DeleteItemIn) returns (google.protobuf.Empty); @@ -50,6 +51,14 @@ message RefreshTokensOut { } // data +message CreateItemIn { + bytes encryptedData = 1; +} + +message CreateItemOut { + string guid = 1; +} + message GetAllItemsIn { uint32 limit = 1; uint32 offset = 2; From 9c32b83d039a20238e1b71f212848a67a1ce1efa Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Fri, 7 Mar 2025 19:36:36 +0300 Subject: [PATCH 48/94] server: refactoring --- internal/app/server/app.go | 8 ++++---- internal/domain/{ => server}/data/item.go | 0 internal/domain/{ => server}/user/user.go | 0 internal/infrastructure/store/server/item/model.go | 2 +- internal/infrastructure/store/server/item/store.go | 2 +- internal/infrastructure/store/server/user/model.go | 2 +- internal/infrastructure/store/server/user/store.go | 4 ++-- internal/rpc/create_item/contract.go | 2 +- internal/rpc/create_item/handler.go | 2 +- internal/rpc/delete_item/contract.go | 2 +- internal/rpc/delete_item/handler.go | 2 +- internal/rpc/get_all_items/contract.go | 2 +- internal/rpc/get_all_items/handler.go | 2 +- internal/rpc/update_item/contract.go | 2 +- internal/rpc/update_item/handler.go | 2 +- internal/usecase/server/{data => item}/create/contract.go | 2 +- internal/usecase/server/{data => item}/create/model.go | 0 internal/usecase/server/{data => item}/create/usecase.go | 2 +- internal/usecase/server/{data => item}/delete/contract.go | 0 internal/usecase/server/{data => item}/delete/model.go | 0 internal/usecase/server/{data => item}/delete/usecase.go | 0 .../usecase/server/{data => item}/get_all/contract.go | 2 +- internal/usecase/server/{data => item}/get_all/model.go | 0 internal/usecase/server/{data => item}/get_all/usecase.go | 0 internal/usecase/server/{data => item}/update/contract.go | 2 +- internal/usecase/server/{data => item}/update/model.go | 0 internal/usecase/server/{data => item}/update/usecase.go | 2 +- internal/usecase/server/user/login/contract.go | 2 +- internal/usecase/server/user/refresh_tokens/contract.go | 2 +- internal/usecase/server/user/register/contract.go | 2 +- internal/usecase/server/user/register/usecase.go | 2 +- 31 files changed, 26 insertions(+), 26 deletions(-) rename internal/domain/{ => server}/data/item.go (100%) rename internal/domain/{ => server}/user/user.go (100%) rename internal/usecase/server/{data => item}/create/contract.go (64%) rename internal/usecase/server/{data => item}/create/model.go (100%) rename internal/usecase/server/{data => item}/create/usecase.go (89%) rename internal/usecase/server/{data => item}/delete/contract.go (100%) rename internal/usecase/server/{data => item}/delete/model.go (100%) rename internal/usecase/server/{data => item}/delete/usecase.go (100%) rename internal/usecase/server/{data => item}/get_all/contract.go (75%) rename internal/usecase/server/{data => item}/get_all/model.go (100%) rename internal/usecase/server/{data => item}/get_all/usecase.go (100%) rename internal/usecase/server/{data => item}/update/contract.go (75%) rename internal/usecase/server/{data => item}/update/model.go (100%) rename internal/usecase/server/{data => item}/update/usecase.go (91%) diff --git a/internal/app/server/app.go b/internal/app/server/app.go index 8bb7645..9f506a2 100644 --- a/internal/app/server/app.go +++ b/internal/app/server/app.go @@ -18,10 +18,10 @@ import ( rpcRefreshTokens "github.com/bjlag/go-keeper/internal/rpc/refresh_tokens" rpcRegister "github.com/bjlag/go-keeper/internal/rpc/register" rpcUpdateItem "github.com/bjlag/go-keeper/internal/rpc/update_item" - "github.com/bjlag/go-keeper/internal/usecase/server/data/create" - "github.com/bjlag/go-keeper/internal/usecase/server/data/delete" - "github.com/bjlag/go-keeper/internal/usecase/server/data/get_all" - "github.com/bjlag/go-keeper/internal/usecase/server/data/update" + "github.com/bjlag/go-keeper/internal/usecase/server/item/create" + "github.com/bjlag/go-keeper/internal/usecase/server/item/delete" + "github.com/bjlag/go-keeper/internal/usecase/server/item/get_all" + "github.com/bjlag/go-keeper/internal/usecase/server/item/update" "github.com/bjlag/go-keeper/internal/usecase/server/user/login" rt "github.com/bjlag/go-keeper/internal/usecase/server/user/refresh_tokens" "github.com/bjlag/go-keeper/internal/usecase/server/user/register" diff --git a/internal/domain/data/item.go b/internal/domain/server/data/item.go similarity index 100% rename from internal/domain/data/item.go rename to internal/domain/server/data/item.go diff --git a/internal/domain/user/user.go b/internal/domain/server/user/user.go similarity index 100% rename from internal/domain/user/user.go rename to internal/domain/server/user/user.go diff --git a/internal/infrastructure/store/server/item/model.go b/internal/infrastructure/store/server/item/model.go index 9bff4bc..74b51ea 100644 --- a/internal/infrastructure/store/server/item/model.go +++ b/internal/infrastructure/store/server/item/model.go @@ -5,7 +5,7 @@ import ( "github.com/google/uuid" - "github.com/bjlag/go-keeper/internal/domain/data" + "github.com/bjlag/go-keeper/internal/domain/server/data" ) type row struct { diff --git a/internal/infrastructure/store/server/item/store.go b/internal/infrastructure/store/server/item/store.go index 3698af2..dd706b4 100644 --- a/internal/infrastructure/store/server/item/store.go +++ b/internal/infrastructure/store/server/item/store.go @@ -8,7 +8,7 @@ import ( "github.com/google/uuid" "github.com/jmoiron/sqlx" - model "github.com/bjlag/go-keeper/internal/domain/data" + model "github.com/bjlag/go-keeper/internal/domain/server/data" ) var ErrNotAffectedRows = errors.New("not affected") diff --git a/internal/infrastructure/store/server/user/model.go b/internal/infrastructure/store/server/user/model.go index 19c7cd3..60a5891 100644 --- a/internal/infrastructure/store/server/user/model.go +++ b/internal/infrastructure/store/server/user/model.go @@ -5,7 +5,7 @@ import ( "github.com/google/uuid" - model "github.com/bjlag/go-keeper/internal/domain/user" + model "github.com/bjlag/go-keeper/internal/domain/server/user" ) type row struct { diff --git a/internal/infrastructure/store/server/user/store.go b/internal/infrastructure/store/server/user/store.go index 5fb3b78..1dc822e 100644 --- a/internal/infrastructure/store/server/user/store.go +++ b/internal/infrastructure/store/server/user/store.go @@ -5,11 +5,11 @@ import ( "database/sql" "errors" "fmt" - "github.com/google/uuid" + "github.com/google/uuid" "github.com/jmoiron/sqlx" - model "github.com/bjlag/go-keeper/internal/domain/user" + model "github.com/bjlag/go-keeper/internal/domain/server/user" ) var ErrNotFound = errors.New("user not found") diff --git a/internal/rpc/create_item/contract.go b/internal/rpc/create_item/contract.go index c6ced1d..72b1b0f 100644 --- a/internal/rpc/create_item/contract.go +++ b/internal/rpc/create_item/contract.go @@ -3,7 +3,7 @@ package create_item import ( "context" - "github.com/bjlag/go-keeper/internal/usecase/server/data/create" + "github.com/bjlag/go-keeper/internal/usecase/server/item/create" ) type usecase interface { diff --git a/internal/rpc/create_item/handler.go b/internal/rpc/create_item/handler.go index 70a8374..94c3217 100644 --- a/internal/rpc/create_item/handler.go +++ b/internal/rpc/create_item/handler.go @@ -11,7 +11,7 @@ import ( pb "github.com/bjlag/go-keeper/internal/generated/rpc" "github.com/bjlag/go-keeper/internal/infrastructure/auth" "github.com/bjlag/go-keeper/internal/infrastructure/logger" - "github.com/bjlag/go-keeper/internal/usecase/server/data/create" + "github.com/bjlag/go-keeper/internal/usecase/server/item/create" ) type Handler struct { diff --git a/internal/rpc/delete_item/contract.go b/internal/rpc/delete_item/contract.go index 94f108b..9dc0f2a 100644 --- a/internal/rpc/delete_item/contract.go +++ b/internal/rpc/delete_item/contract.go @@ -3,7 +3,7 @@ package delete_item import ( "context" - "github.com/bjlag/go-keeper/internal/usecase/server/data/delete" + "github.com/bjlag/go-keeper/internal/usecase/server/item/delete" ) type usecase interface { diff --git a/internal/rpc/delete_item/handler.go b/internal/rpc/delete_item/handler.go index 7df0519..3df6944 100644 --- a/internal/rpc/delete_item/handler.go +++ b/internal/rpc/delete_item/handler.go @@ -13,7 +13,7 @@ import ( pb "github.com/bjlag/go-keeper/internal/generated/rpc" "github.com/bjlag/go-keeper/internal/infrastructure/auth" "github.com/bjlag/go-keeper/internal/infrastructure/logger" - "github.com/bjlag/go-keeper/internal/usecase/server/data/delete" + "github.com/bjlag/go-keeper/internal/usecase/server/item/delete" ) type Handler struct { diff --git a/internal/rpc/get_all_items/contract.go b/internal/rpc/get_all_items/contract.go index d6a863c..0963a38 100644 --- a/internal/rpc/get_all_items/contract.go +++ b/internal/rpc/get_all_items/contract.go @@ -3,7 +3,7 @@ package get_all_items import ( "context" - "github.com/bjlag/go-keeper/internal/usecase/server/data/get_all" + "github.com/bjlag/go-keeper/internal/usecase/server/item/get_all" ) type usecase interface { diff --git a/internal/rpc/get_all_items/handler.go b/internal/rpc/get_all_items/handler.go index cc8c6b6..b2100be 100644 --- a/internal/rpc/get_all_items/handler.go +++ b/internal/rpc/get_all_items/handler.go @@ -13,7 +13,7 @@ import ( pb "github.com/bjlag/go-keeper/internal/generated/rpc" "github.com/bjlag/go-keeper/internal/infrastructure/auth" "github.com/bjlag/go-keeper/internal/infrastructure/logger" - "github.com/bjlag/go-keeper/internal/usecase/server/data/get_all" + "github.com/bjlag/go-keeper/internal/usecase/server/item/get_all" ) const ( diff --git a/internal/rpc/update_item/contract.go b/internal/rpc/update_item/contract.go index 9002403..78bb006 100644 --- a/internal/rpc/update_item/contract.go +++ b/internal/rpc/update_item/contract.go @@ -3,7 +3,7 @@ package update_item import ( "context" - "github.com/bjlag/go-keeper/internal/usecase/server/data/update" + "github.com/bjlag/go-keeper/internal/usecase/server/item/update" ) type usecase interface { diff --git a/internal/rpc/update_item/handler.go b/internal/rpc/update_item/handler.go index 096ec6e..84c131d 100644 --- a/internal/rpc/update_item/handler.go +++ b/internal/rpc/update_item/handler.go @@ -13,7 +13,7 @@ import ( pb "github.com/bjlag/go-keeper/internal/generated/rpc" "github.com/bjlag/go-keeper/internal/infrastructure/auth" "github.com/bjlag/go-keeper/internal/infrastructure/logger" - "github.com/bjlag/go-keeper/internal/usecase/server/data/update" + "github.com/bjlag/go-keeper/internal/usecase/server/item/update" ) type Handler struct { diff --git a/internal/usecase/server/data/create/contract.go b/internal/usecase/server/item/create/contract.go similarity index 64% rename from internal/usecase/server/data/create/contract.go rename to internal/usecase/server/item/create/contract.go index 5a59dc8..2ac92cc 100644 --- a/internal/usecase/server/data/create/contract.go +++ b/internal/usecase/server/item/create/contract.go @@ -3,7 +3,7 @@ package create import ( "context" - model "github.com/bjlag/go-keeper/internal/domain/data" + model "github.com/bjlag/go-keeper/internal/domain/server/data" ) type store interface { diff --git a/internal/usecase/server/data/create/model.go b/internal/usecase/server/item/create/model.go similarity index 100% rename from internal/usecase/server/data/create/model.go rename to internal/usecase/server/item/create/model.go diff --git a/internal/usecase/server/data/create/usecase.go b/internal/usecase/server/item/create/usecase.go similarity index 89% rename from internal/usecase/server/data/create/usecase.go rename to internal/usecase/server/item/create/usecase.go index ba5c769..b0a28c0 100644 --- a/internal/usecase/server/data/create/usecase.go +++ b/internal/usecase/server/item/create/usecase.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - model "github.com/bjlag/go-keeper/internal/domain/data" + model "github.com/bjlag/go-keeper/internal/domain/server/data" ) type Usecase struct { diff --git a/internal/usecase/server/data/delete/contract.go b/internal/usecase/server/item/delete/contract.go similarity index 100% rename from internal/usecase/server/data/delete/contract.go rename to internal/usecase/server/item/delete/contract.go diff --git a/internal/usecase/server/data/delete/model.go b/internal/usecase/server/item/delete/model.go similarity index 100% rename from internal/usecase/server/data/delete/model.go rename to internal/usecase/server/item/delete/model.go diff --git a/internal/usecase/server/data/delete/usecase.go b/internal/usecase/server/item/delete/usecase.go similarity index 100% rename from internal/usecase/server/data/delete/usecase.go rename to internal/usecase/server/item/delete/usecase.go diff --git a/internal/usecase/server/data/get_all/contract.go b/internal/usecase/server/item/get_all/contract.go similarity index 75% rename from internal/usecase/server/data/get_all/contract.go rename to internal/usecase/server/item/get_all/contract.go index e2b711f..b668a6a 100644 --- a/internal/usecase/server/data/get_all/contract.go +++ b/internal/usecase/server/item/get_all/contract.go @@ -5,7 +5,7 @@ import ( "github.com/google/uuid" - model "github.com/bjlag/go-keeper/internal/domain/data" + model "github.com/bjlag/go-keeper/internal/domain/server/data" ) type dataStore interface { diff --git a/internal/usecase/server/data/get_all/model.go b/internal/usecase/server/item/get_all/model.go similarity index 100% rename from internal/usecase/server/data/get_all/model.go rename to internal/usecase/server/item/get_all/model.go diff --git a/internal/usecase/server/data/get_all/usecase.go b/internal/usecase/server/item/get_all/usecase.go similarity index 100% rename from internal/usecase/server/data/get_all/usecase.go rename to internal/usecase/server/item/get_all/usecase.go diff --git a/internal/usecase/server/data/update/contract.go b/internal/usecase/server/item/update/contract.go similarity index 75% rename from internal/usecase/server/data/update/contract.go rename to internal/usecase/server/item/update/contract.go index 6c9052d..eeb6092 100644 --- a/internal/usecase/server/data/update/contract.go +++ b/internal/usecase/server/item/update/contract.go @@ -5,7 +5,7 @@ import ( "github.com/google/uuid" - model "github.com/bjlag/go-keeper/internal/domain/data" + model "github.com/bjlag/go-keeper/internal/domain/server/data" ) type store interface { diff --git a/internal/usecase/server/data/update/model.go b/internal/usecase/server/item/update/model.go similarity index 100% rename from internal/usecase/server/data/update/model.go rename to internal/usecase/server/item/update/model.go diff --git a/internal/usecase/server/data/update/usecase.go b/internal/usecase/server/item/update/usecase.go similarity index 91% rename from internal/usecase/server/data/update/usecase.go rename to internal/usecase/server/item/update/usecase.go index cbb72a2..dfcd552 100644 --- a/internal/usecase/server/data/update/usecase.go +++ b/internal/usecase/server/item/update/usecase.go @@ -6,7 +6,7 @@ import ( "fmt" "time" - model "github.com/bjlag/go-keeper/internal/domain/data" + model "github.com/bjlag/go-keeper/internal/domain/server/data" "github.com/bjlag/go-keeper/internal/infrastructure/store/server/item" ) diff --git a/internal/usecase/server/user/login/contract.go b/internal/usecase/server/user/login/contract.go index e674949..b39223b 100644 --- a/internal/usecase/server/user/login/contract.go +++ b/internal/usecase/server/user/login/contract.go @@ -3,7 +3,7 @@ package login import ( "context" - model "github.com/bjlag/go-keeper/internal/domain/user" + model "github.com/bjlag/go-keeper/internal/domain/server/user" ) type userStore interface { diff --git a/internal/usecase/server/user/refresh_tokens/contract.go b/internal/usecase/server/user/refresh_tokens/contract.go index 98481b2..dc357f9 100644 --- a/internal/usecase/server/user/refresh_tokens/contract.go +++ b/internal/usecase/server/user/refresh_tokens/contract.go @@ -5,7 +5,7 @@ import ( "github.com/google/uuid" - model "github.com/bjlag/go-keeper/internal/domain/user" + model "github.com/bjlag/go-keeper/internal/domain/server/user" ) type userStore interface { diff --git a/internal/usecase/server/user/register/contract.go b/internal/usecase/server/user/register/contract.go index 51e93de..168915e 100644 --- a/internal/usecase/server/user/register/contract.go +++ b/internal/usecase/server/user/register/contract.go @@ -3,7 +3,7 @@ package register import ( "context" - model "github.com/bjlag/go-keeper/internal/domain/user" + model "github.com/bjlag/go-keeper/internal/domain/server/user" ) type userStore interface { diff --git a/internal/usecase/server/user/register/usecase.go b/internal/usecase/server/user/register/usecase.go index 53b4d21..ea76293 100644 --- a/internal/usecase/server/user/register/usecase.go +++ b/internal/usecase/server/user/register/usecase.go @@ -9,7 +9,7 @@ import ( "github.com/google/uuid" "golang.org/x/crypto/bcrypt" - model "github.com/bjlag/go-keeper/internal/domain/user" + model "github.com/bjlag/go-keeper/internal/domain/server/user" storeUser "github.com/bjlag/go-keeper/internal/infrastructure/store/server/user" ) From 75384d863ded4a50b8fc02da1d420d3ffeddb264 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Fri, 7 Mar 2025 19:47:51 +0300 Subject: [PATCH 49/94] client: delete usecase --- .../infrastructure/store/client/item/store.go | 16 ++++++++++ .../usecase/client/item/delete/contract.go | 11 +++++++ .../usecase/client/item/delete/usecase.go | 29 +++++++++++++++++++ internal/usecase/client/item/save/usecase.go | 2 +- 4 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 internal/usecase/client/item/delete/contract.go create mode 100644 internal/usecase/client/item/delete/usecase.go diff --git a/internal/infrastructure/store/client/item/store.go b/internal/infrastructure/store/client/item/store.go index 248393a..af5f0df 100644 --- a/internal/infrastructure/store/client/item/store.go +++ b/internal/infrastructure/store/client/item/store.go @@ -5,6 +5,7 @@ import ( "database/sql" "errors" "fmt" + "github.com/google/uuid" "github.com/jmoiron/sqlx" @@ -48,6 +49,21 @@ func (s *Store) SaveItem(ctx context.Context, item model.Item) error { return nil } +func (s *Store) DeleteItem(ctx context.Context, guid uuid.UUID) error { + const op = prefixOp + "DeleteItem" + + query := ` + DELETE FROM items WHERE guid = $1 + ` + + _, err := s.db.ExecContext(ctx, query, guid) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + return nil +} + // todo пределать на модель model.Item func (s *Store) SaveItems(ctx context.Context, items []model.RawItem) error { const op = prefixOp + "SaveItems" diff --git a/internal/usecase/client/item/delete/contract.go b/internal/usecase/client/item/delete/contract.go new file mode 100644 index 0000000..d9b3f6f --- /dev/null +++ b/internal/usecase/client/item/delete/contract.go @@ -0,0 +1,11 @@ +package delete + +import ( + "context" + + "github.com/google/uuid" +) + +type store interface { + DeleteItem(ctx context.Context, guid uuid.UUID) error +} diff --git a/internal/usecase/client/item/delete/usecase.go b/internal/usecase/client/item/delete/usecase.go new file mode 100644 index 0000000..6a2358f --- /dev/null +++ b/internal/usecase/client/item/delete/usecase.go @@ -0,0 +1,29 @@ +package delete + +import ( + "context" + "fmt" + + "github.com/google/uuid" +) + +type Usecase struct { + store store +} + +func NewUsecase(store store) *Usecase { + return &Usecase{ + store: store, + } +} + +func (u *Usecase) Do(ctx context.Context, guid uuid.UUID) error { + const op = "usecase.item.delete.Do" + + err := u.store.DeleteItem(ctx, guid) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + return nil +} diff --git a/internal/usecase/client/item/save/usecase.go b/internal/usecase/client/item/save/usecase.go index 958f9c3..65e0514 100644 --- a/internal/usecase/client/item/save/usecase.go +++ b/internal/usecase/client/item/save/usecase.go @@ -18,7 +18,7 @@ func NewUsecase(store store) *Usecase { } func (u *Usecase) Do(ctx context.Context, item model.Item) error { - const op = "usecase.saveItem.Do" + const op = "usecase.item.save.Do" err := u.store.SaveItem(ctx, item) if err != nil { From dd368314ddfbf0b190eb35c3adaf926fd511341f Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Fri, 7 Mar 2025 20:00:07 +0300 Subject: [PATCH 50/94] client: create usecase --- .../infrastructure/store/client/item/model.go | 30 +++++++------- .../infrastructure/store/client/item/store.go | 41 ++++++++++++++++++- .../usecase/client/item/create/contract.go | 11 +++++ .../usecase/client/item/create/usecase.go | 29 +++++++++++++ 4 files changed, 95 insertions(+), 16 deletions(-) create mode 100644 internal/usecase/client/item/create/contract.go create mode 100644 internal/usecase/client/item/create/usecase.go diff --git a/internal/infrastructure/store/client/item/model.go b/internal/infrastructure/store/client/item/model.go index 7d4f4d9..855ad4a 100644 --- a/internal/infrastructure/store/client/item/model.go +++ b/internal/infrastructure/store/client/item/model.go @@ -10,13 +10,13 @@ import ( ) type row struct { - GUID uuid.UUID `db:"guid"` - CategoryID model.Category `db:"category_id"` - Title string `db:"title"` - Value *[]byte `db:"value"` - Notes string `db:"notes"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` + GUID uuid.UUID `db:"guid"` + Category model.Category `db:"category_id"` + Title string `db:"title"` + Value *[]byte `db:"value"` + Notes string `db:"notes"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` } func toRow(model model.Item) (row, error) { @@ -26,13 +26,13 @@ func toRow(model model.Item) (row, error) { } return row{ - GUID: model.GUID, - CategoryID: model.Category, - Title: model.Title, - Value: &value, - Notes: model.Notes, - CreatedAt: model.CreatedAt, - UpdatedAt: model.UpdatedAt, + GUID: model.GUID, + Category: model.Category, + Title: model.Title, + Value: &value, + Notes: model.Notes, + CreatedAt: model.CreatedAt, + UpdatedAt: model.UpdatedAt, }, nil } @@ -47,7 +47,7 @@ func toModels(rows []row) []model.RawItem { func (r *row) toModel() model.RawItem { return model.RawItem{ GUID: r.GUID, - Category: r.CategoryID, + Category: r.Category, Title: r.Title, Value: r.Value, Notes: r.Notes, diff --git a/internal/infrastructure/store/client/item/store.go b/internal/infrastructure/store/client/item/store.go index af5f0df..72398d6 100644 --- a/internal/infrastructure/store/client/item/store.go +++ b/internal/infrastructure/store/client/item/store.go @@ -3,10 +3,11 @@ package item import ( "context" "database/sql" + "encoding/json" "errors" "fmt" - "github.com/google/uuid" + "github.com/google/uuid" "github.com/jmoiron/sqlx" model "github.com/bjlag/go-keeper/internal/domain/client" @@ -49,6 +50,44 @@ func (s *Store) SaveItem(ctx context.Context, item model.Item) error { return nil } +func (s *Store) CreateItem(ctx context.Context, item model.Item) error { + const op = prefixOp + "CreateItem" + + query := ` + INSERT INTO items(guid, category_id, title, value, notes, updated_at, created_at) + VALUES (:guid, :category_id, :title, :value, :notes, :updated_at, :created_at) + ` + + var ( + value *[]byte + ) + + if item.Value != nil { + v, err := json.Marshal(item.Value) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + value = &v + } + + args := row{ + GUID: item.GUID, + Category: item.Category, + Title: item.Title, + Value: value, + Notes: item.Notes, + CreatedAt: item.CreatedAt, + UpdatedAt: item.UpdatedAt, + } + + _, err := s.db.NamedExecContext(ctx, query, args) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + return nil +} + func (s *Store) DeleteItem(ctx context.Context, guid uuid.UUID) error { const op = prefixOp + "DeleteItem" diff --git a/internal/usecase/client/item/create/contract.go b/internal/usecase/client/item/create/contract.go new file mode 100644 index 0000000..c7fcfdb --- /dev/null +++ b/internal/usecase/client/item/create/contract.go @@ -0,0 +1,11 @@ +package create + +import ( + "context" + + model "github.com/bjlag/go-keeper/internal/domain/client" +) + +type store interface { + CreateItem(ctx context.Context, item model.Item) error +} diff --git a/internal/usecase/client/item/create/usecase.go b/internal/usecase/client/item/create/usecase.go new file mode 100644 index 0000000..bf4c2e7 --- /dev/null +++ b/internal/usecase/client/item/create/usecase.go @@ -0,0 +1,29 @@ +package create + +import ( + "context" + "fmt" + + model "github.com/bjlag/go-keeper/internal/domain/client" +) + +type Usecase struct { + store store +} + +func NewUsecase(store store) *Usecase { + return &Usecase{ + store: store, + } +} + +func (u *Usecase) Do(ctx context.Context, item model.Item) error { + const op = "usecase.item.create.Do" + + err := u.store.CreateItem(ctx, item) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + return nil +} From 05273f67dd34784f2f5479afb7c6952ae1230711 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Fri, 7 Mar 2025 20:07:42 +0300 Subject: [PATCH 51/94] client: delete ui --- internal/app/client/app.go | 8 +++++-- internal/cli/model/item/password/model.go | 29 +++++++++++++++-------- internal/cli/model/item/text/model.go | 25 ++++++++++++++----- 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/internal/app/client/app.go b/internal/app/client/app.go index 2542906..c2cafed 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -18,6 +18,8 @@ import ( rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" sItem "github.com/bjlag/go-keeper/internal/infrastructure/store/client/item" "github.com/bjlag/go-keeper/internal/infrastructure/store/client/token" + "github.com/bjlag/go-keeper/internal/usecase/client/item/create" + "github.com/bjlag/go-keeper/internal/usecase/client/item/delete" "github.com/bjlag/go-keeper/internal/usecase/client/item/save" "github.com/bjlag/go-keeper/internal/usecase/client/login" "github.com/bjlag/go-keeper/internal/usecase/client/register" @@ -63,7 +65,9 @@ func (a *App) Run(ctx context.Context) error { ucLogin := login.NewUsecase(rpcClient) ucRegister := register.NewUsecase(rpcClient) ucSync := sync.NewUsecase(rpcClient, storeItem) + ucCreateItem := create.NewUsecase(storeItem) ucSaveItem := save.NewUsecase(storeItem) + ucDeleteItem := delete.NewUsecase(storeItem) fetchItem := item.NewFetcher(storeItem) @@ -73,8 +77,8 @@ func (a *App) Run(ctx context.Context) error { master.WithLoginForm(formLogin.InitModel(ucLogin)), master.WithRegisterForm(formRegister.InitModel(ucRegister)), master.WithListFormForm(list.InitModel(ucSync, fetchItem)), - master.WithPasswordItemForm(password.InitModel(ucSaveItem)), - master.WithTextItemForm(text.InitModel(ucSaveItem)), + master.WithPasswordItemForm(password.InitModel(ucCreateItem, ucSaveItem, ucDeleteItem)), + master.WithTextItemForm(text.InitModel(ucCreateItem, ucSaveItem, ucDeleteItem)), ) f, err := tea.LogToFile("debug.log", "debug") diff --git a/internal/cli/model/item/password/model.go b/internal/cli/model/item/password/model.go index c5bebde..56dd311 100644 --- a/internal/cli/model/item/password/model.go +++ b/internal/cli/model/item/password/model.go @@ -20,6 +20,8 @@ import ( "github.com/bjlag/go-keeper/internal/cli/element/button" tarea "github.com/bjlag/go-keeper/internal/cli/element/textarea" tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" + "github.com/bjlag/go-keeper/internal/usecase/client/item/create" + "github.com/bjlag/go-keeper/internal/usecase/client/item/delete" "github.com/bjlag/go-keeper/internal/usecase/client/item/save" ) @@ -47,14 +49,18 @@ type Model struct { guid uuid.UUID category client.Category - usecaseSave *save.Usecase + usecaseCreate *create.Usecase + usecaseSave *save.Usecase + usecaseDelete *delete.Usecase } -func InitModel(usecaseItem *save.Usecase) *Model { +func InitModel(usecaseCreate *create.Usecase, usecaseSave *save.Usecase, usecaseDelete *delete.Usecase) *Model { return &Model{ - help: help.New(), - header: "Регистрация", - usecaseSave: usecaseItem, + help: help.New(), + header: "Регистрация", + usecaseCreate: usecaseCreate, + usecaseSave: usecaseSave, + usecaseDelete: usecaseDelete, } } @@ -154,11 +160,10 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch f.pos { case posEditBtn: - err := f.edit() - if err != nil { - f.err = err - return f, nil - } + f.err = f.edit() + return f, nil + case posDeleteBtn: + f.err = f.delete() return f, nil case posBackBtn: return f.backModel.Update(common.BackMessage{ @@ -265,3 +270,7 @@ func (f *Model) edit() error { return f.usecaseSave.Do(context.TODO(), i) } + +func (f *Model) delete() error { + return f.usecaseDelete.Do(context.TODO(), f.guid) +} diff --git a/internal/cli/model/item/text/model.go b/internal/cli/model/item/text/model.go index c241139..c7ba0b3 100644 --- a/internal/cli/model/item/text/model.go +++ b/internal/cli/model/item/text/model.go @@ -3,13 +3,13 @@ package text import ( "context" "errors" - "github.com/google/uuid" "strings" "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" + "github.com/google/uuid" "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/element" @@ -18,6 +18,8 @@ import ( tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/domain/client" + "github.com/bjlag/go-keeper/internal/usecase/client/item/create" + "github.com/bjlag/go-keeper/internal/usecase/client/item/delete" "github.com/bjlag/go-keeper/internal/usecase/client/item/save" "github.com/charmbracelet/bubbles/textarea" ) @@ -44,14 +46,18 @@ type Model struct { guid uuid.UUID category client.Category - usecaseSave *save.Usecase + usecaseCreate *create.Usecase + usecaseSave *save.Usecase + usecaseDelete *delete.Usecase } -func InitModel(usecaseItem *save.Usecase) *Model { +func InitModel(usecaseCreate *create.Usecase, usecaseSave *save.Usecase, usecaseDelete *delete.Usecase) *Model { return &Model{ - help: help.New(), - header: "Регистрация", - usecaseSave: usecaseItem, + help: help.New(), + header: "Регистрация", + usecaseCreate: usecaseCreate, + usecaseSave: usecaseSave, + usecaseDelete: usecaseDelete, } } @@ -145,6 +151,9 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case posEditBtn: f.err = f.edit() return f, nil + case posDeleteBtn: + f.err = f.delete() + return f, nil case posBackBtn: return f.backModel.Update(common.BackMessage{ State: f.backState, @@ -246,3 +255,7 @@ func (f *Model) edit() error { return f.usecaseSave.Do(context.TODO(), i) } + +func (f *Model) delete() error { + return f.usecaseDelete.Do(context.TODO(), f.guid) +} From 3e696054384cf0c535bfc87037bd053c7b0dddd9 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Fri, 7 Mar 2025 23:52:06 +0300 Subject: [PATCH 52/94] client: ui main page --- internal/cli/element/button/button.go | 14 +++- internal/cli/element/textinput/textinput.go | 8 +-- internal/cli/model/list/model.go | 2 + internal/cli/model/master/message.go | 3 + internal/cli/model/master/model.go | 75 +++++++++++++++++++-- 5 files changed, 90 insertions(+), 12 deletions(-) create mode 100644 internal/cli/model/master/message.go diff --git a/internal/cli/element/button/button.go b/internal/cli/element/button/button.go index 2eb51d1..8de0752 100644 --- a/internal/cli/element/button/button.go +++ b/internal/cli/element/button/button.go @@ -23,11 +23,23 @@ func NewButton(text string) Button { } } -func CreateDefaultButton(text string) Button { +type Option func(m *Button) + +func WithFocused() Option { + return func(m *Button) { + m.Focus() + } +} + +func CreateDefaultButton(text string, opts ...Option) Button { b := NewButton(text) b.FocusedStyle = style.FocusedStyle b.BlurredStyle = style.BlurredStyle + for _, opt := range opts { + opt(&b) + } + return b } diff --git a/internal/cli/element/textinput/textinput.go b/internal/cli/element/textinput/textinput.go index e56fe52..d489057 100644 --- a/internal/cli/element/textinput/textinput.go +++ b/internal/cli/element/textinput/textinput.go @@ -6,21 +6,21 @@ import ( "github.com/bjlag/go-keeper/internal/cli/style" ) -type TextInputOption func(m *textinput.Model) +type Option func(m *textinput.Model) -func WithFocused() TextInputOption { +func WithFocused() Option { return func(m *textinput.Model) { m.Focus() } } -func WithValue(value string) TextInputOption { +func WithValue(value string) Option { return func(m *textinput.Model) { m.SetValue(value) } } -func CreateDefaultTextInput(placeholder string, opts ...TextInputOption) textinput.Model { +func CreateDefaultTextInput(placeholder string, opts ...Option) textinput.Model { m := textinput.New() m.Cursor.Style = style.CursorStyle diff --git a/internal/cli/model/list/model.go b/internal/cli/model/list/model.go index 7f774b7..1454cca 100644 --- a/internal/cli/model/list/model.go +++ b/internal/cli/model/list/model.go @@ -146,6 +146,8 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return f, nil case key.Matches(msg, common.Keys.Back): switch f.state { + case stateCategoryList: + return f.main.Update(common.BackMessage{}) case stateItemList: return f.Update(OpenCategoryListMessage{}) } diff --git a/internal/cli/model/master/message.go b/internal/cli/model/master/message.go new file mode 100644 index 0000000..3e29336 --- /dev/null +++ b/internal/cli/model/master/message.go @@ -0,0 +1,3 @@ +package master + +type Open struct{} diff --git a/internal/cli/model/master/model.go b/internal/cli/model/master/model.go index 955ed26..6e2dfc5 100644 --- a/internal/cli/model/master/model.go +++ b/internal/cli/model/master/model.go @@ -1,7 +1,6 @@ package master import ( - "github.com/bjlag/go-keeper/internal/domain/client" "strings" "github.com/charmbracelet/bubbles/help" @@ -9,18 +8,28 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/bjlag/go-keeper/internal/cli/common" + "github.com/bjlag/go-keeper/internal/cli/element/button" "github.com/bjlag/go-keeper/internal/cli/model/item/password" "github.com/bjlag/go-keeper/internal/cli/model/item/text" listf "github.com/bjlag/go-keeper/internal/cli/model/list" "github.com/bjlag/go-keeper/internal/cli/model/login" "github.com/bjlag/go-keeper/internal/cli/model/register" "github.com/bjlag/go-keeper/internal/cli/style" + "github.com/bjlag/go-keeper/internal/domain/client" "github.com/bjlag/go-keeper/internal/infrastructure/store/client/token" ) +const ( + posViewBtn int = iota + posCreateBtn + posCloseBtn +) + type Model struct { - help help.Model - header string + help help.Model + header string + elements []interface{} + pos int formLogin *login.Model formRegister *register.Model @@ -35,6 +44,11 @@ func InitModel(opts ...Option) *Model { m := &Model{ help: help.New(), header: "Go Keeper", + elements: []interface{}{ + posViewBtn: button.CreateDefaultButton("Просмотр", button.WithFocused()), + posCreateBtn: button.CreateDefaultButton("Создать"), + posCloseBtn: button.CreateDefaultButton("Выйти"), + }, } for _, opt := range opts { @@ -47,7 +61,6 @@ func InitModel(opts ...Option) *Model { func (m *Model) Init() tea.Cmd { return tea.Batch( func() tea.Msg { - //return message.SuccessLoginMessage{} return login.OpenMessage{} }, ) @@ -57,10 +70,53 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch { + case key.Matches(msg, common.Keys.Quit): + return m, tea.Quit + case key.Matches(msg, common.Keys.Enter): + switch m.pos { + case posViewBtn: + return m.formList.Update(listf.GetAllDataMessage{}) + case posCreateBtn: + //m.formList.Update(listf.GetAllDataMessage{}) + case posCloseBtn: + return m, tea.Quit + } + case key.Matches(msg, common.Keys.Navigation): + if key.Matches(msg, common.Keys.Down, common.Keys.Tab) { + m.pos++ + } else { + m.pos-- + } + + if m.pos > len(m.elements)-1 { + m.pos = 0 + } else if m.pos < 0 { + m.pos = len(m.elements) - 1 + } + + for i := range m.elements { + switch e := m.elements[i].(type) { + case button.Button: + if i == m.pos { + e.Focus() + m.elements[i] = e + continue + } + e.Blur() + m.elements[i] = e + } + } + + return m, nil case key.Matches(msg, common.Keys.Quit): return m, tea.Quit } + case Open: + return m, nil + case common.BackMessage: + return m.Update(Open{}) + // Forms case login.OpenMessage: return m.formLogin.Update(msg) @@ -89,13 +145,11 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // Success case login.SuccessMessage: m.storeTokens.SaveTokens(msg.AccessToken, msg.RefreshToken) - return m.formList.Update(listf.GetAllDataMessage{}) case register.SuccessMessage: m.storeTokens.SaveTokens(msg.AccessToken, msg.RefreshToken) - return m.formList.Update(listf.GetAllDataMessage{}) } - return m, nil + return m.Update(Open{}) } func (m *Model) View() string { @@ -104,5 +158,12 @@ func (m *Model) View() string { b.WriteString(style.TitleStyle.Render(m.header)) b.WriteRune('\n') + for i := range m.elements { + if e, ok := m.elements[i].(button.Button); ok { + b.WriteString(e.String()) + b.WriteRune('\n') + } + } + return b.String() } From 6fc5f759ebc5854492a65b72fb3ab0db3ca696a2 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Sat, 8 Mar 2025 00:24:44 +0300 Subject: [PATCH 53/94] client: create item ui --- internal/app/client/app.go | 8 +- internal/cli/common/message.go | 4 +- internal/cli/model/item/create/message.go | 3 + internal/cli/model/item/create/model.go | 99 +++++++ internal/cli/model/item/password/action.go | 43 +++ internal/cli/model/item/password/message.go | 4 +- internal/cli/model/item/password/model.go | 273 ++++++++++-------- internal/cli/model/item/text/action.go | 35 +++ internal/cli/model/item/text/message.go | 4 +- internal/cli/model/item/text/model.go | 237 ++++++++------- internal/cli/model/list/model.go | 3 +- internal/cli/model/master/model.go | 22 +- internal/cli/model/master/option.go | 10 +- .../infrastructure/store/client/item/store.go | 6 +- .../usecase/client/item/create/usecase.go | 4 + .../client/item/{save => edit}/contract.go | 2 +- .../client/item/{save => edit}/usecase.go | 7 +- 17 files changed, 523 insertions(+), 241 deletions(-) create mode 100644 internal/cli/model/item/create/message.go create mode 100644 internal/cli/model/item/create/model.go create mode 100644 internal/cli/model/item/password/action.go create mode 100644 internal/cli/model/item/text/action.go rename internal/usecase/client/item/{save => edit}/contract.go (92%) rename internal/usecase/client/item/{save => edit}/usecase.go (81%) diff --git a/internal/app/client/app.go b/internal/app/client/app.go index c2cafed..d64ca6e 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -7,6 +7,7 @@ import ( tea "github.com/charmbracelet/bubbletea" "go.uber.org/zap" + formCreate "github.com/bjlag/go-keeper/internal/cli/model/item/create" "github.com/bjlag/go-keeper/internal/cli/model/item/password" "github.com/bjlag/go-keeper/internal/cli/model/item/text" "github.com/bjlag/go-keeper/internal/cli/model/list" @@ -20,7 +21,7 @@ import ( "github.com/bjlag/go-keeper/internal/infrastructure/store/client/token" "github.com/bjlag/go-keeper/internal/usecase/client/item/create" "github.com/bjlag/go-keeper/internal/usecase/client/item/delete" - "github.com/bjlag/go-keeper/internal/usecase/client/item/save" + "github.com/bjlag/go-keeper/internal/usecase/client/item/edit" "github.com/bjlag/go-keeper/internal/usecase/client/login" "github.com/bjlag/go-keeper/internal/usecase/client/register" "github.com/bjlag/go-keeper/internal/usecase/client/sync" @@ -66,7 +67,7 @@ func (a *App) Run(ctx context.Context) error { ucRegister := register.NewUsecase(rpcClient) ucSync := sync.NewUsecase(rpcClient, storeItem) ucCreateItem := create.NewUsecase(storeItem) - ucSaveItem := save.NewUsecase(storeItem) + ucSaveItem := edit.NewUsecase(storeItem) ucDeleteItem := delete.NewUsecase(storeItem) fetchItem := item.NewFetcher(storeItem) @@ -76,7 +77,8 @@ func (a *App) Run(ctx context.Context) error { master.WithLoginForm(formLogin.InitModel(ucLogin)), master.WithRegisterForm(formRegister.InitModel(ucRegister)), - master.WithListFormForm(list.InitModel(ucSync, fetchItem)), + master.WithCreatForm(formCreate.InitModel()), + master.WithListForm(list.InitModel(ucSync, fetchItem)), master.WithPasswordItemForm(password.InitModel(ucCreateItem, ucSaveItem, ucDeleteItem)), master.WithTextItemForm(text.InitModel(ucCreateItem, ucSaveItem, ucDeleteItem)), ) diff --git a/internal/cli/common/message.go b/internal/cli/common/message.go index 5752bd5..08e4b49 100644 --- a/internal/cli/common/message.go +++ b/internal/cli/common/message.go @@ -4,6 +4,7 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/bjlag/go-keeper/internal/cli/element/list" + "github.com/bjlag/go-keeper/internal/domain/client" ) type BackMessage struct { @@ -13,5 +14,6 @@ type BackMessage struct { type OpenItemMessage struct { BackModel tea.Model BackState int - Item list.Item + Item *list.Item + Category client.Category } diff --git a/internal/cli/model/item/create/message.go b/internal/cli/model/item/create/message.go new file mode 100644 index 0000000..e938750 --- /dev/null +++ b/internal/cli/model/item/create/message.go @@ -0,0 +1,3 @@ +package create + +type Open struct{} diff --git a/internal/cli/model/item/create/model.go b/internal/cli/model/item/create/model.go new file mode 100644 index 0000000..662e954 --- /dev/null +++ b/internal/cli/model/item/create/model.go @@ -0,0 +1,99 @@ +package create + +import ( + "strings" + + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + + "github.com/bjlag/go-keeper/internal/cli/common" + elist "github.com/bjlag/go-keeper/internal/cli/element/list" + "github.com/bjlag/go-keeper/internal/cli/style" + "github.com/bjlag/go-keeper/internal/domain/client" + "github.com/bjlag/go-keeper/internal/fetcher/item" + "github.com/bjlag/go-keeper/internal/usecase/client/sync" +) + +const ( + defaultWidth = 40 + listHeight = 14 +) + +type Model struct { + main tea.Model + help help.Model + header string + categories list.Model + err error + + usecaseSync *sync.Usecase + fetcherItem *item.Fetcher +} + +func InitModel() *Model { + f := &Model{ + help: help.New(), + header: "Создание", + categories: elist.CreateDefaultList("Выберите категорию:", defaultWidth, listHeight, elist.CategoryDelegate{}, + elist.Category{Category: client.CategoryPassword, Title: client.CategoryPassword.String()}, + elist.Category{Category: client.CategoryText, Title: client.CategoryText.String()}, + elist.Category{Category: client.CategoryBlob, Title: client.CategoryBlob.String()}, + elist.Category{Category: client.CategoryBankCard, Title: client.CategoryBankCard.String()}, + ), + } + + return f +} + +func (f *Model) SetMainModel(m tea.Model) { + f.main = m +} + +func (f *Model) Init() tea.Cmd { + return nil +} + +func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case Open: + return f, nil + case tea.KeyMsg: + switch { + case key.Matches(msg, common.Keys.Enter): + if c, ok := f.categories.SelectedItem().(elist.Category); ok { + return f.main.Update(common.OpenItemMessage{ + BackModel: f, + Category: c.Category, + }) + } + + return f, nil + case key.Matches(msg, common.Keys.Back): + return f.main.Update(common.BackMessage{}) + } + } + + var cmd tea.Cmd + f.categories, cmd = f.categories.Update(msg) + + return f, cmd +} + +func (f *Model) View() string { + var b strings.Builder + + b.WriteString(style.TitleStyle.Render(f.header)) + b.WriteRune('\n') + + b.WriteString(f.categories.View()) + + // выводим прочие ошибки + if f.err != nil { + b.WriteRune('\n') + b.WriteString(style.ErrorBlockStyle.Render(f.err.Error())) + } + + return b.String() +} diff --git a/internal/cli/model/item/password/action.go b/internal/cli/model/item/password/action.go new file mode 100644 index 0000000..d58f274 --- /dev/null +++ b/internal/cli/model/item/password/action.go @@ -0,0 +1,43 @@ +package password + +import ( + "context" + "github.com/google/uuid" + + "github.com/bjlag/go-keeper/internal/cli/element" + "github.com/bjlag/go-keeper/internal/domain/client" +) + +func (m *Model) saveAction() error { + item := client.Item{ + GUID: uuid.New(), + Category: client.CategoryPassword, + Title: element.GetValue(m.elements, posCreateTitle), + Value: client.Password{ + Login: element.GetValue(m.elements, posCreateLogin), + Password: element.GetValue(m.elements, posCreatePassword), + }, + Notes: element.GetValue(m.elements, posCreateNotes), + } + + return m.usecaseCreate.Do(context.TODO(), item) +} + +func (m *Model) editAction() error { + i := client.Item{ + GUID: m.guid, + Category: m.category, + Title: element.GetValue(m.elements, posEditTitle), + Value: client.Password{ + Login: element.GetValue(m.elements, posEditLogin), + Password: element.GetValue(m.elements, posEditPassword), + }, + Notes: element.GetValue(m.elements, posEditNotes), + } + + return m.usecaseEdit.Do(context.TODO(), i) +} + +func (m *Model) deleteAction() error { + return m.usecaseDelete.Do(context.TODO(), m.guid) +} diff --git a/internal/cli/model/item/password/message.go b/internal/cli/model/item/password/message.go index b8e9a32..5c9e14e 100644 --- a/internal/cli/model/item/password/message.go +++ b/internal/cli/model/item/password/message.go @@ -6,8 +6,8 @@ import ( "github.com/bjlag/go-keeper/internal/cli/element/list" ) -type OpenMessage struct { +type OpenMsg struct { BackModel tea.Model BackState int - Item list.Item + Item *list.Item } diff --git a/internal/cli/model/item/password/model.go b/internal/cli/model/item/password/model.go index 56dd311..b94e3b4 100644 --- a/internal/cli/model/item/password/model.go +++ b/internal/cli/model/item/password/model.go @@ -1,44 +1,63 @@ package password import ( - "context" "errors" "strings" "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/textarea" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/google/uuid" "github.com/bjlag/go-keeper/internal/cli/common" - "github.com/bjlag/go-keeper/internal/cli/element" - "github.com/bjlag/go-keeper/internal/cli/style" - "github.com/bjlag/go-keeper/internal/domain/client" - "github.com/charmbracelet/bubbles/textarea" - "github.com/bjlag/go-keeper/internal/cli/element/button" tarea "github.com/bjlag/go-keeper/internal/cli/element/textarea" tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" + "github.com/bjlag/go-keeper/internal/cli/style" + "github.com/bjlag/go-keeper/internal/domain/client" "github.com/bjlag/go-keeper/internal/usecase/client/item/create" "github.com/bjlag/go-keeper/internal/usecase/client/item/delete" - "github.com/bjlag/go-keeper/internal/usecase/client/item/save" + "github.com/bjlag/go-keeper/internal/usecase/client/item/edit" +) + +const ( + posEditTitle int = iota + posEditLogin + posEditPassword + posEditNotes + posEditEditBtn + posEditDeleteBtn + posEditBackBtn +) + +const ( + posCreateTitle int = iota + posCreateLogin + posCreatePassword + posCreateNotes + posCreateSaveBtn + posCreateBackBtn ) +type state int + const ( - posTitle int = iota - posLogin - posPassword - posNotes - posEditBtn - posDeleteBtn - posBackBtn + stateCreate state = iota + stateEdit +) + +var ( + errUnsupportedCommand = errors.New("unsupported command") + errInvalidValuePassword = errors.New("invalid value password") ) type Model struct { main tea.Model help help.Model header string + state state elements []interface{} pos int err error @@ -50,150 +69,187 @@ type Model struct { category client.Category usecaseCreate *create.Usecase - usecaseSave *save.Usecase + usecaseEdit *edit.Usecase usecaseDelete *delete.Usecase } -func InitModel(usecaseCreate *create.Usecase, usecaseSave *save.Usecase, usecaseDelete *delete.Usecase) *Model { +func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *delete.Usecase) *Model { return &Model{ - help: help.New(), - header: "Регистрация", + help: help.New(), + header: "Регистрация", + state: stateCreate, + usecaseCreate: usecaseCreate, - usecaseSave: usecaseSave, + usecaseEdit: usecaseSave, usecaseDelete: usecaseDelete, } } -func (f *Model) SetMainModel(m tea.Model) { - f.main = m +func (m *Model) SetMainModel(model tea.Model) { + m.main = model } -func (f *Model) Init() tea.Cmd { +func (m *Model) Init() tea.Cmd { return nil } -func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: - for i := range f.elements { - switch e := f.elements[i].(type) { + for i := range m.elements { + switch e := m.elements[i].(type) { case textinput.Model: e.Width = msg.Width } } - return f, nil - case OpenMessage: - f.backState = msg.BackState - f.backModel = msg.BackModel - f.header = msg.Item.Title - f.guid = msg.Item.GUID - f.category = msg.Item.Category - - value, ok := msg.Item.Value.(*client.Password) - if !ok { - f.err = errors.New("it is not password") - return f, nil + return m, nil + case OpenMsg: + m.backState = msg.BackState + m.backModel = msg.BackModel + + if msg.Item != nil { + m.state = stateEdit + m.header = msg.Item.Title + m.guid = msg.Item.GUID + m.category = msg.Item.Category + + value, ok := msg.Item.Value.(*client.Password) + if !ok { + m.err = errInvalidValuePassword + return m, nil + } + + m.elements = []interface{}{ + posEditTitle: tinput.CreateDefaultTextInput("Название", tinput.WithValue(msg.Item.Title), tinput.WithFocused()), + posEditLogin: tinput.CreateDefaultTextInput("Логин", tinput.WithValue(value.Login)), + posEditPassword: tinput.CreateDefaultTextInput("Пароль", tinput.WithValue(value.Password)), + posEditNotes: tarea.CreateDefaultTextArea("Заметки", tarea.WithValue(msg.Item.Notes)), + posEditEditBtn: button.CreateDefaultButton("Изменить"), + posEditDeleteBtn: button.CreateDefaultButton("Удалить"), + posEditBackBtn: button.CreateDefaultButton("Назад"), + } + + return m, nil } - f.elements = []interface{}{ - posTitle: tinput.CreateDefaultTextInput("Название", tinput.WithValue(msg.Item.Title), tinput.WithFocused()), - posLogin: tinput.CreateDefaultTextInput("Логин", tinput.WithValue(value.Login)), - posPassword: tinput.CreateDefaultTextInput("Пароль", tinput.WithValue(value.Password)), - posNotes: tarea.CreateDefaultTextArea("Заметки", tarea.WithValue(msg.Item.Notes)), - posEditBtn: button.CreateDefaultButton("Изменить"), - posDeleteBtn: button.CreateDefaultButton("Удалить"), - posBackBtn: button.CreateDefaultButton("Назад"), + m.state = stateCreate + m.header = "Новый пароль" + m.elements = []interface{}{ + posCreateTitle: tinput.CreateDefaultTextInput("Название", tinput.WithFocused()), + posCreateLogin: tinput.CreateDefaultTextInput("Логин"), + posCreatePassword: tinput.CreateDefaultTextInput("Пароль"), + posCreateNotes: tarea.CreateDefaultTextArea("Заметки"), + posCreateSaveBtn: button.CreateDefaultButton("Сохранить"), + posCreateBackBtn: button.CreateDefaultButton("Назад"), } - return f, nil + return m, nil case tea.KeyMsg: switch { case key.Matches(msg, common.Keys.Quit): - return f, tea.Quit + return m, tea.Quit case key.Matches(msg, common.Keys.Navigation): if key.Matches(msg, common.Keys.Down, common.Keys.Tab) { - f.pos++ + m.pos++ } else { - f.pos-- + m.pos-- } - if f.pos > len(f.elements)-1 { - f.pos = 0 - } else if f.pos < 0 { - f.pos = len(f.elements) - 1 + if m.pos > len(m.elements)-1 { + m.pos = 0 + } else if m.pos < 0 { + m.pos = len(m.elements) - 1 } - for i := range f.elements { - switch e := f.elements[i].(type) { + for i := range m.elements { + switch e := m.elements[i].(type) { case textinput.Model: - if i == f.pos { + if i == m.pos { e.Focus() - f.elements[i] = style.SetFocusStyle(e) + m.elements[i] = style.SetFocusStyle(e) continue } e.Blur() - f.elements[i] = style.SetNoStyle(e) + m.elements[i] = style.SetNoStyle(e) case textarea.Model: - if i == f.pos { + if i == m.pos { e.Focus() - f.elements[i] = e + m.elements[i] = e continue } e.Blur() - f.elements[i] = e + m.elements[i] = e case button.Button: - if i == f.pos { + if i == m.pos { e.Focus() - f.elements[i] = e + m.elements[i] = e continue } e.Blur() - f.elements[i] = e + m.elements[i] = e } } - return f, nil + return m, nil case key.Matches(msg, common.Keys.Enter): - f.err = nil - - switch f.pos { - case posEditBtn: - f.err = f.edit() - return f, nil - case posDeleteBtn: - f.err = f.delete() - return f, nil - case posBackBtn: - return f.backModel.Update(common.BackMessage{ - State: f.backState, + m.err = nil + + if m.state == stateCreate { + switch m.pos { + case posCreateSaveBtn: + m.err = m.saveAction() + return m, nil + case posCreateBackBtn: + return m.backModel.Update(common.BackMessage{ + State: m.backState, + }) + default: + m.err = errUnsupportedCommand + } + + return m, nil + } + + switch m.pos { + case posEditEditBtn: + m.err = m.editAction() + return m, nil + case posEditDeleteBtn: + m.err = m.deleteAction() + return m, nil + case posEditBackBtn: + return m.backModel.Update(common.BackMessage{ + State: m.backState, }) + default: + m.err = errUnsupportedCommand } - return f, nil + return m, nil case key.Matches(msg, common.Keys.Back): - return f.backModel.Update(common.BackMessage{ - State: f.backState, + return m.backModel.Update(common.BackMessage{ + State: m.backState, }) } } - return f, f.updateInputs(msg) + return m, m.updateInputs(msg) } -func (f *Model) View() string { +func (m *Model) View() string { var b strings.Builder - b.WriteString(style.TitleStyle.Render(f.header)) + b.WriteString(style.TitleStyle.Render(m.header)) b.WriteRune('\n') b.WriteString("Категория: ") - b.WriteString(f.category.String()) + b.WriteString(m.category.String()) b.WriteRune('\n') - for i := range f.elements { - switch e := f.elements[i].(type) { + for i := range m.elements { + switch e := m.elements[i].(type) { case textinput.Model: b.WriteString(e.Placeholder) b.WriteRune('\n') @@ -211,8 +267,8 @@ func (f *Model) View() string { b.WriteRune('\n') - for i := range f.elements { - if e, ok := f.elements[i].(button.Button); ok { + for i := range m.elements { + if e, ok := m.elements[i].(button.Button); ok { b.WriteString(e.String()) b.WriteRune('\n') } @@ -224,53 +280,34 @@ func (f *Model) View() string { ) // выводим ошибки валидации - if f.err != nil && (errors.As(f.err, &errValidate) || errors.As(f.err, &errForm)) { - b.WriteString(style.ErrorBlockStyle.Render(f.err.Error())) + if m.err != nil && (errors.As(m.err, &errValidate) || errors.As(m.err, &errForm)) { + b.WriteString(style.ErrorBlockStyle.Render(m.err.Error())) b.WriteRune('\n') } b.WriteRune('\n') - b.WriteString(f.help.View(common.Keys)) + b.WriteString(m.help.View(common.Keys)) // выводим прочие ошибки - if f.err != nil && !(errors.As(f.err, &errValidate) || errors.As(f.err, &errForm)) { + if m.err != nil && !(errors.As(m.err, &errValidate) || errors.As(m.err, &errForm)) { b.WriteRune('\n') - b.WriteString(style.ErrorBlockStyle.Render(f.err.Error())) + b.WriteString(style.ErrorBlockStyle.Render(m.err.Error())) } return b.String() } -func (f *Model) updateInputs(msg tea.Msg) tea.Cmd { - cmds := make([]tea.Cmd, len(f.elements)) +func (m *Model) updateInputs(msg tea.Msg) tea.Cmd { + cmds := make([]tea.Cmd, len(m.elements)) - for i := range f.elements { - switch m := f.elements[i].(type) { + for i := range m.elements { + switch e := m.elements[i].(type) { case textinput.Model: - f.elements[i], cmds[i] = m.Update(msg) + m.elements[i], cmds[i] = e.Update(msg) case textarea.Model: - f.elements[i], cmds[i] = m.Update(msg) + m.elements[i], cmds[i] = e.Update(msg) } } return tea.Batch(cmds...) } - -func (f *Model) edit() error { - i := client.Item{ - GUID: f.guid, - Category: f.category, - Title: element.GetValue(f.elements, posTitle), - Value: client.Password{ - Password: element.GetValue(f.elements, posPassword), - Login: element.GetValue(f.elements, posLogin), - }, - Notes: element.GetValue(f.elements, posNotes), - } - - return f.usecaseSave.Do(context.TODO(), i) -} - -func (f *Model) delete() error { - return f.usecaseDelete.Do(context.TODO(), f.guid) -} diff --git a/internal/cli/model/item/text/action.go b/internal/cli/model/item/text/action.go new file mode 100644 index 0000000..fffbfb7 --- /dev/null +++ b/internal/cli/model/item/text/action.go @@ -0,0 +1,35 @@ +package text + +import ( + "context" + "github.com/google/uuid" + + "github.com/bjlag/go-keeper/internal/cli/element" + "github.com/bjlag/go-keeper/internal/domain/client" +) + +func (m *Model) editAction() error { + i := client.Item{ + GUID: m.guid, + Category: m.category, + Title: element.GetValue(m.elements, posEditTitle), + Notes: element.GetValue(m.elements, posEditNotes), + } + + return m.usecaseEdit.Do(context.TODO(), i) +} + +func (m *Model) deleteAction() error { + return m.usecaseDelete.Do(context.TODO(), m.guid) +} + +func (m *Model) saveAction() error { + item := client.Item{ + GUID: uuid.New(), + Category: client.CategoryText, + Title: element.GetValue(m.elements, posCreateTitle), + Notes: element.GetValue(m.elements, posCreateNotes), + } + + return m.usecaseCreate.Do(context.TODO(), item) +} diff --git a/internal/cli/model/item/text/message.go b/internal/cli/model/item/text/message.go index 8abdf8d..ad10bfa 100644 --- a/internal/cli/model/item/text/message.go +++ b/internal/cli/model/item/text/message.go @@ -6,8 +6,8 @@ import ( "github.com/bjlag/go-keeper/internal/cli/element/list" ) -type OpenMessage struct { +type OpenMsg struct { BackModel tea.Model BackState int - Item list.Item + Item *list.Item } diff --git a/internal/cli/model/item/text/model.go b/internal/cli/model/item/text/model.go index c7ba0b3..08f7615 100644 --- a/internal/cli/model/item/text/model.go +++ b/internal/cli/model/item/text/model.go @@ -1,7 +1,6 @@ package text import ( - "context" "errors" "strings" @@ -12,7 +11,6 @@ import ( "github.com/google/uuid" "github.com/bjlag/go-keeper/internal/cli/common" - "github.com/bjlag/go-keeper/internal/cli/element" "github.com/bjlag/go-keeper/internal/cli/element/button" tarea "github.com/bjlag/go-keeper/internal/cli/element/textarea" tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" @@ -20,22 +18,39 @@ import ( "github.com/bjlag/go-keeper/internal/domain/client" "github.com/bjlag/go-keeper/internal/usecase/client/item/create" "github.com/bjlag/go-keeper/internal/usecase/client/item/delete" - "github.com/bjlag/go-keeper/internal/usecase/client/item/save" + "github.com/bjlag/go-keeper/internal/usecase/client/item/edit" "github.com/charmbracelet/bubbles/textarea" ) const ( - posTitle int = iota - posNotes - posEditBtn - posDeleteBtn - posBackBtn + posEditTitle int = iota + posEditNotes + posEditEditBtn + posEditDeleteBtn + posEditBackBtn ) +const ( + posCreateTitle int = iota + posCreateNotes + posCreateSaveBtn + posCreateBackBtn +) + +type state int + +const ( + stateCreate state = iota + stateEdit +) + +var errUnsupportedCommand = errors.New("unsupported command") + type Model struct { main tea.Model help help.Model header string + state state elements []interface{} pos int err error @@ -47,142 +62,175 @@ type Model struct { category client.Category usecaseCreate *create.Usecase - usecaseSave *save.Usecase + usecaseEdit *edit.Usecase usecaseDelete *delete.Usecase } -func InitModel(usecaseCreate *create.Usecase, usecaseSave *save.Usecase, usecaseDelete *delete.Usecase) *Model { +func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *delete.Usecase) *Model { return &Model{ - help: help.New(), - header: "Регистрация", + help: help.New(), + header: "Регистрация", + state: stateCreate, + usecaseCreate: usecaseCreate, - usecaseSave: usecaseSave, + usecaseEdit: usecaseSave, usecaseDelete: usecaseDelete, } } -func (f *Model) SetMainModel(m tea.Model) { - f.main = m +func (m *Model) SetMainModel(model tea.Model) { + m.main = model } -func (f *Model) Init() tea.Cmd { +func (m *Model) Init() tea.Cmd { return nil } -func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: - for i := range f.elements { - switch e := f.elements[i].(type) { + for i := range m.elements { + switch e := m.elements[i].(type) { case textinput.Model: e.Width = msg.Width } } - return f, nil - case OpenMessage: - f.backState = msg.BackState - f.backModel = msg.BackModel - f.header = msg.Item.Title - f.guid = msg.Item.GUID - f.category = msg.Item.Category - - f.elements = []interface{}{ - posTitle: tinput.CreateDefaultTextInput("Название", tinput.WithValue(msg.Item.Title), tinput.WithFocused()), - posNotes: tarea.CreateDefaultTextArea("Текст", tarea.WithValue(msg.Item.Notes)), - posEditBtn: button.CreateDefaultButton("Изменить"), - posDeleteBtn: button.CreateDefaultButton("Удалить"), - posBackBtn: button.CreateDefaultButton("Назад"), + return m, nil + case OpenMsg: + m.backState = msg.BackState + m.backModel = msg.BackModel + + if msg.Item != nil { + m.state = stateEdit + m.header = msg.Item.Title + m.guid = msg.Item.GUID + m.category = msg.Item.Category + + m.elements = []interface{}{ + posEditTitle: tinput.CreateDefaultTextInput("Название", tinput.WithValue(msg.Item.Title), tinput.WithFocused()), + posEditNotes: tarea.CreateDefaultTextArea("Текст", tarea.WithValue(msg.Item.Notes)), + posEditEditBtn: button.CreateDefaultButton("Изменить"), + posEditDeleteBtn: button.CreateDefaultButton("Удалить"), + posEditBackBtn: button.CreateDefaultButton("Назад"), + } + + return m, nil + } + + m.state = stateCreate + m.header = "Новый текст" + m.elements = []interface{}{ + posCreateTitle: tinput.CreateDefaultTextInput("Название", tinput.WithFocused()), + posCreateNotes: tarea.CreateDefaultTextArea("Текст"), + posCreateSaveBtn: button.CreateDefaultButton("Сохранить"), + posCreateBackBtn: button.CreateDefaultButton("Назад"), } - return f, nil + return m, nil case tea.KeyMsg: switch { case key.Matches(msg, common.Keys.Quit): - return f, tea.Quit + return m, tea.Quit case key.Matches(msg, common.Keys.Navigation): if key.Matches(msg, common.Keys.Down, common.Keys.Tab) { - f.pos++ + m.pos++ } else { - f.pos-- + m.pos-- } - if f.pos > len(f.elements)-1 { - f.pos = 0 - } else if f.pos < 0 { - f.pos = len(f.elements) - 1 + if m.pos > len(m.elements)-1 { + m.pos = 0 + } else if m.pos < 0 { + m.pos = len(m.elements) - 1 } - for i := range f.elements { - switch e := f.elements[i].(type) { + for i := range m.elements { + switch e := m.elements[i].(type) { case textinput.Model: - if i == f.pos { + if i == m.pos { e.Focus() - f.elements[i] = style.SetFocusStyle(e) + m.elements[i] = style.SetFocusStyle(e) continue } e.Blur() - f.elements[i] = style.SetNoStyle(e) + m.elements[i] = style.SetNoStyle(e) case textarea.Model: - if i == f.pos { + if i == m.pos { e.Focus() - f.elements[i] = e + m.elements[i] = e continue } e.Blur() - f.elements[i] = e + m.elements[i] = e case button.Button: - if i == f.pos { + if i == m.pos { e.Focus() - f.elements[i] = e + m.elements[i] = e continue } e.Blur() - f.elements[i] = e + m.elements[i] = e } } - return f, nil + return m, nil case key.Matches(msg, common.Keys.Enter): - f.err = nil - - switch f.pos { - case posEditBtn: - f.err = f.edit() - return f, nil - case posDeleteBtn: - f.err = f.delete() - return f, nil - case posBackBtn: - return f.backModel.Update(common.BackMessage{ - State: f.backState, + m.err = nil + + if m.state == stateCreate { + switch m.pos { + case posCreateSaveBtn: + m.err = m.saveAction() + return m, nil + case posCreateBackBtn: + return m.backModel.Update(common.BackMessage{ + State: m.backState, + }) + default: + m.err = errUnsupportedCommand + } + } + + switch m.pos { + case posEditEditBtn: + m.err = m.editAction() + return m, nil + case posEditDeleteBtn: + m.err = m.deleteAction() + return m, nil + case posEditBackBtn: + return m.backModel.Update(common.BackMessage{ + State: m.backState, }) + default: + m.err = errUnsupportedCommand } - return f, nil + return m, nil case key.Matches(msg, common.Keys.Back): - return f.backModel.Update(common.BackMessage{ - State: f.backState, + return m.backModel.Update(common.BackMessage{ + State: m.backState, }) } } - return f, f.updateInputs(msg) + return m, m.updateInputs(msg) } -func (f *Model) View() string { +func (m *Model) View() string { var b strings.Builder - b.WriteString(style.TitleStyle.Render(f.header)) + b.WriteString(style.TitleStyle.Render(m.header)) b.WriteRune('\n') b.WriteString("Категория: ") - b.WriteString(f.category.String()) + b.WriteString(m.category.String()) b.WriteRune('\n') - for i := range f.elements { - switch e := f.elements[i].(type) { + for i := range m.elements { + switch e := m.elements[i].(type) { case textinput.Model: b.WriteString(e.Placeholder) b.WriteRune('\n') @@ -200,8 +248,8 @@ func (f *Model) View() string { b.WriteRune('\n') - for i := range f.elements { - if e, ok := f.elements[i].(button.Button); ok { + for i := range m.elements { + if e, ok := m.elements[i].(button.Button); ok { b.WriteString(e.String()) b.WriteRune('\n') } @@ -213,49 +261,34 @@ func (f *Model) View() string { ) // выводим ошибки валидации - if f.err != nil && (errors.As(f.err, &errValidate) || errors.As(f.err, &errForm)) { - b.WriteString(style.ErrorBlockStyle.Render(f.err.Error())) + if m.err != nil && (errors.As(m.err, &errValidate) || errors.As(m.err, &errForm)) { + b.WriteString(style.ErrorBlockStyle.Render(m.err.Error())) b.WriteRune('\n') } b.WriteRune('\n') - b.WriteString(f.help.View(common.Keys)) + b.WriteString(m.help.View(common.Keys)) // выводим прочие ошибки - if f.err != nil && !(errors.As(f.err, &errValidate) || errors.As(f.err, &errForm)) { + if m.err != nil && !(errors.As(m.err, &errValidate) || errors.As(m.err, &errForm)) { b.WriteRune('\n') - b.WriteString(style.ErrorBlockStyle.Render(f.err.Error())) + b.WriteString(style.ErrorBlockStyle.Render(m.err.Error())) } return b.String() } -func (f *Model) updateInputs(msg tea.Msg) tea.Cmd { - cmds := make([]tea.Cmd, len(f.elements)) +func (m *Model) updateInputs(msg tea.Msg) tea.Cmd { + cmds := make([]tea.Cmd, len(m.elements)) - for i := range f.elements { - switch m := f.elements[i].(type) { + for i := range m.elements { + switch e := m.elements[i].(type) { case textinput.Model: - f.elements[i], cmds[i] = m.Update(msg) + m.elements[i], cmds[i] = e.Update(msg) case textarea.Model: - f.elements[i], cmds[i] = m.Update(msg) + m.elements[i], cmds[i] = e.Update(msg) } } return tea.Batch(cmds...) } - -func (f *Model) edit() error { - i := client.Item{ - GUID: f.guid, - Category: f.category, - Title: element.GetValue(f.elements, posTitle), - Notes: element.GetValue(f.elements, posNotes), - } - - return f.usecaseSave.Do(context.TODO(), i) -} - -func (f *Model) delete() error { - return f.usecaseDelete.Do(context.TODO(), f.guid) -} diff --git a/internal/cli/model/list/model.go b/internal/cli/model/list/model.go index 1454cca..3335486 100644 --- a/internal/cli/model/list/model.go +++ b/internal/cli/model/list/model.go @@ -138,7 +138,8 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return f.main.Update(common.OpenItemMessage{ BackModel: f, BackState: f.state, - Item: i, + Category: i.Category, + Item: &i, }) } } diff --git a/internal/cli/model/master/model.go b/internal/cli/model/master/model.go index 6e2dfc5..226a28a 100644 --- a/internal/cli/model/master/model.go +++ b/internal/cli/model/master/model.go @@ -1,6 +1,7 @@ package master import ( + "errors" "strings" "github.com/charmbracelet/bubbles/help" @@ -9,6 +10,7 @@ import ( "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/element/button" + "github.com/bjlag/go-keeper/internal/cli/model/item/create" "github.com/bjlag/go-keeper/internal/cli/model/item/password" "github.com/bjlag/go-keeper/internal/cli/model/item/text" listf "github.com/bjlag/go-keeper/internal/cli/model/list" @@ -19,6 +21,8 @@ import ( "github.com/bjlag/go-keeper/internal/infrastructure/store/client/token" ) +var errUnsupportedCategory = errors.New("unsupported category") + const ( posViewBtn int = iota posCreateBtn @@ -30,9 +34,11 @@ type Model struct { header string elements []interface{} pos int + err error formLogin *login.Model formRegister *register.Model + formCreate *create.Model formList *listf.Model formPassword *password.Model formText *text.Model @@ -77,7 +83,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case posViewBtn: return m.formList.Update(listf.GetAllDataMessage{}) case posCreateBtn: - //m.formList.Update(listf.GetAllDataMessage{}) + return m.formCreate.Update(create.Open{}) case posCloseBtn: return m, tea.Quit } @@ -127,19 +133,21 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case listf.OpenItemListMessage: return m.formList.Update(msg) case common.OpenItemMessage: - switch msg.Item.Category { + switch msg.Category { case client.CategoryPassword: - return m.formPassword.Update(password.OpenMessage{ + return m.formPassword.Update(password.OpenMsg{ BackModel: msg.BackModel, BackState: msg.BackState, Item: msg.Item, }) case client.CategoryText: - return m.formText.Update(text.OpenMessage{ + return m.formText.Update(text.OpenMsg{ BackModel: msg.BackModel, BackState: msg.BackState, Item: msg.Item, }) + default: + m.err = errUnsupportedCategory } // Success @@ -165,5 +173,11 @@ func (m *Model) View() string { } } + // выводим прочие ошибки + if m.err != nil { + b.WriteRune('\n') + b.WriteString(style.ErrorBlockStyle.Render(m.err.Error())) + } + return b.String() } diff --git a/internal/cli/model/master/option.go b/internal/cli/model/master/option.go index cc12637..28b601b 100644 --- a/internal/cli/model/master/option.go +++ b/internal/cli/model/master/option.go @@ -1,6 +1,7 @@ package master import ( + "github.com/bjlag/go-keeper/internal/cli/model/item/create" "github.com/bjlag/go-keeper/internal/cli/model/item/password" "github.com/bjlag/go-keeper/internal/cli/model/item/text" "github.com/bjlag/go-keeper/internal/cli/model/list" @@ -31,7 +32,14 @@ func WithRegisterForm(form *register.Model) Option { } } -func WithListFormForm(form *list.Model) Option { +func WithCreatForm(form *create.Model) Option { + return func(m *Model) { + form.SetMainModel(m) + m.formCreate = form + } +} + +func WithListForm(form *list.Model) Option { return func(m *Model) { form.SetMainModel(m) m.formList = form diff --git a/internal/infrastructure/store/client/item/store.go b/internal/infrastructure/store/client/item/store.go index 72398d6..93afc98 100644 --- a/internal/infrastructure/store/client/item/store.go +++ b/internal/infrastructure/store/client/item/store.go @@ -33,7 +33,7 @@ func (s *Store) SaveItem(ctx context.Context, item model.Item) error { SET title = :title, value = :value, notes = :notes, - updated_at = datetime() + updated_at = :updated_at WHERE guid = :guid ` @@ -58,9 +58,7 @@ func (s *Store) CreateItem(ctx context.Context, item model.Item) error { VALUES (:guid, :category_id, :title, :value, :notes, :updated_at, :created_at) ` - var ( - value *[]byte - ) + var value *[]byte if item.Value != nil { v, err := json.Marshal(item.Value) diff --git a/internal/usecase/client/item/create/usecase.go b/internal/usecase/client/item/create/usecase.go index bf4c2e7..286b658 100644 --- a/internal/usecase/client/item/create/usecase.go +++ b/internal/usecase/client/item/create/usecase.go @@ -3,6 +3,7 @@ package create import ( "context" "fmt" + "time" model "github.com/bjlag/go-keeper/internal/domain/client" ) @@ -20,6 +21,9 @@ func NewUsecase(store store) *Usecase { func (u *Usecase) Do(ctx context.Context, item model.Item) error { const op = "usecase.item.create.Do" + item.CreatedAt = time.Now() + item.UpdatedAt = time.Now() + err := u.store.CreateItem(ctx, item) if err != nil { return fmt.Errorf("%s: %w", op, err) diff --git a/internal/usecase/client/item/save/contract.go b/internal/usecase/client/item/edit/contract.go similarity index 92% rename from internal/usecase/client/item/save/contract.go rename to internal/usecase/client/item/edit/contract.go index 1b526b4..ce94b39 100644 --- a/internal/usecase/client/item/save/contract.go +++ b/internal/usecase/client/item/edit/contract.go @@ -1,4 +1,4 @@ -package save +package edit import ( "context" diff --git a/internal/usecase/client/item/save/usecase.go b/internal/usecase/client/item/edit/usecase.go similarity index 81% rename from internal/usecase/client/item/save/usecase.go rename to internal/usecase/client/item/edit/usecase.go index 65e0514..8e66193 100644 --- a/internal/usecase/client/item/save/usecase.go +++ b/internal/usecase/client/item/edit/usecase.go @@ -1,8 +1,9 @@ -package save +package edit import ( "context" "fmt" + "time" model "github.com/bjlag/go-keeper/internal/domain/client" ) @@ -18,7 +19,9 @@ func NewUsecase(store store) *Usecase { } func (u *Usecase) Do(ctx context.Context, item model.Item) error { - const op = "usecase.item.save.Do" + const op = "usecase.item.edit.Do" + + item.UpdatedAt = time.Now() err := u.store.SaveItem(ctx, item) if err != nil { From 5bbe6e10a0eacd25e236e7316c4d181793c2fcde Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Sun, 9 Mar 2025 23:26:55 +0300 Subject: [PATCH 54/94] client: create items on server --- .gitignore | 3 +- internal/app/client/app.go | 2 +- internal/cli/model/item/password/action.go | 38 ++--- internal/cli/model/item/password/model.go | 2 +- internal/cli/model/item/text/action.go | 37 ++-- internal/cli/model/item/text/model.go | 2 +- internal/domain/client/encrypted_data.go | 52 ++++++ internal/domain/client/item.go | 34 ++++ internal/fetcher/item/fetcher.go | 2 +- internal/generated/rpc/keeper.pb.go | 160 ++++++++++-------- internal/generated/rpc/keeper_grpc.pb.go | 10 +- .../infrastructure/rpc/client/create_item.go | 35 ++++ internal/infrastructure/rpc/server/method.go | 4 +- .../infrastructure/store/server/item/store.go | 2 +- internal/rpc/create_item/handler.go | 15 +- .../usecase/client/item/create/contract.go | 5 + .../usecase/client/item/create/usecase.go | 45 ++++- internal/usecase/client/sync/usecase.go | 33 +--- internal/usecase/server/item/create/model.go | 2 + .../usecase/server/item/create/usecase.go | 7 +- proto/keeper.proto | 6 +- 21 files changed, 321 insertions(+), 175 deletions(-) create mode 100644 internal/domain/client/encrypted_data.go create mode 100644 internal/infrastructure/rpc/client/create_item.go diff --git a/.gitignore b/.gitignore index 00f7da5..7dba713 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,5 @@ config/client.yaml *.log # client -client.db \ No newline at end of file +client.db +__debug_bin* \ No newline at end of file diff --git a/internal/app/client/app.go b/internal/app/client/app.go index d64ca6e..6c29f79 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -66,7 +66,7 @@ func (a *App) Run(ctx context.Context) error { ucLogin := login.NewUsecase(rpcClient) ucRegister := register.NewUsecase(rpcClient) ucSync := sync.NewUsecase(rpcClient, storeItem) - ucCreateItem := create.NewUsecase(storeItem) + ucCreateItem := create.NewUsecase(rpcClient, storeItem) ucSaveItem := edit.NewUsecase(storeItem) ucDeleteItem := delete.NewUsecase(storeItem) diff --git a/internal/cli/model/item/password/action.go b/internal/cli/model/item/password/action.go index d58f274..78c917a 100644 --- a/internal/cli/model/item/password/action.go +++ b/internal/cli/model/item/password/action.go @@ -2,40 +2,32 @@ package password import ( "context" - "github.com/google/uuid" "github.com/bjlag/go-keeper/internal/cli/element" "github.com/bjlag/go-keeper/internal/domain/client" ) -func (m *Model) saveAction() error { - item := client.Item{ - GUID: uuid.New(), - Category: client.CategoryPassword, - Title: element.GetValue(m.elements, posCreateTitle), - Value: client.Password{ - Login: element.GetValue(m.elements, posCreateLogin), - Password: element.GetValue(m.elements, posCreatePassword), - }, - Notes: element.GetValue(m.elements, posCreateNotes), - } +func (m *Model) createAction() error { + item := client.NewPasswordItem( + element.GetValue(m.elements, posCreateTitle), + element.GetValue(m.elements, posCreateLogin), + element.GetValue(m.elements, posCreatePassword), + element.GetValue(m.elements, posCreateNotes), + ) return m.usecaseCreate.Do(context.TODO(), item) } func (m *Model) editAction() error { - i := client.Item{ - GUID: m.guid, - Category: m.category, - Title: element.GetValue(m.elements, posEditTitle), - Value: client.Password{ - Login: element.GetValue(m.elements, posEditLogin), - Password: element.GetValue(m.elements, posEditPassword), - }, - Notes: element.GetValue(m.elements, posEditNotes), - } + item := client.NewPasswordItem( + element.GetValue(m.elements, posEditTitle), + element.GetValue(m.elements, posEditLogin), + element.GetValue(m.elements, posEditPassword), + element.GetValue(m.elements, posEditNotes), + ) + item.GUID = m.guid - return m.usecaseEdit.Do(context.TODO(), i) + return m.usecaseEdit.Do(context.TODO(), item) } func (m *Model) deleteAction() error { diff --git a/internal/cli/model/item/password/model.go b/internal/cli/model/item/password/model.go index b94e3b4..5179b18 100644 --- a/internal/cli/model/item/password/model.go +++ b/internal/cli/model/item/password/model.go @@ -199,7 +199,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if m.state == stateCreate { switch m.pos { case posCreateSaveBtn: - m.err = m.saveAction() + m.err = m.createAction() return m, nil case posCreateBackBtn: return m.backModel.Update(common.BackMessage{ diff --git a/internal/cli/model/item/text/action.go b/internal/cli/model/item/text/action.go index fffbfb7..319b209 100644 --- a/internal/cli/model/item/text/action.go +++ b/internal/cli/model/item/text/action.go @@ -2,34 +2,31 @@ package text import ( "context" - "github.com/google/uuid" "github.com/bjlag/go-keeper/internal/cli/element" "github.com/bjlag/go-keeper/internal/domain/client" ) +func (m *Model) createAction() error { + item := client.NewTextItem( + element.GetValue(m.elements, posCreateTitle), + element.GetValue(m.elements, posCreateNotes), + ) + + return m.usecaseCreate.Do(context.TODO(), item) +} + func (m *Model) editAction() error { - i := client.Item{ - GUID: m.guid, - Category: m.category, - Title: element.GetValue(m.elements, posEditTitle), - Notes: element.GetValue(m.elements, posEditNotes), - } - - return m.usecaseEdit.Do(context.TODO(), i) + item := client.NewTextItem( + element.GetValue(m.elements, posEditTitle), + element.GetValue(m.elements, posEditNotes), + ) + + item.GUID = m.guid + + return m.usecaseEdit.Do(context.TODO(), item) } func (m *Model) deleteAction() error { return m.usecaseDelete.Do(context.TODO(), m.guid) } - -func (m *Model) saveAction() error { - item := client.Item{ - GUID: uuid.New(), - Category: client.CategoryText, - Title: element.GetValue(m.elements, posCreateTitle), - Notes: element.GetValue(m.elements, posCreateNotes), - } - - return m.usecaseCreate.Do(context.TODO(), item) -} diff --git a/internal/cli/model/item/text/model.go b/internal/cli/model/item/text/model.go index 08f7615..176c26b 100644 --- a/internal/cli/model/item/text/model.go +++ b/internal/cli/model/item/text/model.go @@ -182,7 +182,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if m.state == stateCreate { switch m.pos { case posCreateSaveBtn: - m.err = m.saveAction() + m.err = m.createAction() return m, nil case posCreateBackBtn: return m.backModel.Update(common.BackMessage{ diff --git a/internal/domain/client/encrypted_data.go b/internal/domain/client/encrypted_data.go new file mode 100644 index 0000000..9954ad3 --- /dev/null +++ b/internal/domain/client/encrypted_data.go @@ -0,0 +1,52 @@ +package client + +import ( + "encoding/json" + "fmt" +) + +type EncryptedData struct { + Title string `json:"title"` + Category Category `json:"category_id"` + Value *[]byte `json:"value,omitempty"` + Notes string `json:"notes"` +} + +func (d *EncryptedData) UnmarshalJSON(data []byte) error { + type Alias EncryptedData + + alias := &struct { + *Alias + Value *json.RawMessage `json:"value,omitempty"` + }{ + Alias: (*Alias)(d), + } + + if err := json.Unmarshal(data, alias); err != nil { + return fmt.Errorf("unmarshal data: %w", err) + } + + if alias.Value != nil { + value := []byte(*alias.Value) + d.Value = &value + } + + return nil +} + +func (d EncryptedData) MarshalJSON() ([]byte, error) { + type Alias EncryptedData + + alias := struct { + Alias + Value json.RawMessage `json:"value,omitempty"` + }{ + Alias: Alias(d), + } + + if d.Value != nil { + alias.Value = *d.Value + } + + return json.Marshal(alias) +} diff --git a/internal/domain/client/item.go b/internal/domain/client/item.go index c7a1611..a94743d 100644 --- a/internal/domain/client/item.go +++ b/internal/domain/client/item.go @@ -49,6 +49,40 @@ type Item struct { UpdatedAt time.Time } +func NewItem(category Category, title string, value interface{}, note string) Item { + now := time.Now() + return Item{ + GUID: uuid.New(), + Category: category, + Title: title, + Value: value, + Notes: note, + CreatedAt: now, + UpdatedAt: now, + } +} + +func NewPasswordItem(title, login, password, note string) Item { + return NewItem( + CategoryPassword, + title, + &Password{ + Login: login, + Password: password, + }, + note, + ) +} + +func NewTextItem(title, note string) Item { + return NewItem( + CategoryText, + title, + nil, + note, + ) +} + type Password struct { Login string `json:"login"` Password string `json:"password"` diff --git a/internal/fetcher/item/fetcher.go b/internal/fetcher/item/fetcher.go index 43dc032..796a7ce 100644 --- a/internal/fetcher/item/fetcher.go +++ b/internal/fetcher/item/fetcher.go @@ -9,7 +9,7 @@ import ( model "github.com/bjlag/go-keeper/internal/domain/client" ) -const prefixOp = "usecase.item" +const prefixOp = "fetcher.item" var ErrUnknownCategory = errors.New("unknown category") diff --git a/internal/generated/rpc/keeper.pb.go b/internal/generated/rpc/keeper.pb.go index 190578e..851ffe4 100644 --- a/internal/generated/rpc/keeper.pb.go +++ b/internal/generated/rpc/keeper.pb.go @@ -331,7 +331,9 @@ func (x *RefreshTokensOut) GetRefreshToken() string { // data type CreateItemIn struct { state protoimpl.MessageState `protogen:"open.v1"` - EncryptedData []byte `protobuf:"bytes,1,opt,name=encryptedData,proto3" json:"encryptedData,omitempty"` + Guid string `protobuf:"bytes,1,opt,name=guid,proto3" json:"guid,omitempty"` + EncryptedData []byte `protobuf:"bytes,2,opt,name=encryptedData,proto3" json:"encryptedData,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=CreatedAt,proto3" json:"CreatedAt,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -366,6 +368,13 @@ func (*CreateItemIn) Descriptor() ([]byte, []int) { return file_proto_keeper_proto_rawDescGZIP(), []int{6} } +func (x *CreateItemIn) GetGuid() string { + if x != nil { + return x.Guid + } + return "" +} + func (x *CreateItemIn) GetEncryptedData() []byte { if x != nil { return x.EncryptedData @@ -373,6 +382,13 @@ func (x *CreateItemIn) GetEncryptedData() []byte { return nil } +func (x *CreateItemIn) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + type CreateItemOut struct { state protoimpl.MessageState `protogen:"open.v1"` Guid string `protobuf:"bytes,1,opt,name=guid,proto3" json:"guid,omitempty"` @@ -713,53 +729,58 @@ var file_proto_keeper_proto_rawDesc = string([]byte{ 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, - 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x34, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x12, 0x24, 0x0a, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, - 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x65, - 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x22, 0x23, 0x0a, 0x0d, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x4f, 0x75, 0x74, 0x12, 0x12, 0x0a, - 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, - 0x64, 0x22, 0x3d, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, - 0x49, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, - 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, - 0x22, 0x34, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x4f, - 0x75, 0x74, 0x12, 0x22, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x0c, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x52, - 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0xb4, 0x01, 0x0a, 0x04, 0x49, 0x74, 0x65, 0x6d, 0x12, - 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, - 0x75, 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, - 0x44, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, - 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x12, 0x38, 0x0a, 0x09, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x03, 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, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x41, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 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, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x48, 0x0a, - 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x12, 0x12, 0x0a, - 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, - 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, - 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, - 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x22, 0x22, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x82, 0x01, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, 0x32, 0x9e, 0x03, 0x0a, 0x06, - 0x4b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x12, 0x33, 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, - 0x65, 0x72, 0x12, 0x12, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, - 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x1a, 0x13, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, - 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x12, 0x2a, 0x0a, 0x05, 0x4c, - 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x0f, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, - 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x1a, 0x10, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, - 0x6f, 0x67, 0x69, 0x6e, 0x4f, 0x75, 0x74, 0x12, 0x42, 0x0a, 0x0d, 0x52, 0x65, 0x66, 0x72, 0x65, - 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x17, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, - 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x49, - 0x6e, 0x1a, 0x18, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, - 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x4f, 0x75, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x14, 0x2e, 0x6b, 0x65, 0x65, 0x70, - 0x65, 0x72, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x1a, - 0x15, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, - 0x74, 0x65, 0x6d, 0x4f, 0x75, 0x74, 0x12, 0x3c, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x65, + 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, + 0x61, 0x12, 0x38, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x03, + 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, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x23, 0x0a, 0x0d, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x4f, 0x75, 0x74, 0x12, 0x12, 0x0a, 0x04, + 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, + 0x22, 0x3d, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x49, + 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x22, + 0x34, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x4f, 0x75, + 0x74, 0x12, 0x22, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x0c, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, + 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0xb4, 0x01, 0x0a, 0x04, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x12, + 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, + 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, + 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, + 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x12, 0x38, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x03, 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, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x41, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 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, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x48, 0x0a, 0x0c, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x12, 0x12, 0x0a, 0x04, + 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, + 0x12, 0x24, 0x0a, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, + 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, + 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x22, 0x22, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, 0x32, 0x9f, 0x03, 0x0a, 0x06, 0x4b, + 0x65, 0x65, 0x70, 0x65, 0x72, 0x12, 0x33, 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, + 0x72, 0x12, 0x12, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x65, 0x72, 0x49, 0x6e, 0x1a, 0x13, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x12, 0x2a, 0x0a, 0x05, 0x4c, 0x6f, + 0x67, 0x69, 0x6e, 0x12, 0x0f, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, + 0x69, 0x6e, 0x49, 0x6e, 0x1a, 0x10, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, + 0x67, 0x69, 0x6e, 0x4f, 0x75, 0x74, 0x12, 0x42, 0x0a, 0x0d, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, + 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x17, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, + 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x49, 0x6e, + 0x1a, 0x18, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, + 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x4f, 0x75, 0x74, 0x12, 0x3a, 0x0a, 0x0a, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x14, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, + 0x72, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x1a, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3c, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x15, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x49, 0x6e, 0x1a, 0x16, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, @@ -806,28 +827,29 @@ var file_proto_keeper_proto_goTypes = []any{ (*emptypb.Empty)(nil), // 14: google.protobuf.Empty } var file_proto_keeper_proto_depIdxs = []int32{ - 10, // 0: keeper.GetAllItemsOut.items:type_name -> keeper.Item - 13, // 1: keeper.Item.CreatedAt:type_name -> google.protobuf.Timestamp - 13, // 2: keeper.Item.UpdatedAt:type_name -> google.protobuf.Timestamp - 0, // 3: keeper.Keeper.Register:input_type -> keeper.RegisterIn - 2, // 4: keeper.Keeper.Login:input_type -> keeper.LoginIn - 4, // 5: keeper.Keeper.RefreshTokens:input_type -> keeper.RefreshTokensIn - 6, // 6: keeper.Keeper.CreateItem:input_type -> keeper.CreateItemIn - 8, // 7: keeper.Keeper.GetAllItems:input_type -> keeper.GetAllItemsIn - 11, // 8: keeper.Keeper.UpdateItem:input_type -> keeper.UpdateItemIn - 12, // 9: keeper.Keeper.DeleteItem:input_type -> keeper.DeleteItemIn - 1, // 10: keeper.Keeper.Register:output_type -> keeper.RegisterOut - 3, // 11: keeper.Keeper.Login:output_type -> keeper.LoginOut - 5, // 12: keeper.Keeper.RefreshTokens:output_type -> keeper.RefreshTokensOut - 7, // 13: keeper.Keeper.CreateItem:output_type -> keeper.CreateItemOut - 9, // 14: keeper.Keeper.GetAllItems:output_type -> keeper.GetAllItemsOut - 14, // 15: keeper.Keeper.UpdateItem:output_type -> google.protobuf.Empty - 14, // 16: keeper.Keeper.DeleteItem:output_type -> google.protobuf.Empty - 10, // [10:17] is the sub-list for method output_type - 3, // [3:10] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 13, // 0: keeper.CreateItemIn.CreatedAt:type_name -> google.protobuf.Timestamp + 10, // 1: keeper.GetAllItemsOut.items:type_name -> keeper.Item + 13, // 2: keeper.Item.CreatedAt:type_name -> google.protobuf.Timestamp + 13, // 3: keeper.Item.UpdatedAt:type_name -> google.protobuf.Timestamp + 0, // 4: keeper.Keeper.Register:input_type -> keeper.RegisterIn + 2, // 5: keeper.Keeper.Login:input_type -> keeper.LoginIn + 4, // 6: keeper.Keeper.RefreshTokens:input_type -> keeper.RefreshTokensIn + 6, // 7: keeper.Keeper.CreateItem:input_type -> keeper.CreateItemIn + 8, // 8: keeper.Keeper.GetAllItems:input_type -> keeper.GetAllItemsIn + 11, // 9: keeper.Keeper.UpdateItem:input_type -> keeper.UpdateItemIn + 12, // 10: keeper.Keeper.DeleteItem:input_type -> keeper.DeleteItemIn + 1, // 11: keeper.Keeper.Register:output_type -> keeper.RegisterOut + 3, // 12: keeper.Keeper.Login:output_type -> keeper.LoginOut + 5, // 13: keeper.Keeper.RefreshTokens:output_type -> keeper.RefreshTokensOut + 14, // 14: keeper.Keeper.CreateItem:output_type -> google.protobuf.Empty + 9, // 15: keeper.Keeper.GetAllItems:output_type -> keeper.GetAllItemsOut + 14, // 16: keeper.Keeper.UpdateItem:output_type -> google.protobuf.Empty + 14, // 17: keeper.Keeper.DeleteItem:output_type -> google.protobuf.Empty + 11, // [11:18] is the sub-list for method output_type + 4, // [4:11] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name } func init() { file_proto_keeper_proto_init() } diff --git a/internal/generated/rpc/keeper_grpc.pb.go b/internal/generated/rpc/keeper_grpc.pb.go index 846aaf4..3316cf4 100644 --- a/internal/generated/rpc/keeper_grpc.pb.go +++ b/internal/generated/rpc/keeper_grpc.pb.go @@ -38,7 +38,7 @@ type KeeperClient interface { Login(ctx context.Context, in *LoginIn, opts ...grpc.CallOption) (*LoginOut, error) RefreshTokens(ctx context.Context, in *RefreshTokensIn, opts ...grpc.CallOption) (*RefreshTokensOut, error) // data - CreateItem(ctx context.Context, in *CreateItemIn, opts ...grpc.CallOption) (*CreateItemOut, error) + CreateItem(ctx context.Context, in *CreateItemIn, opts ...grpc.CallOption) (*emptypb.Empty, error) GetAllItems(ctx context.Context, in *GetAllItemsIn, opts ...grpc.CallOption) (*GetAllItemsOut, error) UpdateItem(ctx context.Context, in *UpdateItemIn, opts ...grpc.CallOption) (*emptypb.Empty, error) DeleteItem(ctx context.Context, in *DeleteItemIn, opts ...grpc.CallOption) (*emptypb.Empty, error) @@ -82,9 +82,9 @@ func (c *keeperClient) RefreshTokens(ctx context.Context, in *RefreshTokensIn, o return out, nil } -func (c *keeperClient) CreateItem(ctx context.Context, in *CreateItemIn, opts ...grpc.CallOption) (*CreateItemOut, error) { +func (c *keeperClient) CreateItem(ctx context.Context, in *CreateItemIn, opts ...grpc.CallOption) (*emptypb.Empty, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(CreateItemOut) + out := new(emptypb.Empty) err := c.cc.Invoke(ctx, Keeper_CreateItem_FullMethodName, in, out, cOpts...) if err != nil { return nil, err @@ -131,7 +131,7 @@ type KeeperServer interface { Login(context.Context, *LoginIn) (*LoginOut, error) RefreshTokens(context.Context, *RefreshTokensIn) (*RefreshTokensOut, error) // data - CreateItem(context.Context, *CreateItemIn) (*CreateItemOut, error) + CreateItem(context.Context, *CreateItemIn) (*emptypb.Empty, error) GetAllItems(context.Context, *GetAllItemsIn) (*GetAllItemsOut, error) UpdateItem(context.Context, *UpdateItemIn) (*emptypb.Empty, error) DeleteItem(context.Context, *DeleteItemIn) (*emptypb.Empty, error) @@ -153,7 +153,7 @@ func (UnimplementedKeeperServer) Login(context.Context, *LoginIn) (*LoginOut, er func (UnimplementedKeeperServer) RefreshTokens(context.Context, *RefreshTokensIn) (*RefreshTokensOut, error) { return nil, status.Errorf(codes.Unimplemented, "method RefreshTokens not implemented") } -func (UnimplementedKeeperServer) CreateItem(context.Context, *CreateItemIn) (*CreateItemOut, error) { +func (UnimplementedKeeperServer) CreateItem(context.Context, *CreateItemIn) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method CreateItem not implemented") } func (UnimplementedKeeperServer) GetAllItems(context.Context, *GetAllItemsIn) (*GetAllItemsOut, error) { diff --git a/internal/infrastructure/rpc/client/create_item.go b/internal/infrastructure/rpc/client/create_item.go new file mode 100644 index 0000000..55bbd97 --- /dev/null +++ b/internal/infrastructure/rpc/client/create_item.go @@ -0,0 +1,35 @@ +package client + +import ( + "context" + "fmt" + "time" + + "github.com/google/uuid" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/bjlag/go-keeper/internal/generated/rpc" +) + +type CreateItemIn struct { + GUID uuid.UUID + EncryptedData []byte + CreatedAt time.Time +} + +func (c RPCClient) CreateItem(ctx context.Context, in *CreateItemIn) error { + const op = "client.rpc.CreateItem" + + rpcIn := &rpc.CreateItemIn{ + Guid: in.GUID.String(), + EncryptedData: in.EncryptedData, + CreatedAt: timestamppb.New(in.CreatedAt), + } + + _, err := c.client.CreateItem(ctx, rpcIn) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + return nil +} diff --git a/internal/infrastructure/rpc/server/method.go b/internal/infrastructure/rpc/server/method.go index 08ae401..c1c3e83 100644 --- a/internal/infrastructure/rpc/server/method.go +++ b/internal/infrastructure/rpc/server/method.go @@ -76,13 +76,13 @@ func (s RPCServer) GetAllItems(ctx context.Context, in *pb.GetAllItemsIn) (*pb.G return h(ctx, in) } -func (s RPCServer) CreateItem(ctx context.Context, in *pb.CreateItemIn) (*pb.CreateItemOut, error) { +func (s RPCServer) CreateItem(ctx context.Context, in *pb.CreateItemIn) (*emptypb.Empty, error) { handler, err := s.getHandler(CreateItemMethod) if err != nil { return nil, err } - h, ok := handler.(func(context.Context, *pb.CreateItemIn) (*pb.CreateItemOut, error)) + h, ok := handler.(func(context.Context, *pb.CreateItemIn) (*emptypb.Empty, error)) if !ok { return nil, status.Errorf(codes.Internal, "handler for %s method not found", CreateItemMethod) } diff --git a/internal/infrastructure/store/server/item/store.go b/internal/infrastructure/store/server/item/store.go index dd706b4..885429a 100644 --- a/internal/infrastructure/store/server/item/store.go +++ b/internal/infrastructure/store/server/item/store.go @@ -48,7 +48,7 @@ func (s *Store) Create(ctx context.Context, item model.Item) error { query := ` INSERT INTO items(guid, user_guid, encrypted_data, created_at, updated_at) - VALUES(:quid, :user_guid, :encrypted_data, :created_at, :updated_at) + VALUES(:guid, :user_guid, :encrypted_data, :created_at, :updated_at) ` arg := row{ diff --git a/internal/rpc/create_item/handler.go b/internal/rpc/create_item/handler.go index 94c3217..a244e2e 100644 --- a/internal/rpc/create_item/handler.go +++ b/internal/rpc/create_item/handler.go @@ -7,6 +7,7 @@ import ( "go.uber.org/zap" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" pb "github.com/bjlag/go-keeper/internal/generated/rpc" "github.com/bjlag/go-keeper/internal/infrastructure/auth" @@ -24,7 +25,7 @@ func New(usecase usecase) *Handler { } } -func (h *Handler) Handle(ctx context.Context, in *pb.CreateItemIn) (*pb.CreateItemOut, error) { +func (h *Handler) Handle(ctx context.Context, in *pb.CreateItemIn) (*emptypb.Empty, error) { log := logger.FromCtx(ctx) userGUID := auth.UserGUIDFromCtx(ctx) @@ -36,19 +37,21 @@ func (h *Handler) Handle(ctx context.Context, in *pb.CreateItemIn) (*pb.CreateIt return nil, status.Error(codes.InvalidArgument, "encrypted data is empty") } - itemGUID := uuid.New() + itemGUID, err := uuid.Parse(in.GetGuid()) + if err != nil { + return nil, status.Error(codes.InvalidArgument, "invalid item guid") + } - err := h.usecase.Do(ctx, create.In{ + err = h.usecase.Do(ctx, create.In{ ItemGUID: itemGUID, UserGUID: userGUID, EncryptedData: in.GetEncryptedData(), + CreatedAt: in.GetCreatedAt().AsTime(), }) if err != nil { log.Error("Failed to update item", zap.Error(err)) return nil, status.Error(codes.Internal, "internal error") } - return &pb.CreateItemOut{ - Guid: itemGUID.String(), - }, nil + return &emptypb.Empty{}, nil } diff --git a/internal/usecase/client/item/create/contract.go b/internal/usecase/client/item/create/contract.go index c7fcfdb..7c37a0b 100644 --- a/internal/usecase/client/item/create/contract.go +++ b/internal/usecase/client/item/create/contract.go @@ -4,8 +4,13 @@ import ( "context" model "github.com/bjlag/go-keeper/internal/domain/client" + rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" ) +type server interface { + CreateItem(ctx context.Context, in *rpc.CreateItemIn) error +} + type store interface { CreateItem(ctx context.Context, item model.Item) error } diff --git a/internal/usecase/client/item/create/usecase.go b/internal/usecase/client/item/create/usecase.go index 286b658..3d9ab98 100644 --- a/internal/usecase/client/item/create/usecase.go +++ b/internal/usecase/client/item/create/usecase.go @@ -2,29 +2,60 @@ package create import ( "context" + "encoding/json" "fmt" - "time" model "github.com/bjlag/go-keeper/internal/domain/client" + rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" ) type Usecase struct { - store store + server server + store store } -func NewUsecase(store store) *Usecase { +func NewUsecase(server server, store store) *Usecase { return &Usecase{ - store: store, + server: server, + store: store, } } func (u *Usecase) Do(ctx context.Context, item model.Item) error { const op = "usecase.item.create.Do" - item.CreatedAt = time.Now() - item.UpdatedAt = time.Now() + var value *[]byte + if item.Value != nil { + v, err := json.Marshal(item.Value) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } - err := u.store.CreateItem(ctx, item) + value = &v + } + + data := model.EncryptedData{ + Title: item.Title, + Category: item.Category, + Value: value, + Notes: item.Notes, + } + + encrypted, err := json.Marshal(data) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + err = u.server.CreateItem(ctx, &rpc.CreateItemIn{ + GUID: item.GUID, + EncryptedData: encrypted, + CreatedAt: item.CreatedAt, + }) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + err = u.store.CreateItem(ctx, item) if err != nil { return fmt.Errorf("%s: %w", op, err) } diff --git a/internal/usecase/client/sync/usecase.go b/internal/usecase/client/sync/usecase.go index 8f13054..d183eb2 100644 --- a/internal/usecase/client/sync/usecase.go +++ b/internal/usecase/client/sync/usecase.go @@ -15,35 +15,6 @@ const ( limit = 40 ) -type Data struct { - Title string `json:"title"` - CategoryID model.Category `json:"category_id"` - Value *[]byte `json:"value,omitempty"` - Notes string `json:"notes"` -} - -func (d *Data) UnmarshalJSON(data []byte) error { - type Alias Data - - alias := &struct { - *Alias - Value *json.RawMessage `json:"value,omitempty"` - }{ - Alias: (*Alias)(d), - } - - if err := json.Unmarshal(data, alias); err != nil { - return fmt.Errorf("unmarshal data: %w", err) - } - - if alias.Value != nil { - value := []byte(*alias.Value) - d.Value = &value - } - - return nil -} - type Usecase struct { client client store store @@ -79,7 +50,7 @@ func (u *Usecase) Do(ctx context.Context) error { // todo расшифровка // todo общие данные в отдельных полях - var data Data + var data model.EncryptedData err = json.Unmarshal(item.EncryptedData, &data) if err != nil { return fmt.Errorf("%s: %w", op, err) @@ -87,7 +58,7 @@ func (u *Usecase) Do(ctx context.Context) error { items = append(items, model.RawItem{ GUID: item.GUID, - Category: data.CategoryID, + Category: data.Category, Title: data.Title, Value: data.Value, Notes: data.Notes, diff --git a/internal/usecase/server/item/create/model.go b/internal/usecase/server/item/create/model.go index a553da1..45ff281 100644 --- a/internal/usecase/server/item/create/model.go +++ b/internal/usecase/server/item/create/model.go @@ -2,10 +2,12 @@ package create import ( "github.com/google/uuid" + "time" ) type In struct { ItemGUID uuid.UUID UserGUID uuid.UUID EncryptedData []byte + CreatedAt time.Time } diff --git a/internal/usecase/server/item/create/usecase.go b/internal/usecase/server/item/create/usecase.go index b0a28c0..15cc30a 100644 --- a/internal/usecase/server/item/create/usecase.go +++ b/internal/usecase/server/item/create/usecase.go @@ -3,7 +3,6 @@ package create import ( "context" "fmt" - "time" model "github.com/bjlag/go-keeper/internal/domain/server/data" ) @@ -19,14 +18,14 @@ func NewUsecase(store store) *Usecase { } func (u Usecase) Do(ctx context.Context, in In) error { - const op = "usecase.item.update.Do" + const op = "usecase.item.create.Do" data := model.Item{ GUID: in.ItemGUID, UserGUID: in.UserGUID, EncryptedData: in.EncryptedData, - CreatedAt: time.Now(), - UpdatedAt: time.Now(), + CreatedAt: in.CreatedAt, + UpdatedAt: in.CreatedAt, } err := u.store.Create(ctx, data) diff --git a/proto/keeper.proto b/proto/keeper.proto index f8fe2d4..7a681f5 100644 --- a/proto/keeper.proto +++ b/proto/keeper.proto @@ -14,7 +14,7 @@ service Keeper { rpc RefreshTokens(RefreshTokensIn) returns (RefreshTokensOut); // data - rpc CreateItem(CreateItemIn) returns (CreateItemOut); + rpc CreateItem(CreateItemIn) returns (google.protobuf.Empty); rpc GetAllItems(GetAllItemsIn) returns (GetAllItemsOut); rpc UpdateItem(UpdateItemIn) returns (google.protobuf.Empty); rpc DeleteItem(DeleteItemIn) returns (google.protobuf.Empty); @@ -52,7 +52,9 @@ message RefreshTokensOut { // data message CreateItemIn { - bytes encryptedData = 1; + string guid = 1; + bytes encryptedData = 2; + google.protobuf.Timestamp CreatedAt = 3; } message CreateItemOut { From eb8112ad4ceae9bbfe08da68d0644375d0da908f Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Mon, 10 Mar 2025 00:06:33 +0300 Subject: [PATCH 55/94] client: delete items on server --- internal/app/client/app.go | 2 +- .../infrastructure/rpc/client/delete_item.go | 28 +++++++++++++++++++ .../usecase/client/item/delete/contract.go | 6 ++++ .../usecase/client/item/delete/usecase.go | 19 ++++++++++--- .../usecase/server/item/delete/usecase.go | 1 + 5 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 internal/infrastructure/rpc/client/delete_item.go diff --git a/internal/app/client/app.go b/internal/app/client/app.go index 6c29f79..402f8e2 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -68,7 +68,7 @@ func (a *App) Run(ctx context.Context) error { ucSync := sync.NewUsecase(rpcClient, storeItem) ucCreateItem := create.NewUsecase(rpcClient, storeItem) ucSaveItem := edit.NewUsecase(storeItem) - ucDeleteItem := delete.NewUsecase(storeItem) + ucDeleteItem := delete.NewUsecase(rpcClient, storeItem) fetchItem := item.NewFetcher(storeItem) diff --git a/internal/infrastructure/rpc/client/delete_item.go b/internal/infrastructure/rpc/client/delete_item.go new file mode 100644 index 0000000..1a90f93 --- /dev/null +++ b/internal/infrastructure/rpc/client/delete_item.go @@ -0,0 +1,28 @@ +package client + +import ( + "context" + "fmt" + + "github.com/bjlag/go-keeper/internal/generated/rpc" + "github.com/google/uuid" +) + +type DeleteItemIn struct { + GUID uuid.UUID +} + +func (c RPCClient) DeleteItem(ctx context.Context, in *DeleteItemIn) error { + const op = "client.rpc.DeleteItem" + + rpcIn := &rpc.DeleteItemIn{ + Guid: in.GUID.String(), + } + + _, err := c.client.DeleteItem(ctx, rpcIn) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + return nil +} diff --git a/internal/usecase/client/item/delete/contract.go b/internal/usecase/client/item/delete/contract.go index d9b3f6f..b2183f9 100644 --- a/internal/usecase/client/item/delete/contract.go +++ b/internal/usecase/client/item/delete/contract.go @@ -4,8 +4,14 @@ import ( "context" "github.com/google/uuid" + + rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" ) +type server interface { + DeleteItem(ctx context.Context, in *rpc.DeleteItemIn) error +} + type store interface { DeleteItem(ctx context.Context, guid uuid.UUID) error } diff --git a/internal/usecase/client/item/delete/usecase.go b/internal/usecase/client/item/delete/usecase.go index 6a2358f..ef3b544 100644 --- a/internal/usecase/client/item/delete/usecase.go +++ b/internal/usecase/client/item/delete/usecase.go @@ -5,22 +5,33 @@ import ( "fmt" "github.com/google/uuid" + + rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" ) type Usecase struct { - store store + server server + store store } -func NewUsecase(store store) *Usecase { +func NewUsecase(server server, store store) *Usecase { return &Usecase{ - store: store, + server: server, + store: store, } } func (u *Usecase) Do(ctx context.Context, guid uuid.UUID) error { const op = "usecase.item.delete.Do" - err := u.store.DeleteItem(ctx, guid) + err := u.server.DeleteItem(ctx, &rpc.DeleteItemIn{ + GUID: guid, + }) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + err = u.store.DeleteItem(ctx, guid) if err != nil { return fmt.Errorf("%s: %w", op, err) } diff --git a/internal/usecase/server/item/delete/usecase.go b/internal/usecase/server/item/delete/usecase.go index bcea1e3..8641db4 100644 --- a/internal/usecase/server/item/delete/usecase.go +++ b/internal/usecase/server/item/delete/usecase.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/bjlag/go-keeper/internal/infrastructure/store/server/item" ) From 8e523fff35c0afd689b54a8355a8d25b7c80acb5 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Mon, 10 Mar 2025 17:42:39 +0300 Subject: [PATCH 56/94] client: encryption --- go.mod | 10 +-- go.sum | 8 +++ internal/app/client/app.go | 34 +++++++--- internal/cli/model/login/message.go | 7 +- internal/cli/model/login/model.go | 24 ++++--- internal/cli/model/master/message.go | 2 +- internal/cli/model/master/model.go | 21 +++--- internal/cli/model/master/option.go | 7 -- internal/cli/model/register/message.go | 2 +- internal/cli/model/register/model.go | 24 +++++-- internal/domain/client/option.go | 10 +++ .../infrastructure/crypt/cipher/cipher.go | 63 ++++++++++++++++++ .../infrastructure/crypt/master_key/keygen.go | 23 +++++++ .../infrastructure/crypt/master_key/salt.go | 39 +++++++++++ .../crypt/master_key/saltgen.go | 15 +++++ .../infrastructure/rpc/client/update_item.go | 30 +++++++++ .../store/client/option/model.go | 24 +++++++ .../store/client/option/store.go | 65 +++++++++++++++++++ .../store/client/token/store.go | 9 +++ .../usecase/client/item/create/contract.go | 16 +++-- .../usecase/client/item/create/usecase.go | 29 ++++++--- .../usecase/client/item/delete/contract.go | 6 +- .../usecase/client/item/delete/usecase.go | 14 ++-- internal/usecase/client/item/edit/contract.go | 15 ++++- internal/usecase/client/item/edit/usecase.go | 49 ++++++++++++-- internal/usecase/client/login/contract.go | 4 ++ internal/usecase/client/login/model.go | 5 -- internal/usecase/client/login/usecase.go | 15 +++-- .../usecase/client/master_key/contract.go | 25 +++++++ internal/usecase/client/master_key/model.go | 5 ++ internal/usecase/client/master_key/usecase.go | 62 ++++++++++++++++++ internal/usecase/client/register/contract.go | 5 ++ internal/usecase/client/register/usecase.go | 15 +++-- internal/usecase/client/sync/contract.go | 10 ++- internal/usecase/client/sync/usecase.go | 26 +++++--- .../client/002_create_options_table.up.sql | 4 ++ .../server/002_create_data_table.up.sql | 2 +- proto/keeper.proto | 4 -- 38 files changed, 607 insertions(+), 121 deletions(-) create mode 100644 internal/domain/client/option.go create mode 100644 internal/infrastructure/crypt/cipher/cipher.go create mode 100644 internal/infrastructure/crypt/master_key/keygen.go create mode 100644 internal/infrastructure/crypt/master_key/salt.go create mode 100644 internal/infrastructure/crypt/master_key/saltgen.go create mode 100644 internal/infrastructure/rpc/client/update_item.go create mode 100644 internal/infrastructure/store/client/option/model.go create mode 100644 internal/infrastructure/store/client/option/store.go create mode 100644 internal/usecase/client/master_key/contract.go create mode 100644 internal/usecase/client/master_key/model.go create mode 100644 internal/usecase/client/master_key/usecase.go create mode 100644 migrations/client/002_create_options_table.up.sql diff --git a/go.mod b/go.mod index d499ebf..a097ad8 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/bjlag/go-keeper -go 1.23 +go 1.23.0 require ( github.com/charmbracelet/bubbles v0.20.0 @@ -16,8 +16,8 @@ require ( github.com/mattn/go-sqlite3 v1.14.24 github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.33.0 - golang.org/x/sync v0.11.0 + golang.org/x/crypto v0.36.0 + golang.org/x/sync v0.12.0 google.golang.org/grpc v1.64.1 google.golang.org/protobuf v1.34.2 ) @@ -50,8 +50,8 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/net v0.33.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/text v0.22.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect diff --git a/go.sum b/go.sum index 5a015e8..492d844 100644 --- a/go.sum +++ b/go.sum @@ -144,16 +144,24 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 h1:mxSlqyb8ZAHsYDCfiXN1EDdNTdvjUJSLY+OnAUtYNYA= google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM= google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= diff --git a/internal/app/client/app.go b/internal/app/client/app.go index 402f8e2..070cc8e 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -15,18 +15,28 @@ import ( "github.com/bjlag/go-keeper/internal/cli/model/master" formRegister "github.com/bjlag/go-keeper/internal/cli/model/register" "github.com/bjlag/go-keeper/internal/fetcher/item" + crypt "github.com/bjlag/go-keeper/internal/infrastructure/crypt/cipher" + "github.com/bjlag/go-keeper/internal/infrastructure/crypt/master_key" "github.com/bjlag/go-keeper/internal/infrastructure/db/sqlite" rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" sItem "github.com/bjlag/go-keeper/internal/infrastructure/store/client/item" + "github.com/bjlag/go-keeper/internal/infrastructure/store/client/option" "github.com/bjlag/go-keeper/internal/infrastructure/store/client/token" "github.com/bjlag/go-keeper/internal/usecase/client/item/create" "github.com/bjlag/go-keeper/internal/usecase/client/item/delete" "github.com/bjlag/go-keeper/internal/usecase/client/item/edit" "github.com/bjlag/go-keeper/internal/usecase/client/login" + mkey "github.com/bjlag/go-keeper/internal/usecase/client/master_key" "github.com/bjlag/go-keeper/internal/usecase/client/register" "github.com/bjlag/go-keeper/internal/usecase/client/sync" ) +const ( + saltLength = 16 + masterKeyIterCount = 100_000 + masterKeyLength = 256 / 8 +) + type App struct { cfg Config log *zap.Logger @@ -61,22 +71,26 @@ func (a *App) Run(ctx context.Context) error { return fmt.Errorf("%s: %w", op, err) } - storeItem := sItem.NewStore(db) + salter := master_key.NewSaltGenerator(saltLength) + keymaker := master_key.NewKeyGenerator(masterKeyIterCount, masterKeyLength) + cipher := new(crypt.Cipher) - ucLogin := login.NewUsecase(rpcClient) - ucRegister := register.NewUsecase(rpcClient) - ucSync := sync.NewUsecase(rpcClient, storeItem) - ucCreateItem := create.NewUsecase(rpcClient, storeItem) - ucSaveItem := edit.NewUsecase(storeItem) + storeItem := sItem.NewStore(db) + storeOption := option.NewStore(db) + + ucLogin := login.NewUsecase(rpcClient, storeTokens) + ucRegister := register.NewUsecase(rpcClient, storeTokens) + ucMasterKey := mkey.NewUsecase(storeTokens, storeOption, salter, keymaker) + ucSync := sync.NewUsecase(rpcClient, storeItem, storeTokens, cipher) + ucCreateItem := create.NewUsecase(rpcClient, storeItem, storeTokens, cipher) + ucSaveItem := edit.NewUsecase(rpcClient, storeItem, storeTokens, cipher) ucDeleteItem := delete.NewUsecase(rpcClient, storeItem) fetchItem := item.NewFetcher(storeItem) m := master.InitModel( - master.WithStoreTokens(storeTokens), - - master.WithLoginForm(formLogin.InitModel(ucLogin)), - master.WithRegisterForm(formRegister.InitModel(ucRegister)), + master.WithLoginForm(formLogin.InitModel(ucLogin, ucMasterKey)), + master.WithRegisterForm(formRegister.InitModel(ucRegister, ucMasterKey)), master.WithCreatForm(formCreate.InitModel()), master.WithListForm(list.InitModel(ucSync, fetchItem)), master.WithPasswordItemForm(password.InitModel(ucCreateItem, ucSaveItem, ucDeleteItem)), diff --git a/internal/cli/model/login/message.go b/internal/cli/model/login/message.go index aa0e9ae..5555650 100644 --- a/internal/cli/model/login/message.go +++ b/internal/cli/model/login/message.go @@ -1,8 +1,5 @@ package login -type OpenMessage struct{} +type OpenMsg struct{} -type SuccessMessage struct { - AccessToken string - RefreshToken string -} +type SuccessMsg struct{} diff --git a/internal/cli/model/login/model.go b/internal/cli/model/login/model.go index b650cb0..b28c172 100644 --- a/internal/cli/model/login/model.go +++ b/internal/cli/model/login/model.go @@ -19,6 +19,7 @@ import ( "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/infrastructure/validator" "github.com/bjlag/go-keeper/internal/usecase/client/login" + "github.com/bjlag/go-keeper/internal/usecase/client/master_key" ) const ( @@ -39,10 +40,11 @@ type Model struct { pos int err error - usecase *login.Usecase + usecaseLogin *login.Usecase + usecaseMasterKey *master_key.Usecase } -func InitModel(usecase *login.Usecase) *Model { +func InitModel(usecaseLogin *login.Usecase, usecaseMasterKey *master_key.Usecase) *Model { f := &Model{ help: help.New(), header: "Авторизация", @@ -54,7 +56,8 @@ func InitModel(usecase *login.Usecase) *Model { posCloseBtn: button.CreateDefaultButton("Закрыть"), }, - usecase: usecase, + usecaseLogin: usecaseLogin, + usecaseMasterKey: usecaseMasterKey, } for i := range f.elements { @@ -90,7 +93,7 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } return f, nil - case OpenMessage: + case OpenMsg: for i := range f.elements { switch e := f.elements[i].(type) { case textinput.Model: @@ -233,7 +236,7 @@ func (f *Model) submit() (tea.Model, tea.Cmd) { return f, nil } - result, err := f.usecase.Do(context.TODO(), login.Data{ + err := f.usecaseLogin.Do(context.TODO(), login.Data{ Email: email.Value(), Password: password.Value(), }) @@ -251,10 +254,15 @@ func (f *Model) submit() (tea.Model, tea.Cmd) { return f, nil } - return f.main.Update(SuccessMessage{ - AccessToken: result.AccessToken, - RefreshToken: result.RefreshToken, + err = f.usecaseMasterKey.Do(context.TODO(), master_key.Data{ + Password: password.Value(), }) + if err != nil { + f.err = err + return f, nil + } + + return f.main.Update(SuccessMsg{}) } func (f *Model) updateInputs(msg tea.Msg) tea.Cmd { diff --git a/internal/cli/model/master/message.go b/internal/cli/model/master/message.go index 3e29336..a1b47bf 100644 --- a/internal/cli/model/master/message.go +++ b/internal/cli/model/master/message.go @@ -1,3 +1,3 @@ package master -type Open struct{} +type OpenMsg struct{} diff --git a/internal/cli/model/master/model.go b/internal/cli/model/master/model.go index 226a28a..1ac77ef 100644 --- a/internal/cli/model/master/model.go +++ b/internal/cli/model/master/model.go @@ -18,7 +18,6 @@ import ( "github.com/bjlag/go-keeper/internal/cli/model/register" "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/domain/client" - "github.com/bjlag/go-keeper/internal/infrastructure/store/client/token" ) var errUnsupportedCategory = errors.New("unsupported category") @@ -42,8 +41,6 @@ type Model struct { formList *listf.Model formPassword *password.Model formText *text.Model - - storeTokens *token.Store } func InitModel(opts ...Option) *Model { @@ -67,7 +64,7 @@ func InitModel(opts ...Option) *Model { func (m *Model) Init() tea.Cmd { return tea.Batch( func() tea.Msg { - return login.OpenMessage{} + return login.OpenMsg{} }, ) } @@ -118,13 +115,13 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, tea.Quit } - case Open: + case OpenMsg: return m, nil case common.BackMessage: - return m.Update(Open{}) + return m.Update(OpenMsg{}) // Forms - case login.OpenMessage: + case login.OpenMsg: return m.formLogin.Update(msg) case register.OpenMessage: return m.formRegister.Update(msg) @@ -151,13 +148,13 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } // Success - case login.SuccessMessage: - m.storeTokens.SaveTokens(msg.AccessToken, msg.RefreshToken) - case register.SuccessMessage: - m.storeTokens.SaveTokens(msg.AccessToken, msg.RefreshToken) + case login.SuccessMsg: + return m.Update(OpenMsg{}) + case register.SuccessMsg: + return m.Update(OpenMsg{}) } - return m.Update(Open{}) + return m.Update(OpenMsg{}) } func (m *Model) View() string { diff --git a/internal/cli/model/master/option.go b/internal/cli/model/master/option.go index 28b601b..b0fd669 100644 --- a/internal/cli/model/master/option.go +++ b/internal/cli/model/master/option.go @@ -7,17 +7,10 @@ import ( "github.com/bjlag/go-keeper/internal/cli/model/list" "github.com/bjlag/go-keeper/internal/cli/model/login" "github.com/bjlag/go-keeper/internal/cli/model/register" - "github.com/bjlag/go-keeper/internal/infrastructure/store/client/token" ) type Option func(*Model) -func WithStoreTokens(store *token.Store) Option { - return func(m *Model) { - m.storeTokens = store - } -} - func WithLoginForm(form *login.Model) Option { return func(m *Model) { form.SetMainModel(m) diff --git a/internal/cli/model/register/message.go b/internal/cli/model/register/message.go index 5e36236..a31b1a4 100644 --- a/internal/cli/model/register/message.go +++ b/internal/cli/model/register/message.go @@ -2,7 +2,7 @@ package register import tea "github.com/charmbracelet/bubbletea" -type SuccessMessage struct { +type SuccessMsg struct { AccessToken string RefreshToken string } diff --git a/internal/cli/model/register/model.go b/internal/cli/model/register/model.go index 3152588..bde6a43 100644 --- a/internal/cli/model/register/model.go +++ b/internal/cli/model/register/model.go @@ -17,6 +17,7 @@ import ( tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/infrastructure/validator" + "github.com/bjlag/go-keeper/internal/usecase/client/master_key" "github.com/bjlag/go-keeper/internal/usecase/client/register" ) @@ -38,10 +39,12 @@ type Model struct { err error backModel tea.Model - usecase *register.Usecase + + usecaseRegister *register.Usecase + usecaseMasterKey *master_key.Usecase } -func InitModel(usecase *register.Usecase) *Model { +func InitModel(usecaseRegister *register.Usecase, usecaseMasterKey *master_key.Usecase) *Model { f := &Model{ help: help.New(), header: "Регистрация", @@ -51,7 +54,9 @@ func InitModel(usecase *register.Usecase) *Model { posSubmitBtn: button.CreateDefaultButton("Регистрация"), posBackBtn: button.CreateDefaultButton("Назад"), }, - usecase: usecase, + + usecaseRegister: usecaseRegister, + usecaseMasterKey: usecaseMasterKey, } for i := range f.elements { @@ -226,7 +231,7 @@ func (f *Model) submit() (tea.Model, tea.Cmd) { return f, nil } - result, err := f.usecase.Do(context.TODO(), register.Data{ + err := f.usecaseRegister.Do(context.TODO(), register.Data{ Email: email.Value(), Password: password.Value(), }) @@ -244,10 +249,15 @@ func (f *Model) submit() (tea.Model, tea.Cmd) { return f, nil } - return f.main.Update(SuccessMessage{ - AccessToken: result.AccessToken, - RefreshToken: result.RefreshToken, + err = f.usecaseMasterKey.Do(context.TODO(), master_key.Data{ + Password: password.Value(), }) + if err != nil { + f.err = err + return f, nil + } + + return f.main.Update(SuccessMsg{}) } func (f *Model) updateInputs(msg tea.Msg) tea.Cmd { diff --git a/internal/domain/client/option.go b/internal/domain/client/option.go new file mode 100644 index 0000000..29f2891 --- /dev/null +++ b/internal/domain/client/option.go @@ -0,0 +1,10 @@ +package client + +const ( + OptSaltKey = "salt" +) + +type Option struct { + Slug string + Value string +} diff --git a/internal/infrastructure/crypt/cipher/cipher.go b/internal/infrastructure/crypt/cipher/cipher.go new file mode 100644 index 0000000..349be26 --- /dev/null +++ b/internal/infrastructure/crypt/cipher/cipher.go @@ -0,0 +1,63 @@ +package cipher + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "io" + "sync" +) + +type Cipher struct { + once sync.Once + gcmInstance cipher.AEAD +} + +func (c *Cipher) Encrypt(data, key []byte) ([]byte, error) { + err := c.setup(key) + if err != nil { + return nil, err + } + + nonce := make([]byte, c.gcmInstance.NonceSize()) + _, err = io.ReadFull(rand.Reader, nonce) + if err != nil { + return nil, err + } + + return c.gcmInstance.Seal(nonce, nonce, data, nil), nil +} + +func (c *Cipher) Decrypt(encryptedData, key []byte) ([]byte, error) { + err := c.setup(key) + if err != nil { + return nil, err + } + + nonceSize := c.gcmInstance.NonceSize() + nonce, cipheredText := encryptedData[:nonceSize], encryptedData[nonceSize:] + + return c.gcmInstance.Open(nil, nonce, cipheredText, nil) +} + +func (c *Cipher) setup(key []byte) error { + var err error + + c.once.Do(func() { + var aesBlock cipher.Block + aesBlock, err = aes.NewCipher(key) + if err != nil { + return + } + + var gcmInstance cipher.AEAD + gcmInstance, err = cipher.NewGCM(aesBlock) + if err != nil { + return + } + + c.gcmInstance = gcmInstance + }) + + return err +} diff --git a/internal/infrastructure/crypt/master_key/keygen.go b/internal/infrastructure/crypt/master_key/keygen.go new file mode 100644 index 0000000..36c034d --- /dev/null +++ b/internal/infrastructure/crypt/master_key/keygen.go @@ -0,0 +1,23 @@ +package master_key + +import ( + "crypto/sha512" + + "golang.org/x/crypto/pbkdf2" +) + +type KeyGenerator struct { + iteCount int + keyLen int +} + +func NewKeyGenerator(iterCount, keyLen int) *KeyGenerator { + return &KeyGenerator{ + iteCount: iterCount, + keyLen: keyLen, + } +} + +func (g KeyGenerator) GenerateMasterKey(password, salt []byte) []byte { + return pbkdf2.Key(password, salt, g.iteCount, g.keyLen, sha512.New) +} diff --git a/internal/infrastructure/crypt/master_key/salt.go b/internal/infrastructure/crypt/master_key/salt.go new file mode 100644 index 0000000..a2f8947 --- /dev/null +++ b/internal/infrastructure/crypt/master_key/salt.go @@ -0,0 +1,39 @@ +package master_key + +import ( + "crypto/rand" + "encoding/base64" +) + +type Salt []byte + +func NewSalt(length int) (*Salt, error) { + salt := make([]byte, length) + _, err := rand.Read(salt) + if err != nil { + return nil, err + } + + s := Salt(salt) + + return &s, nil +} + +func ParseString(salt string) (*Salt, error) { + b, err := base64.StdEncoding.DecodeString(salt) + if err != nil { + return nil, err + } + + s := Salt(b) + + return &s, nil +} + +func (s Salt) Value() []byte { + return s +} + +func (s Salt) ToString() string { + return base64.StdEncoding.EncodeToString(s) +} diff --git a/internal/infrastructure/crypt/master_key/saltgen.go b/internal/infrastructure/crypt/master_key/saltgen.go new file mode 100644 index 0000000..5e1205b --- /dev/null +++ b/internal/infrastructure/crypt/master_key/saltgen.go @@ -0,0 +1,15 @@ +package master_key + +type SaltGenerator struct { + length int +} + +func NewSaltGenerator(length int) *SaltGenerator { + return &SaltGenerator{ + length: length, + } +} + +func (g SaltGenerator) GenerateSalt() (*Salt, error) { + return NewSalt(g.length) +} diff --git a/internal/infrastructure/rpc/client/update_item.go b/internal/infrastructure/rpc/client/update_item.go new file mode 100644 index 0000000..354adf3 --- /dev/null +++ b/internal/infrastructure/rpc/client/update_item.go @@ -0,0 +1,30 @@ +package client + +import ( + "context" + "fmt" + "github.com/google/uuid" + + "github.com/bjlag/go-keeper/internal/generated/rpc" +) + +type UpdateItemIn struct { + GUID uuid.UUID + EncryptedData []byte +} + +func (c RPCClient) UpdateItem(ctx context.Context, in *UpdateItemIn) error { + const op = "client.rpc.UpdateItem" + + rpcIn := &rpc.UpdateItemIn{ + Guid: in.GUID.String(), + EncryptedData: in.EncryptedData, + } + + _, err := c.client.UpdateItem(ctx, rpcIn) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + return nil +} diff --git a/internal/infrastructure/store/client/option/model.go b/internal/infrastructure/store/client/option/model.go new file mode 100644 index 0000000..68df1db --- /dev/null +++ b/internal/infrastructure/store/client/option/model.go @@ -0,0 +1,24 @@ +package option + +import ( + model "github.com/bjlag/go-keeper/internal/domain/client" +) + +type row struct { + Slug string `db:"slug"` + Value string `db:"value"` +} + +func toRow(model model.Option) row { + return row{ + Slug: model.Slug, + Value: model.Value, + } +} + +func (r *row) toModel() model.Option { + return model.Option{ + Slug: r.Slug, + Value: r.Value, + } +} diff --git a/internal/infrastructure/store/client/option/store.go b/internal/infrastructure/store/client/option/store.go new file mode 100644 index 0000000..5919747 --- /dev/null +++ b/internal/infrastructure/store/client/option/store.go @@ -0,0 +1,65 @@ +package option + +import ( + "context" + "database/sql" + "errors" + "fmt" + + "github.com/jmoiron/sqlx" + + model "github.com/bjlag/go-keeper/internal/domain/client" +) + +const prefixOp = "store.option." + +type Store struct { + db *sqlx.DB +} + +func NewStore(db *sqlx.DB) *Store { + return &Store{ + db: db, + } +} + +func (s *Store) SaveOption(ctx context.Context, option model.Option) error { + const op = prefixOp + "SaveOption" + + query := ` + INSERT INTO options (slug, value) + VALUES (:slug, :value) + ON CONFLICT (slug) DO UPDATE + SET value = excluded.value + ` + + r := toRow(option) + _, err := s.db.NamedExecContext(ctx, query, r) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + return nil +} + +func (s *Store) OptionBySlug(ctx context.Context, slug string) (*model.Option, error) { + const op = prefixOp + "OptionBySlug" + + query := ` + SELECT slug, value + FROM options + WHERE slug = $1; + ` + var r row + err := s.db.GetContext(ctx, &r, query, slug) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, nil + } + return nil, fmt.Errorf("%s: %w", op, err) + } + + m := r.toModel() + + return &m, nil +} diff --git a/internal/infrastructure/store/client/token/store.go b/internal/infrastructure/store/client/token/store.go index 22f3d0e..3f318e3 100644 --- a/internal/infrastructure/store/client/token/store.go +++ b/internal/infrastructure/store/client/token/store.go @@ -3,6 +3,7 @@ package token type tokens struct { accessToken string refreshToken string + masterKey []byte } type Store struct { @@ -18,6 +19,10 @@ func (s *Store) SaveTokens(accessToken, refreshToken string) { s.tokens.refreshToken = refreshToken } +func (s *Store) SaveMasterKey(key []byte) { + s.tokens.masterKey = key +} + func (s *Store) AccessToken() string { return s.tokens.accessToken } @@ -25,3 +30,7 @@ func (s *Store) AccessToken() string { func (s *Store) RefreshToken() string { return s.tokens.refreshToken } + +func (s *Store) MasterKey() []byte { + return s.tokens.masterKey +} diff --git a/internal/usecase/client/item/create/contract.go b/internal/usecase/client/item/create/contract.go index 7c37a0b..f7a51b9 100644 --- a/internal/usecase/client/item/create/contract.go +++ b/internal/usecase/client/item/create/contract.go @@ -4,13 +4,21 @@ import ( "context" model "github.com/bjlag/go-keeper/internal/domain/client" - rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" + dto "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" ) -type server interface { - CreateItem(ctx context.Context, in *rpc.CreateItemIn) error +type rpc interface { + CreateItem(ctx context.Context, in *dto.CreateItemIn) error } -type store interface { +type itemStore interface { CreateItem(ctx context.Context, item model.Item) error } + +type keyStore interface { + MasterKey() []byte +} + +type cipher interface { + Encrypt(data, key []byte) ([]byte, error) +} diff --git a/internal/usecase/client/item/create/usecase.go b/internal/usecase/client/item/create/usecase.go index 3d9ab98..c22f32f 100644 --- a/internal/usecase/client/item/create/usecase.go +++ b/internal/usecase/client/item/create/usecase.go @@ -6,18 +6,22 @@ import ( "fmt" model "github.com/bjlag/go-keeper/internal/domain/client" - rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" + dto "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" ) type Usecase struct { - server server - store store + rpc rpc + itemStore itemStore + keyStore keyStore + cipher cipher } -func NewUsecase(server server, store store) *Usecase { +func NewUsecase(rpc rpc, itemStore itemStore, keyStore keyStore, cipher cipher) *Usecase { return &Usecase{ - server: server, - store: store, + rpc: rpc, + itemStore: itemStore, + keyStore: keyStore, + cipher: cipher, } } @@ -41,21 +45,26 @@ func (u *Usecase) Do(ctx context.Context, item model.Item) error { Notes: item.Notes, } - encrypted, err := json.Marshal(data) + plainText, err := json.Marshal(data) if err != nil { return fmt.Errorf("%s: %w", op, err) } - err = u.server.CreateItem(ctx, &rpc.CreateItemIn{ + encryptedData, err := u.cipher.Encrypt(plainText, u.keyStore.MasterKey()) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + err = u.rpc.CreateItem(ctx, &dto.CreateItemIn{ GUID: item.GUID, - EncryptedData: encrypted, + EncryptedData: encryptedData, CreatedAt: item.CreatedAt, }) if err != nil { return fmt.Errorf("%s: %w", op, err) } - err = u.store.CreateItem(ctx, item) + err = u.itemStore.CreateItem(ctx, item) if err != nil { return fmt.Errorf("%s: %w", op, err) } diff --git a/internal/usecase/client/item/delete/contract.go b/internal/usecase/client/item/delete/contract.go index b2183f9..bb6afff 100644 --- a/internal/usecase/client/item/delete/contract.go +++ b/internal/usecase/client/item/delete/contract.go @@ -5,11 +5,11 @@ import ( "github.com/google/uuid" - rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" + dto "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" ) -type server interface { - DeleteItem(ctx context.Context, in *rpc.DeleteItemIn) error +type rpc interface { + DeleteItem(ctx context.Context, in *dto.DeleteItemIn) error } type store interface { diff --git a/internal/usecase/client/item/delete/usecase.go b/internal/usecase/client/item/delete/usecase.go index ef3b544..fae2501 100644 --- a/internal/usecase/client/item/delete/usecase.go +++ b/internal/usecase/client/item/delete/usecase.go @@ -6,25 +6,25 @@ import ( "github.com/google/uuid" - rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" + dto "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" ) type Usecase struct { - server server - store store + rpc rpc + store store } -func NewUsecase(server server, store store) *Usecase { +func NewUsecase(rpc rpc, store store) *Usecase { return &Usecase{ - server: server, - store: store, + rpc: rpc, + store: store, } } func (u *Usecase) Do(ctx context.Context, guid uuid.UUID) error { const op = "usecase.item.delete.Do" - err := u.server.DeleteItem(ctx, &rpc.DeleteItemIn{ + err := u.rpc.DeleteItem(ctx, &dto.DeleteItemIn{ GUID: guid, }) if err != nil { diff --git a/internal/usecase/client/item/edit/contract.go b/internal/usecase/client/item/edit/contract.go index ce94b39..c9d043e 100644 --- a/internal/usecase/client/item/edit/contract.go +++ b/internal/usecase/client/item/edit/contract.go @@ -4,8 +4,21 @@ import ( "context" model "github.com/bjlag/go-keeper/internal/domain/client" + dto "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" ) -type store interface { +type rpc interface { + UpdateItem(ctx context.Context, in *dto.UpdateItemIn) error +} + +type itemStore interface { SaveItem(ctx context.Context, item model.Item) error } + +type keyStore interface { + MasterKey() []byte +} + +type cipher interface { + Encrypt(data, key []byte) ([]byte, error) +} diff --git a/internal/usecase/client/item/edit/usecase.go b/internal/usecase/client/item/edit/usecase.go index 8e66193..33b34df 100644 --- a/internal/usecase/client/item/edit/usecase.go +++ b/internal/usecase/client/item/edit/usecase.go @@ -2,28 +2,65 @@ package edit import ( "context" + "encoding/json" "fmt" - "time" model "github.com/bjlag/go-keeper/internal/domain/client" + dto "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" ) type Usecase struct { - store store + rpc rpc + itemStore itemStore + keyStore keyStore + cipher cipher } -func NewUsecase(store store) *Usecase { +func NewUsecase(rpc rpc, itemStore itemStore, keyStore keyStore, cipher cipher) *Usecase { return &Usecase{ - store: store, + rpc: rpc, + itemStore: itemStore, + keyStore: keyStore, + cipher: cipher, } } func (u *Usecase) Do(ctx context.Context, item model.Item) error { const op = "usecase.item.edit.Do" - item.UpdatedAt = time.Now() + var value *[]byte + if item.Value != nil { + v, err := json.Marshal(item.Value) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } - err := u.store.SaveItem(ctx, item) + value = &v + } + + data := model.EncryptedData{ + Title: item.Title, + Category: item.Category, + Value: value, + Notes: item.Notes, + } + + plainText, err := json.Marshal(data) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + encryptedData, err := u.cipher.Encrypt(plainText, u.keyStore.MasterKey()) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + err = u.rpc.UpdateItem(ctx, &dto.UpdateItemIn{ + GUID: item.GUID, + EncryptedData: encryptedData, + }) + + err = u.itemStore.SaveItem(ctx, item) if err != nil { return fmt.Errorf("%s: %w", op, err) } diff --git a/internal/usecase/client/login/contract.go b/internal/usecase/client/login/contract.go index 1fff280..eeee22a 100644 --- a/internal/usecase/client/login/contract.go +++ b/internal/usecase/client/login/contract.go @@ -9,3 +9,7 @@ import ( type client interface { Login(ctx context.Context, in rpc.LoginIn) (*rpc.LoginOut, error) } + +type tokens interface { + SaveTokens(accessToken, refreshToken string) +} diff --git a/internal/usecase/client/login/model.go b/internal/usecase/client/login/model.go index 17dac34..4efa94d 100644 --- a/internal/usecase/client/login/model.go +++ b/internal/usecase/client/login/model.go @@ -4,8 +4,3 @@ type Data struct { Email string Password string } - -type Result struct { - AccessToken string - RefreshToken string -} diff --git a/internal/usecase/client/login/usecase.go b/internal/usecase/client/login/usecase.go index 3d06e45..02d1e36 100644 --- a/internal/usecase/client/login/usecase.go +++ b/internal/usecase/client/login/usecase.go @@ -9,15 +9,17 @@ import ( type Usecase struct { client client + tokens tokens } -func NewUsecase(client client) *Usecase { +func NewUsecase(client client, tokens tokens) *Usecase { return &Usecase{ client: client, + tokens: tokens, } } -func (u *Usecase) Do(ctx context.Context, data Data) (*Result, error) { +func (u *Usecase) Do(ctx context.Context, data Data) error { const op = "usecase.login.Do" out, err := u.client.Login(ctx, rpc.LoginIn{ @@ -25,11 +27,10 @@ func (u *Usecase) Do(ctx context.Context, data Data) (*Result, error) { Password: data.Password, }) if err != nil { - return nil, fmt.Errorf("%s: %w", op, err) + return fmt.Errorf("%s: %w", op, err) } - return &Result{ - AccessToken: out.AccessToken, - RefreshToken: out.RefreshToken, - }, nil + u.tokens.SaveTokens(out.AccessToken, out.RefreshToken) + + return nil } diff --git a/internal/usecase/client/master_key/contract.go b/internal/usecase/client/master_key/contract.go new file mode 100644 index 0000000..b7df1db --- /dev/null +++ b/internal/usecase/client/master_key/contract.go @@ -0,0 +1,25 @@ +package master_key + +import ( + "context" + + model "github.com/bjlag/go-keeper/internal/domain/client" + "github.com/bjlag/go-keeper/internal/infrastructure/crypt/master_key" +) + +type tokens interface { + SaveMasterKey(key []byte) +} + +type options interface { + OptionBySlug(ctx context.Context, slug string) (*model.Option, error) + SaveOption(ctx context.Context, option model.Option) error +} + +type salter interface { + GenerateSalt() (*master_key.Salt, error) +} + +type keymaker interface { + GenerateMasterKey(password, salt []byte) []byte +} diff --git a/internal/usecase/client/master_key/model.go b/internal/usecase/client/master_key/model.go new file mode 100644 index 0000000..7743b80 --- /dev/null +++ b/internal/usecase/client/master_key/model.go @@ -0,0 +1,5 @@ +package master_key + +type Data struct { + Password string +} diff --git a/internal/usecase/client/master_key/usecase.go b/internal/usecase/client/master_key/usecase.go new file mode 100644 index 0000000..2e3c86e --- /dev/null +++ b/internal/usecase/client/master_key/usecase.go @@ -0,0 +1,62 @@ +package master_key + +import ( + "context" + "fmt" + + model "github.com/bjlag/go-keeper/internal/domain/client" + "github.com/bjlag/go-keeper/internal/infrastructure/crypt/master_key" +) + +type Usecase struct { + tokens tokens + options options + salter salter + keymaker keymaker +} + +func NewUsecase(tokens tokens, options options, salter salter, keymaker keymaker) *Usecase { + return &Usecase{ + tokens: tokens, + options: options, + salter: salter, + keymaker: keymaker, + } +} + +func (u *Usecase) Do(ctx context.Context, data Data) error { + const op = "usecase.master_key.Do" + + option, err := u.options.OptionBySlug(ctx, model.OptSaltKey) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + var salt *master_key.Salt + + if option == nil { + salt, err = u.salter.GenerateSalt() + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + err = u.options.SaveOption(ctx, model.Option{ + Slug: model.OptSaltKey, + Value: salt.ToString(), + }) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + } else { + salt, err = master_key.ParseString(option.Value) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + } + + masterKey := u.keymaker.GenerateMasterKey([]byte(data.Password), salt.Value()) + + u.tokens.SaveMasterKey(masterKey) + + return nil +} diff --git a/internal/usecase/client/register/contract.go b/internal/usecase/client/register/contract.go index 5d300f5..67c4d20 100644 --- a/internal/usecase/client/register/contract.go +++ b/internal/usecase/client/register/contract.go @@ -9,3 +9,8 @@ import ( type client interface { Register(ctx context.Context, in rpc.RegisterIn) (*rpc.RegisterOut, error) } + +type tokens interface { + SaveTokens(accessToken, refreshToken string) + SaveMasterKey(key []byte) +} diff --git a/internal/usecase/client/register/usecase.go b/internal/usecase/client/register/usecase.go index 02f0e6d..ff3de05 100644 --- a/internal/usecase/client/register/usecase.go +++ b/internal/usecase/client/register/usecase.go @@ -9,15 +9,17 @@ import ( type Usecase struct { client client + tokens tokens } -func NewUsecase(client client) *Usecase { +func NewUsecase(client client, tokens tokens) *Usecase { return &Usecase{ client: client, + tokens: tokens, } } -func (u *Usecase) Do(ctx context.Context, data Data) (*Result, error) { +func (u *Usecase) Do(ctx context.Context, data Data) error { const op = "usecase.register.Do" out, err := u.client.Register(ctx, rpc.RegisterIn{ @@ -25,11 +27,10 @@ func (u *Usecase) Do(ctx context.Context, data Data) (*Result, error) { Password: data.Password, }) if err != nil { - return nil, fmt.Errorf("%s: %w", op, err) + return fmt.Errorf("%s: %w", op, err) } - return &Result{ - AccessToken: out.AccessToken, - RefreshToken: out.RefreshToken, - }, nil + u.tokens.SaveTokens(out.AccessToken, out.RefreshToken) + + return nil } diff --git a/internal/usecase/client/sync/contract.go b/internal/usecase/client/sync/contract.go index 1b20e83..ba04aa3 100644 --- a/internal/usecase/client/sync/contract.go +++ b/internal/usecase/client/sync/contract.go @@ -11,6 +11,14 @@ type client interface { GetAllItems(ctx context.Context, in *rpc.GetAllItemsIn) (*rpc.GetAllItemsOut, error) } -type store interface { +type itemStore interface { SaveItems(ctx context.Context, items []model.RawItem) error } + +type keyStore interface { + MasterKey() []byte +} + +type cipher interface { + Decrypt(data, key []byte) ([]byte, error) +} diff --git a/internal/usecase/client/sync/usecase.go b/internal/usecase/client/sync/usecase.go index d183eb2..d4349f4 100644 --- a/internal/usecase/client/sync/usecase.go +++ b/internal/usecase/client/sync/usecase.go @@ -16,14 +16,18 @@ const ( ) type Usecase struct { - client client - store store + client client + itemStore itemStore + keyStore keyStore + cipher cipher } -func NewUsecase(client client, store store) *Usecase { +func NewUsecase(client client, itemStore itemStore, keyStore keyStore, cipher cipher) *Usecase { return &Usecase{ - client: client, - store: store, + client: client, + itemStore: itemStore, + keyStore: keyStore, + cipher: cipher, } } @@ -32,6 +36,8 @@ func (u *Usecase) Do(ctx context.Context) error { var offset uint32 + key := u.keyStore.MasterKey() + for { in := &rpc.GetAllItemsIn{ Limit: limit, @@ -47,11 +53,13 @@ func (u *Usecase) Do(ctx context.Context) error { items := make([]model.RawItem, 0, len(out.Items)) for _, item := range out.Items { - // todo расшифровка - // todo общие данные в отдельных полях + decrypted, err := u.cipher.Decrypt(item.EncryptedData, key) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } var data model.EncryptedData - err = json.Unmarshal(item.EncryptedData, &data) + err = json.Unmarshal(decrypted, &data) if err != nil { return fmt.Errorf("%s: %w", op, err) } @@ -67,7 +75,7 @@ func (u *Usecase) Do(ctx context.Context) error { }) } - err = u.store.SaveItems(ctx, items) + err = u.itemStore.SaveItems(ctx, items) if err != nil { return fmt.Errorf("%s: %w", op, err) } diff --git a/migrations/client/002_create_options_table.up.sql b/migrations/client/002_create_options_table.up.sql new file mode 100644 index 0000000..2f9aaa5 --- /dev/null +++ b/migrations/client/002_create_options_table.up.sql @@ -0,0 +1,4 @@ +CREATE TABLE IF NOT EXISTS options ( + slug text PRIMARY KEY NOT NULL, + value text NOT NULL +); diff --git a/migrations/server/002_create_data_table.up.sql b/migrations/server/002_create_data_table.up.sql index 7cb4792..698db74 100644 --- a/migrations/server/002_create_data_table.up.sql +++ b/migrations/server/002_create_data_table.up.sql @@ -1,7 +1,7 @@ CREATE TABLE IF NOT EXISTS items ( guid uuid PRIMARY KEY NOT NULL, user_guid uuid NOT NULL REFERENCES users (guid) ON DELETE RESTRICT, - encrypted_data text NOT NULL, + encrypted_data bytea NOT NULL, created_at timestamptz NOT NULL DEFAULT NOW(), updated_at timestamptz NOT NULL DEFAULT NOW() ); diff --git a/proto/keeper.proto b/proto/keeper.proto index 7a681f5..4ed7d91 100644 --- a/proto/keeper.proto +++ b/proto/keeper.proto @@ -57,10 +57,6 @@ message CreateItemIn { google.protobuf.Timestamp CreatedAt = 3; } -message CreateItemOut { - string guid = 1; -} - message GetAllItemsIn { uint32 limit = 1; uint32 offset = 2; From 01eddd709ed51c4145929517c49995b5df897d05 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Tue, 11 Mar 2025 11:44:07 +0300 Subject: [PATCH 57/94] client: refactoring --- .golangci.yml | 7 ++++++- Makefile | 2 +- internal/app/client/app.go | 12 ++++++------ internal/app/server/app.go | 6 +++--- internal/cli/model/item/create/model.go | 5 ----- internal/cli/model/item/password/model.go | 9 ++++----- internal/cli/model/item/text/model.go | 11 +++++------ internal/cli/model/login/model.go | 3 +-- internal/cli/model/master/model.go | 5 +---- internal/cli/model/register/model.go | 3 +-- internal/infrastructure/db/sqlite/sqlite.go | 1 - internal/infrastructure/rpc/client/delete_item.go | 3 ++- internal/infrastructure/rpc/client/get_all_items.go | 3 ++- internal/infrastructure/rpc/client/update_item.go | 1 + internal/infrastructure/store/client/item/store.go | 2 +- internal/rpc/delete_item/contract.go | 4 ++-- internal/rpc/delete_item/handler.go | 6 +++--- internal/usecase/client/item/edit/usecase.go | 3 +++ .../client/item/{delete => remove}/contract.go | 2 +- .../client/item/{delete => remove}/usecase.go | 4 ++-- internal/usecase/server/item/create/model.go | 3 ++- .../server/item/{delete => remove}/contract.go | 2 +- .../usecase/server/item/{delete => remove}/model.go | 2 +- .../server/item/{delete => remove}/usecase.go | 4 ++-- 24 files changed, 51 insertions(+), 52 deletions(-) rename internal/usecase/client/item/{delete => remove}/contract.go (95%) rename internal/usecase/client/item/{delete => remove}/usecase.go (91%) rename internal/usecase/server/item/{delete => remove}/contract.go (90%) rename internal/usecase/server/item/{delete => remove}/model.go (86%) rename internal/usecase/server/item/{delete => remove}/usecase.go (91%) diff --git a/.golangci.yml b/.golangci.yml index e95ac97..deba70e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -21,6 +21,10 @@ linters: - gocognit - cyclop - exhaustive + - funlen + - nilnil + - mnd + - godox linters-settings: revive: @@ -46,4 +50,5 @@ linters-settings: - error - empty - stdlib - - github\.com\/charmbracelet\/bubbletea\.Model \ No newline at end of file + - github\.com\/charmbracelet\/bubbletea\.Model + - github\.com\/golang-migrate\/migrate\/v4\/database\.Driver \ No newline at end of file diff --git a/Makefile b/Makefile index 083e732..688def5 100644 --- a/Makefile +++ b/Makefile @@ -61,7 +61,7 @@ lint: .PHONY: fmt fmt: @echo " > Start fmt" - @goimports -local "github.com/bjlag/go-keeper" -d -w $$(find . -type f -name '*.go' -not -path "*_mock.go") + @goimports -local "github.com/bjlag/go-keeper" -d -w $$(find . -type f -name '*.go' -not -path "*_mock.go" -not -path "./internal/generated/*") ## test: start testing .PHONY: test diff --git a/internal/app/client/app.go b/internal/app/client/app.go index 070cc8e..727f158 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -23,8 +23,8 @@ import ( "github.com/bjlag/go-keeper/internal/infrastructure/store/client/option" "github.com/bjlag/go-keeper/internal/infrastructure/store/client/token" "github.com/bjlag/go-keeper/internal/usecase/client/item/create" - "github.com/bjlag/go-keeper/internal/usecase/client/item/delete" "github.com/bjlag/go-keeper/internal/usecase/client/item/edit" + "github.com/bjlag/go-keeper/internal/usecase/client/item/remove" "github.com/bjlag/go-keeper/internal/usecase/client/login" mkey "github.com/bjlag/go-keeper/internal/usecase/client/master_key" "github.com/bjlag/go-keeper/internal/usecase/client/register" @@ -63,8 +63,8 @@ func (a *App) Run(ctx context.Context) error { _ = rpcClient.Close() }() - // todo базу создавать и подключаться после успешного логин - // todo название файла базы должно быть уникальным под каждую учетку под которой авторизовались + // TODO базу создавать и подключаться после успешного логин + // TODO название файла базы должно быть уникальным под каждую учетку под которой авторизовались db, err := sqlite.New("./client.db").Connect() if err != nil { a.log.Error("failed to open db", zap.Error(err)) @@ -84,7 +84,7 @@ func (a *App) Run(ctx context.Context) error { ucSync := sync.NewUsecase(rpcClient, storeItem, storeTokens, cipher) ucCreateItem := create.NewUsecase(rpcClient, storeItem, storeTokens, cipher) ucSaveItem := edit.NewUsecase(rpcClient, storeItem, storeTokens, cipher) - ucDeleteItem := delete.NewUsecase(rpcClient, storeItem) + ucRemoveItem := remove.NewUsecase(rpcClient, storeItem) fetchItem := item.NewFetcher(storeItem) @@ -93,8 +93,8 @@ func (a *App) Run(ctx context.Context) error { master.WithRegisterForm(formRegister.InitModel(ucRegister, ucMasterKey)), master.WithCreatForm(formCreate.InitModel()), master.WithListForm(list.InitModel(ucSync, fetchItem)), - master.WithPasswordItemForm(password.InitModel(ucCreateItem, ucSaveItem, ucDeleteItem)), - master.WithTextItemForm(text.InitModel(ucCreateItem, ucSaveItem, ucDeleteItem)), + master.WithPasswordItemForm(password.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem)), + master.WithTextItemForm(text.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem)), ) f, err := tea.LogToFile("debug.log", "debug") diff --git a/internal/app/server/app.go b/internal/app/server/app.go index 9f506a2..2fce32b 100644 --- a/internal/app/server/app.go +++ b/internal/app/server/app.go @@ -19,8 +19,8 @@ import ( rpcRegister "github.com/bjlag/go-keeper/internal/rpc/register" rpcUpdateItem "github.com/bjlag/go-keeper/internal/rpc/update_item" "github.com/bjlag/go-keeper/internal/usecase/server/item/create" - "github.com/bjlag/go-keeper/internal/usecase/server/item/delete" "github.com/bjlag/go-keeper/internal/usecase/server/item/get_all" + "github.com/bjlag/go-keeper/internal/usecase/server/item/remove" "github.com/bjlag/go-keeper/internal/usecase/server/item/update" "github.com/bjlag/go-keeper/internal/usecase/server/user/login" rt "github.com/bjlag/go-keeper/internal/usecase/server/user/refresh_tokens" @@ -62,7 +62,7 @@ func (a *App) Run(ctx context.Context) error { ucGetAllData := get_all.NewUsecase(dataStore) ucCreateItem := create.NewUsecase(dataStore) ucUpdateItem := update.NewUsecase(dataStore) - ucDeleteItem := delete.NewUsecase(dataStore) + ucRemoveItem := remove.NewUsecase(dataStore) s := server.NewRPCServer( server.WithAddress(a.cfg.Address.Host, a.cfg.Address.Port), @@ -75,7 +75,7 @@ func (a *App) Run(ctx context.Context) error { server.WithHandler(server.GetAllItemsMethod, rpcGetAllItems.New(ucGetAllData).Handle), server.WithHandler(server.CreateItemMethod, rpcCreateItem.New(ucCreateItem).Handle), server.WithHandler(server.UpdateItemMethod, rpcUpdateItem.New(ucUpdateItem).Handle), - server.WithHandler(server.DeleteItemMethod, rpcDeleteItem.New(ucDeleteItem).Handle), + server.WithHandler(server.DeleteItemMethod, rpcDeleteItem.New(ucRemoveItem).Handle), ) err = s.Start(ctx) diff --git a/internal/cli/model/item/create/model.go b/internal/cli/model/item/create/model.go index 662e954..1886d15 100644 --- a/internal/cli/model/item/create/model.go +++ b/internal/cli/model/item/create/model.go @@ -12,8 +12,6 @@ import ( elist "github.com/bjlag/go-keeper/internal/cli/element/list" "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/domain/client" - "github.com/bjlag/go-keeper/internal/fetcher/item" - "github.com/bjlag/go-keeper/internal/usecase/client/sync" ) const ( @@ -27,9 +25,6 @@ type Model struct { header string categories list.Model err error - - usecaseSync *sync.Usecase - fetcherItem *item.Fetcher } func InitModel() *Model { diff --git a/internal/cli/model/item/password/model.go b/internal/cli/model/item/password/model.go index 5179b18..88c3bfe 100644 --- a/internal/cli/model/item/password/model.go +++ b/internal/cli/model/item/password/model.go @@ -18,8 +18,8 @@ import ( "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/domain/client" "github.com/bjlag/go-keeper/internal/usecase/client/item/create" - "github.com/bjlag/go-keeper/internal/usecase/client/item/delete" "github.com/bjlag/go-keeper/internal/usecase/client/item/edit" + "github.com/bjlag/go-keeper/internal/usecase/client/item/remove" ) const ( @@ -70,10 +70,10 @@ type Model struct { usecaseCreate *create.Usecase usecaseEdit *edit.Usecase - usecaseDelete *delete.Usecase + usecaseDelete *remove.Usecase } -func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *delete.Usecase) *Model { +func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *remove.Usecase) *Model { return &Model{ help: help.New(), header: "Регистрация", @@ -97,8 +97,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: for i := range m.elements { - switch e := m.elements[i].(type) { - case textinput.Model: + if e, ok := m.elements[i].(textinput.Model); ok { e.Width = msg.Width } } diff --git a/internal/cli/model/item/text/model.go b/internal/cli/model/item/text/model.go index 176c26b..3a1d919 100644 --- a/internal/cli/model/item/text/model.go +++ b/internal/cli/model/item/text/model.go @@ -6,6 +6,7 @@ import ( "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/textarea" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/google/uuid" @@ -17,9 +18,8 @@ import ( "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/domain/client" "github.com/bjlag/go-keeper/internal/usecase/client/item/create" - "github.com/bjlag/go-keeper/internal/usecase/client/item/delete" "github.com/bjlag/go-keeper/internal/usecase/client/item/edit" - "github.com/charmbracelet/bubbles/textarea" + "github.com/bjlag/go-keeper/internal/usecase/client/item/remove" ) const ( @@ -63,10 +63,10 @@ type Model struct { usecaseCreate *create.Usecase usecaseEdit *edit.Usecase - usecaseDelete *delete.Usecase + usecaseDelete *remove.Usecase } -func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *delete.Usecase) *Model { +func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *remove.Usecase) *Model { return &Model{ help: help.New(), header: "Регистрация", @@ -90,8 +90,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: for i := range m.elements { - switch e := m.elements[i].(type) { - case textinput.Model: + if e, ok := m.elements[i].(textinput.Model); ok { e.Width = msg.Width } } diff --git a/internal/cli/model/login/model.go b/internal/cli/model/login/model.go index b28c172..b335a77 100644 --- a/internal/cli/model/login/model.go +++ b/internal/cli/model/login/model.go @@ -95,8 +95,7 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return f, nil case OpenMsg: for i := range f.elements { - switch e := f.elements[i].(type) { - case textinput.Model: + if e, ok := f.elements[i].(textinput.Model); ok { e.SetValue("") f.elements[i] = e } diff --git a/internal/cli/model/master/model.go b/internal/cli/model/master/model.go index 1ac77ef..d6900bb 100644 --- a/internal/cli/model/master/model.go +++ b/internal/cli/model/master/model.go @@ -98,8 +98,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } for i := range m.elements { - switch e := m.elements[i].(type) { - case button.Button: + if e, ok := m.elements[i].(button.Button); ok { if i == m.pos { e.Focus() m.elements[i] = e @@ -111,8 +110,6 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } return m, nil - case key.Matches(msg, common.Keys.Quit): - return m, tea.Quit } case OpenMsg: diff --git a/internal/cli/model/register/model.go b/internal/cli/model/register/model.go index bde6a43..854b2ce 100644 --- a/internal/cli/model/register/model.go +++ b/internal/cli/model/register/model.go @@ -90,8 +90,7 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: for i := range f.elements { - switch e := f.elements[i].(type) { - case textinput.Model: + if e, ok := f.elements[i].(textinput.Model); ok { e.Width = msg.Width } } diff --git a/internal/infrastructure/db/sqlite/sqlite.go b/internal/infrastructure/db/sqlite/sqlite.go index 2d7b142..46a1965 100644 --- a/internal/infrastructure/db/sqlite/sqlite.go +++ b/internal/infrastructure/db/sqlite/sqlite.go @@ -22,7 +22,6 @@ func (l SQLite) Connect() (*sqlx.DB, error) { db, err := sqlx.Connect("sqlite3", l.dsn) if err != nil { - return nil, fmt.Errorf("%s: %w", op, err) } diff --git a/internal/infrastructure/rpc/client/delete_item.go b/internal/infrastructure/rpc/client/delete_item.go index 1a90f93..9bac1b5 100644 --- a/internal/infrastructure/rpc/client/delete_item.go +++ b/internal/infrastructure/rpc/client/delete_item.go @@ -4,8 +4,9 @@ import ( "context" "fmt" - "github.com/bjlag/go-keeper/internal/generated/rpc" "github.com/google/uuid" + + "github.com/bjlag/go-keeper/internal/generated/rpc" ) type DeleteItemIn struct { diff --git a/internal/infrastructure/rpc/client/get_all_items.go b/internal/infrastructure/rpc/client/get_all_items.go index 31bbea0..7248911 100644 --- a/internal/infrastructure/rpc/client/get_all_items.go +++ b/internal/infrastructure/rpc/client/get_all_items.go @@ -3,9 +3,10 @@ package client import ( "context" "fmt" - "github.com/google/uuid" "time" + "github.com/google/uuid" + "github.com/bjlag/go-keeper/internal/generated/rpc" ) diff --git a/internal/infrastructure/rpc/client/update_item.go b/internal/infrastructure/rpc/client/update_item.go index 354adf3..4fddce3 100644 --- a/internal/infrastructure/rpc/client/update_item.go +++ b/internal/infrastructure/rpc/client/update_item.go @@ -3,6 +3,7 @@ package client import ( "context" "fmt" + "github.com/google/uuid" "github.com/bjlag/go-keeper/internal/generated/rpc" diff --git a/internal/infrastructure/store/client/item/store.go b/internal/infrastructure/store/client/item/store.go index 93afc98..57fb915 100644 --- a/internal/infrastructure/store/client/item/store.go +++ b/internal/infrastructure/store/client/item/store.go @@ -101,8 +101,8 @@ func (s *Store) DeleteItem(ctx context.Context, guid uuid.UUID) error { return nil } -// todo пределать на модель model.Item func (s *Store) SaveItems(ctx context.Context, items []model.RawItem) error { + // todo пределать на модель model.Item const op = prefixOp + "SaveItems" tx, err := s.db.BeginTx(ctx, nil) diff --git a/internal/rpc/delete_item/contract.go b/internal/rpc/delete_item/contract.go index 9dc0f2a..52c8330 100644 --- a/internal/rpc/delete_item/contract.go +++ b/internal/rpc/delete_item/contract.go @@ -3,9 +3,9 @@ package delete_item import ( "context" - "github.com/bjlag/go-keeper/internal/usecase/server/item/delete" + "github.com/bjlag/go-keeper/internal/usecase/server/item/remove" ) type usecase interface { - Do(ctx context.Context, data delete.In) error + Do(ctx context.Context, data remove.In) error } diff --git a/internal/rpc/delete_item/handler.go b/internal/rpc/delete_item/handler.go index 3df6944..a684ec7 100644 --- a/internal/rpc/delete_item/handler.go +++ b/internal/rpc/delete_item/handler.go @@ -13,7 +13,7 @@ import ( pb "github.com/bjlag/go-keeper/internal/generated/rpc" "github.com/bjlag/go-keeper/internal/infrastructure/auth" "github.com/bjlag/go-keeper/internal/infrastructure/logger" - "github.com/bjlag/go-keeper/internal/usecase/server/item/delete" + "github.com/bjlag/go-keeper/internal/usecase/server/item/remove" ) type Handler struct { @@ -39,12 +39,12 @@ func (h *Handler) Handle(ctx context.Context, in *pb.DeleteItemIn) (*emptypb.Emp return nil, status.Error(codes.InvalidArgument, "invalid item guid") } - err = h.usecase.Do(ctx, delete.In{ + err = h.usecase.Do(ctx, remove.In{ UserGUID: userGUID, ItemGUID: itemGUID, }) if err != nil { - if errors.Is(err, delete.ErrNotFoundUpdatedData) { + if errors.Is(err, remove.ErrNotFoundUpdatedData) { return nil, status.Error(codes.NotFound, "item not found") } diff --git a/internal/usecase/client/item/edit/usecase.go b/internal/usecase/client/item/edit/usecase.go index 33b34df..87d8c93 100644 --- a/internal/usecase/client/item/edit/usecase.go +++ b/internal/usecase/client/item/edit/usecase.go @@ -59,6 +59,9 @@ func (u *Usecase) Do(ctx context.Context, item model.Item) error { GUID: item.GUID, EncryptedData: encryptedData, }) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } err = u.itemStore.SaveItem(ctx, item) if err != nil { diff --git a/internal/usecase/client/item/delete/contract.go b/internal/usecase/client/item/remove/contract.go similarity index 95% rename from internal/usecase/client/item/delete/contract.go rename to internal/usecase/client/item/remove/contract.go index bb6afff..bf9d2a1 100644 --- a/internal/usecase/client/item/delete/contract.go +++ b/internal/usecase/client/item/remove/contract.go @@ -1,4 +1,4 @@ -package delete +package remove import ( "context" diff --git a/internal/usecase/client/item/delete/usecase.go b/internal/usecase/client/item/remove/usecase.go similarity index 91% rename from internal/usecase/client/item/delete/usecase.go rename to internal/usecase/client/item/remove/usecase.go index fae2501..8e96ac0 100644 --- a/internal/usecase/client/item/delete/usecase.go +++ b/internal/usecase/client/item/remove/usecase.go @@ -1,4 +1,4 @@ -package delete +package remove import ( "context" @@ -22,7 +22,7 @@ func NewUsecase(rpc rpc, store store) *Usecase { } func (u *Usecase) Do(ctx context.Context, guid uuid.UUID) error { - const op = "usecase.item.delete.Do" + const op = "usecase.item.remove.Do" err := u.rpc.DeleteItem(ctx, &dto.DeleteItemIn{ GUID: guid, diff --git a/internal/usecase/server/item/create/model.go b/internal/usecase/server/item/create/model.go index 45ff281..3eed4ad 100644 --- a/internal/usecase/server/item/create/model.go +++ b/internal/usecase/server/item/create/model.go @@ -1,8 +1,9 @@ package create import ( - "github.com/google/uuid" "time" + + "github.com/google/uuid" ) type In struct { diff --git a/internal/usecase/server/item/delete/contract.go b/internal/usecase/server/item/remove/contract.go similarity index 90% rename from internal/usecase/server/item/delete/contract.go rename to internal/usecase/server/item/remove/contract.go index f5f27e9..89e477c 100644 --- a/internal/usecase/server/item/delete/contract.go +++ b/internal/usecase/server/item/remove/contract.go @@ -1,4 +1,4 @@ -package delete +package remove import ( "context" diff --git a/internal/usecase/server/item/delete/model.go b/internal/usecase/server/item/remove/model.go similarity index 86% rename from internal/usecase/server/item/delete/model.go rename to internal/usecase/server/item/remove/model.go index 9c04698..95a90cd 100644 --- a/internal/usecase/server/item/delete/model.go +++ b/internal/usecase/server/item/remove/model.go @@ -1,4 +1,4 @@ -package delete +package remove import ( "github.com/google/uuid" diff --git a/internal/usecase/server/item/delete/usecase.go b/internal/usecase/server/item/remove/usecase.go similarity index 91% rename from internal/usecase/server/item/delete/usecase.go rename to internal/usecase/server/item/remove/usecase.go index 8641db4..859e413 100644 --- a/internal/usecase/server/item/delete/usecase.go +++ b/internal/usecase/server/item/remove/usecase.go @@ -1,4 +1,4 @@ -package delete +package remove import ( "context" @@ -21,7 +21,7 @@ func NewUsecase(store store) *Usecase { } func (u Usecase) Do(ctx context.Context, in In) error { - const op = "usecase.item.delete.Do" + const op = "usecase.item.remove.Do" err := u.store.Delete(ctx, in.ItemGUID, in.UserGUID) if err != nil { From 9b4e948b948835ca105e4891c34b620c1eb83c6e Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Tue, 11 Mar 2025 18:35:56 +0300 Subject: [PATCH 58/94] client: ui bank card --- internal/app/client/app.go | 2 + internal/cli/common/message.go | 2 +- internal/cli/model/item/bank_card/action.go | 37 +++ internal/cli/model/item/bank_card/message.go | 13 + internal/cli/model/item/bank_card/model.go | 316 +++++++++++++++++++ internal/cli/model/item/create/model.go | 2 +- internal/cli/model/item/password/model.go | 8 +- internal/cli/model/item/text/model.go | 8 +- internal/cli/model/list/message.go | 6 +- internal/cli/model/list/model.go | 20 +- internal/cli/model/master/model.go | 16 +- internal/cli/model/master/option.go | 8 + internal/cli/model/register/model.go | 4 +- internal/domain/client/item.go | 19 +- 14 files changed, 429 insertions(+), 32 deletions(-) create mode 100644 internal/cli/model/item/bank_card/action.go create mode 100644 internal/cli/model/item/bank_card/message.go create mode 100644 internal/cli/model/item/bank_card/model.go diff --git a/internal/app/client/app.go b/internal/app/client/app.go index 727f158..c4ca9ee 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -7,6 +7,7 @@ import ( tea "github.com/charmbracelet/bubbletea" "go.uber.org/zap" + "github.com/bjlag/go-keeper/internal/cli/model/item/bank_card" formCreate "github.com/bjlag/go-keeper/internal/cli/model/item/create" "github.com/bjlag/go-keeper/internal/cli/model/item/password" "github.com/bjlag/go-keeper/internal/cli/model/item/text" @@ -95,6 +96,7 @@ func (a *App) Run(ctx context.Context) error { master.WithListForm(list.InitModel(ucSync, fetchItem)), master.WithPasswordItemForm(password.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem)), master.WithTextItemForm(text.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem)), + master.WithBankCardItemForm(bank_card.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem)), ) f, err := tea.LogToFile("debug.log", "debug") diff --git a/internal/cli/common/message.go b/internal/cli/common/message.go index 08e4b49..e89f6c8 100644 --- a/internal/cli/common/message.go +++ b/internal/cli/common/message.go @@ -7,7 +7,7 @@ import ( "github.com/bjlag/go-keeper/internal/domain/client" ) -type BackMessage struct { +type BackMsg struct { State int } diff --git a/internal/cli/model/item/bank_card/action.go b/internal/cli/model/item/bank_card/action.go new file mode 100644 index 0000000..c1a774d --- /dev/null +++ b/internal/cli/model/item/bank_card/action.go @@ -0,0 +1,37 @@ +package bank_card + +import ( + "context" + + "github.com/bjlag/go-keeper/internal/cli/element" + "github.com/bjlag/go-keeper/internal/domain/client" +) + +func (m *Model) createAction() error { + item := client.NewBankCardItem( + element.GetValue(m.elements, posCreateTitle), + element.GetValue(m.elements, posCreateNumber), + element.GetValue(m.elements, posCreateCVV), + element.GetValue(m.elements, posCreateExpiry), + element.GetValue(m.elements, posCreateNotes), + ) + + return m.usecaseCreate.Do(context.TODO(), item) +} + +func (m *Model) editAction() error { + item := client.NewBankCardItem( + element.GetValue(m.elements, posEditTitle), + element.GetValue(m.elements, posEditNumber), + element.GetValue(m.elements, posEditCVV), + element.GetValue(m.elements, posEditExpiry), + element.GetValue(m.elements, posEditNotes), + ) + item.GUID = m.guid + + return m.usecaseEdit.Do(context.TODO(), item) +} + +func (m *Model) deleteAction() error { + return m.usecaseDelete.Do(context.TODO(), m.guid) +} diff --git a/internal/cli/model/item/bank_card/message.go b/internal/cli/model/item/bank_card/message.go new file mode 100644 index 0000000..3207f48 --- /dev/null +++ b/internal/cli/model/item/bank_card/message.go @@ -0,0 +1,13 @@ +package bank_card + +import ( + tea "github.com/charmbracelet/bubbletea" + + "github.com/bjlag/go-keeper/internal/cli/element/list" +) + +type OpenMsg struct { + BackModel tea.Model + BackState int + Item *list.Item +} diff --git a/internal/cli/model/item/bank_card/model.go b/internal/cli/model/item/bank_card/model.go new file mode 100644 index 0000000..5a31361 --- /dev/null +++ b/internal/cli/model/item/bank_card/model.go @@ -0,0 +1,316 @@ +package bank_card + +import ( + "errors" + "strings" + + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/textarea" + "github.com/charmbracelet/bubbles/textinput" + tea "github.com/charmbracelet/bubbletea" + "github.com/google/uuid" + + "github.com/bjlag/go-keeper/internal/cli/common" + "github.com/bjlag/go-keeper/internal/cli/element/button" + tarea "github.com/bjlag/go-keeper/internal/cli/element/textarea" + tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" + "github.com/bjlag/go-keeper/internal/cli/style" + "github.com/bjlag/go-keeper/internal/domain/client" + "github.com/bjlag/go-keeper/internal/usecase/client/item/create" + "github.com/bjlag/go-keeper/internal/usecase/client/item/edit" + "github.com/bjlag/go-keeper/internal/usecase/client/item/remove" +) + +const ( + posEditTitle int = iota + posEditNumber + posEditExpiry + posEditCVV + posEditNotes + posEditEditBtn + posEditDeleteBtn + posEditBackBtn +) + +const ( + posCreateTitle int = iota + posCreateNumber + posCreateExpiry + posCreateCVV + posCreateNotes + posCreateSaveBtn + posCreateBackBtn +) + +type state int + +const ( + stateCreate state = iota + stateEdit +) + +var ( + errUnsupportedCommand = errors.New("unsupported command") + errInvalidValuePassword = errors.New("invalid value password") +) + +type Model struct { + main tea.Model + help help.Model + header string + state state + elements []interface{} + pos int + err error + + backModel tea.Model + backState int + + guid uuid.UUID + category client.Category + + usecaseCreate *create.Usecase + usecaseEdit *edit.Usecase + usecaseDelete *remove.Usecase +} + +func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *remove.Usecase) *Model { + return &Model{ + help: help.New(), + header: "Банковская карта", + state: stateCreate, + + usecaseCreate: usecaseCreate, + usecaseEdit: usecaseSave, + usecaseDelete: usecaseDelete, + } +} + +func (m *Model) SetMainModel(model tea.Model) { + m.main = model +} + +func (m *Model) Init() tea.Cmd { + return nil +} + +func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + for i := range m.elements { + if e, ok := m.elements[i].(textinput.Model); ok { + e.Width = msg.Width + } + } + return m, nil + case OpenMsg: + m.backState = msg.BackState + m.backModel = msg.BackModel + + if msg.Item != nil { + m.state = stateEdit + m.header = msg.Item.Title + m.guid = msg.Item.GUID + m.category = msg.Item.Category + + value, ok := msg.Item.Value.(*client.BankCard) + if !ok { + m.err = errInvalidValuePassword + return m, nil + } + + m.elements = []interface{}{ + posEditTitle: tinput.CreateDefaultTextInput("Название", tinput.WithValue(msg.Item.Title), tinput.WithFocused()), + posEditNumber: tinput.CreateDefaultTextInput("Номер", tinput.WithValue(value.Number)), + posEditExpiry: tinput.CreateDefaultTextInput("Истекает", tinput.WithValue(value.Expiry)), + posEditCVV: tinput.CreateDefaultTextInput("CVV", tinput.WithValue(value.CVV)), + posEditNotes: tarea.CreateDefaultTextArea("Заметки", tarea.WithValue(msg.Item.Notes)), + posEditEditBtn: button.CreateDefaultButton("Изменить"), + posEditDeleteBtn: button.CreateDefaultButton("Удалить"), + posEditBackBtn: button.CreateDefaultButton("Назад"), + } + + return m, nil + } + + m.state = stateCreate + m.header = "Новая банковская карта" + m.elements = []interface{}{ + posCreateTitle: tinput.CreateDefaultTextInput("Название", tinput.WithFocused()), + posCreateNumber: tinput.CreateDefaultTextInput("Номер"), + posCreateExpiry: tinput.CreateDefaultTextInput("Истекает"), + posCreateCVV: tinput.CreateDefaultTextInput("CVV"), + posCreateNotes: tarea.CreateDefaultTextArea("Заметки"), + posCreateSaveBtn: button.CreateDefaultButton("Сохранить"), + posCreateBackBtn: button.CreateDefaultButton("Назад"), + } + + return m, nil + case tea.KeyMsg: + switch { + case key.Matches(msg, common.Keys.Quit): + return m, tea.Quit + case key.Matches(msg, common.Keys.Navigation): + if key.Matches(msg, common.Keys.Down, common.Keys.Tab) { + m.pos++ + } else { + m.pos-- + } + + if m.pos > len(m.elements)-1 { + m.pos = 0 + } else if m.pos < 0 { + m.pos = len(m.elements) - 1 + } + + for i := range m.elements { + switch e := m.elements[i].(type) { + case textinput.Model: + if i == m.pos { + e.Focus() + m.elements[i] = style.SetFocusStyle(e) + continue + } + + e.Blur() + m.elements[i] = style.SetNoStyle(e) + case textarea.Model: + if i == m.pos { + e.Focus() + m.elements[i] = e + continue + } + + e.Blur() + m.elements[i] = e + case button.Button: + if i == m.pos { + e.Focus() + m.elements[i] = e + continue + } + e.Blur() + m.elements[i] = e + } + } + + return m, nil + case key.Matches(msg, common.Keys.Enter): + m.err = nil + + if m.state == stateCreate { + switch m.pos { + case posCreateSaveBtn: + m.err = m.createAction() + return m, nil + case posCreateBackBtn: + return m.backModel.Update(common.BackMsg{ + State: m.backState, + }) + default: + m.err = errUnsupportedCommand + } + + return m, nil + } + + switch m.pos { + case posEditEditBtn: + m.err = m.editAction() + return m, nil + case posEditDeleteBtn: + m.err = m.deleteAction() + return m, nil + case posEditBackBtn: + return m.backModel.Update(common.BackMsg{ + State: m.backState, + }) + default: + m.err = errUnsupportedCommand + } + + return m, nil + case key.Matches(msg, common.Keys.Back): + return m.backModel.Update(common.BackMsg{ + State: m.backState, + }) + } + } + + return m, m.updateInputs(msg) +} + +func (m *Model) View() string { + var b strings.Builder + + b.WriteString(style.TitleStyle.Render(m.header)) + b.WriteRune('\n') + + b.WriteString("Категория: ") + b.WriteString(m.category.String()) + b.WriteRune('\n') + + for i := range m.elements { + switch e := m.elements[i].(type) { + case textinput.Model: + b.WriteString(e.Placeholder) + b.WriteRune('\n') + b.WriteString(e.View()) + b.WriteRune('\n') + b.WriteRune('\n') + case textarea.Model: + b.WriteString(e.Placeholder) + b.WriteRune('\n') + b.WriteString(e.View()) + b.WriteRune('\n') + b.WriteRune('\n') + } + } + + b.WriteRune('\n') + + for i := range m.elements { + if e, ok := m.elements[i].(button.Button); ok { + b.WriteString(e.String()) + b.WriteRune('\n') + } + } + + var ( + errValidate *common.ValidateError + errForm *common.FormError + ) + + // выводим ошибки валидации + if m.err != nil && (errors.As(m.err, &errValidate) || errors.As(m.err, &errForm)) { + b.WriteString(style.ErrorBlockStyle.Render(m.err.Error())) + b.WriteRune('\n') + } + + b.WriteRune('\n') + b.WriteString(m.help.View(common.Keys)) + + // выводим прочие ошибки + if m.err != nil && !(errors.As(m.err, &errValidate) || errors.As(m.err, &errForm)) { + b.WriteRune('\n') + b.WriteString(style.ErrorBlockStyle.Render(m.err.Error())) + } + + return b.String() +} + +func (m *Model) updateInputs(msg tea.Msg) tea.Cmd { + cmds := make([]tea.Cmd, len(m.elements)) + + for i := range m.elements { + switch e := m.elements[i].(type) { + case textinput.Model: + m.elements[i], cmds[i] = e.Update(msg) + case textarea.Model: + m.elements[i], cmds[i] = e.Update(msg) + } + } + + return tea.Batch(cmds...) +} diff --git a/internal/cli/model/item/create/model.go b/internal/cli/model/item/create/model.go index 1886d15..eb4b777 100644 --- a/internal/cli/model/item/create/model.go +++ b/internal/cli/model/item/create/model.go @@ -66,7 +66,7 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return f, nil case key.Matches(msg, common.Keys.Back): - return f.main.Update(common.BackMessage{}) + return f.main.Update(common.BackMsg{}) } } diff --git a/internal/cli/model/item/password/model.go b/internal/cli/model/item/password/model.go index 88c3bfe..eb1adb4 100644 --- a/internal/cli/model/item/password/model.go +++ b/internal/cli/model/item/password/model.go @@ -76,7 +76,7 @@ type Model struct { func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *remove.Usecase) *Model { return &Model{ help: help.New(), - header: "Регистрация", + header: "Пароль", state: stateCreate, usecaseCreate: usecaseCreate, @@ -201,7 +201,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.err = m.createAction() return m, nil case posCreateBackBtn: - return m.backModel.Update(common.BackMessage{ + return m.backModel.Update(common.BackMsg{ State: m.backState, }) default: @@ -219,7 +219,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.err = m.deleteAction() return m, nil case posEditBackBtn: - return m.backModel.Update(common.BackMessage{ + return m.backModel.Update(common.BackMsg{ State: m.backState, }) default: @@ -228,7 +228,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil case key.Matches(msg, common.Keys.Back): - return m.backModel.Update(common.BackMessage{ + return m.backModel.Update(common.BackMsg{ State: m.backState, }) } diff --git a/internal/cli/model/item/text/model.go b/internal/cli/model/item/text/model.go index 3a1d919..1dbe283 100644 --- a/internal/cli/model/item/text/model.go +++ b/internal/cli/model/item/text/model.go @@ -69,7 +69,7 @@ type Model struct { func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *remove.Usecase) *Model { return &Model{ help: help.New(), - header: "Регистрация", + header: "Текст", state: stateCreate, usecaseCreate: usecaseCreate, @@ -184,7 +184,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.err = m.createAction() return m, nil case posCreateBackBtn: - return m.backModel.Update(common.BackMessage{ + return m.backModel.Update(common.BackMsg{ State: m.backState, }) default: @@ -200,7 +200,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.err = m.deleteAction() return m, nil case posEditBackBtn: - return m.backModel.Update(common.BackMessage{ + return m.backModel.Update(common.BackMsg{ State: m.backState, }) default: @@ -209,7 +209,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil case key.Matches(msg, common.Keys.Back): - return m.backModel.Update(common.BackMessage{ + return m.backModel.Update(common.BackMsg{ State: m.backState, }) } diff --git a/internal/cli/model/list/message.go b/internal/cli/model/list/message.go index caed1dc..edb33a9 100644 --- a/internal/cli/model/list/message.go +++ b/internal/cli/model/list/message.go @@ -4,10 +4,10 @@ import ( "github.com/bjlag/go-keeper/internal/domain/client" ) -type GetAllDataMessage struct{} +type GetDataMsg struct{} -type OpenCategoryListMessage struct{} +type OpenCategoriesMsg struct{} -type OpenItemListMessage struct { +type OpenItemsMsg struct { Category client.Category } diff --git a/internal/cli/model/list/model.go b/internal/cli/model/list/model.go index 3335486..66bab44 100644 --- a/internal/cli/model/list/model.go +++ b/internal/cli/model/list/model.go @@ -70,20 +70,20 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { f.categories.SetWidth(msg.Width) f.items.SetWidth(msg.Width) return f, nil - case common.BackMessage: + case common.BackMsg: switch msg.State { case stateCategoryList: - return f.Update(OpenCategoryListMessage{}) + return f.Update(OpenCategoriesMsg{}) case stateItemList: - return f.Update(OpenItemListMessage{}) + return f.Update(OpenItemsMsg{}) } - case GetAllDataMessage: + case GetDataMsg: f.state = stateCategoryList f.err = f.usecaseSync.Do(context.TODO()) - return f.Update(OpenCategoryListMessage{}) - case OpenCategoryListMessage: + return f.Update(OpenCategoriesMsg{}) + case OpenCategoriesMsg: f.state = stateCategoryList f.categories.SetItems(nil) @@ -93,7 +93,7 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { f.categories.InsertItem(len(f.categories.Items()), elist.Category{Category: client.CategoryBankCard, Title: client.CategoryBankCard.String()}) return f, nil - case OpenItemListMessage: + case OpenItemsMsg: f.state = stateItemList if c, ok := f.categories.SelectedItem().(elist.Category); ok { @@ -127,7 +127,7 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case stateCategoryList: if c, ok := f.categories.SelectedItem().(elist.Category); ok { f.selectedCategory = c.Category - return f.Update(OpenItemListMessage{ + return f.Update(OpenItemsMsg{ Category: c.Category, }) } @@ -148,9 +148,9 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, common.Keys.Back): switch f.state { case stateCategoryList: - return f.main.Update(common.BackMessage{}) + return f.main.Update(common.BackMsg{}) case stateItemList: - return f.Update(OpenCategoryListMessage{}) + return f.Update(OpenCategoriesMsg{}) } } } diff --git a/internal/cli/model/master/model.go b/internal/cli/model/master/model.go index d6900bb..4848c71 100644 --- a/internal/cli/model/master/model.go +++ b/internal/cli/model/master/model.go @@ -10,6 +10,7 @@ import ( "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/element/button" + "github.com/bjlag/go-keeper/internal/cli/model/item/bank_card" "github.com/bjlag/go-keeper/internal/cli/model/item/create" "github.com/bjlag/go-keeper/internal/cli/model/item/password" "github.com/bjlag/go-keeper/internal/cli/model/item/text" @@ -41,6 +42,7 @@ type Model struct { formList *listf.Model formPassword *password.Model formText *text.Model + formBankCard *bank_card.Model } func InitModel(opts ...Option) *Model { @@ -78,7 +80,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, common.Keys.Enter): switch m.pos { case posViewBtn: - return m.formList.Update(listf.GetAllDataMessage{}) + return m.formList.Update(listf.GetDataMsg{}) case posCreateBtn: return m.formCreate.Update(create.Open{}) case posCloseBtn: @@ -114,7 +116,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case OpenMsg: return m, nil - case common.BackMessage: + case common.BackMsg: return m.Update(OpenMsg{}) // Forms @@ -122,9 +124,9 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m.formLogin.Update(msg) case register.OpenMessage: return m.formRegister.Update(msg) - case listf.OpenCategoryListMessage: + case listf.OpenCategoriesMsg: return m.formList.Update(msg) - case listf.OpenItemListMessage: + case listf.OpenItemsMsg: return m.formList.Update(msg) case common.OpenItemMessage: switch msg.Category { @@ -140,6 +142,12 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { BackState: msg.BackState, Item: msg.Item, }) + case client.CategoryBankCard: + return m.formBankCard.Update(bank_card.OpenMsg{ + BackModel: msg.BackModel, + BackState: msg.BackState, + Item: msg.Item, + }) default: m.err = errUnsupportedCategory } diff --git a/internal/cli/model/master/option.go b/internal/cli/model/master/option.go index b0fd669..f671f58 100644 --- a/internal/cli/model/master/option.go +++ b/internal/cli/model/master/option.go @@ -1,6 +1,7 @@ package master import ( + "github.com/bjlag/go-keeper/internal/cli/model/item/bank_card" "github.com/bjlag/go-keeper/internal/cli/model/item/create" "github.com/bjlag/go-keeper/internal/cli/model/item/password" "github.com/bjlag/go-keeper/internal/cli/model/item/text" @@ -52,3 +53,10 @@ func WithTextItemForm(form *text.Model) Option { m.formText = form } } + +func WithBankCardItemForm(form *bank_card.Model) Option { + return func(m *Model) { + form.SetMainModel(m) + m.formBankCard = form + } +} diff --git a/internal/cli/model/register/model.go b/internal/cli/model/register/model.go index 854b2ce..fb04e9b 100644 --- a/internal/cli/model/register/model.go +++ b/internal/cli/model/register/model.go @@ -138,7 +138,7 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return f, nil case key.Matches(msg, common.Keys.Back): - return f.backModel.Update(common.BackMessage{}) + return f.backModel.Update(common.BackMsg{}) case key.Matches(msg, common.Keys.Enter): f.err = nil @@ -146,7 +146,7 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case f.pos == posSubmitBtn || f.pos == posEmail || f.pos == posPassword: return f.submit() case f.pos == posBackBtn: - return f.main.Update(common.BackMessage{}) + return f.main.Update(common.BackMsg{}) } return f, nil diff --git a/internal/domain/client/item.go b/internal/domain/client/item.go index a94743d..dab7a2c 100644 --- a/internal/domain/client/item.go +++ b/internal/domain/client/item.go @@ -83,6 +83,19 @@ func NewTextItem(title, note string) Item { ) } +func NewBankCardItem(title, number, cvv, expiry, note string) Item { + return NewItem( + CategoryBankCard, + title, + &BankCard{ + Number: number, + CVV: cvv, + Expiry: expiry, + }, + note, + ) +} + type Password struct { Login string `json:"login"` Password string `json:"password"` @@ -93,7 +106,7 @@ type Blob struct { } type BankCard struct { - Number string `json:"number"` - CVV string `json:"cvv"` - Expiry time.Time `json:"exp"` + Number string `json:"number"` + CVV string `json:"cvv"` + Expiry string `json:"exp"` } From 8696cb906d36119663d4b3551508746e0eb07db8 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Tue, 11 Mar 2025 19:07:35 +0300 Subject: [PATCH 59/94] client: ui file --- go.mod | 1 + go.sum | 12 +- internal/app/client/app.go | 2 + internal/cli/model/item/create/model.go | 2 +- internal/cli/model/item/file/action.go | 55 ++++ internal/cli/model/item/file/message.go | 15 + internal/cli/model/item/file/model.go | 393 ++++++++++++++++++++++++ internal/cli/model/list/model.go | 2 +- internal/cli/model/master/model.go | 8 + internal/cli/model/master/option.go | 8 + internal/domain/client/item.go | 21 +- internal/fetcher/item/fetcher.go | 4 +- 12 files changed, 507 insertions(+), 16 deletions(-) create mode 100644 internal/cli/model/item/file/action.go create mode 100644 internal/cli/model/item/file/message.go create mode 100644 internal/cli/model/item/file/model.go diff --git a/go.mod b/go.mod index a097ad8..9da4a2b 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,7 @@ require ( github.com/charmbracelet/x/ansi v0.8.0 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect diff --git a/go.sum b/go.sum index 492d844..91007af 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,8 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg6 github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= @@ -34,6 +36,8 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -142,24 +146,16 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 h1:mxSlqyb8ZAHsYDCfiXN1EDdNTdvjUJSLY+OnAUtYNYA= diff --git a/internal/app/client/app.go b/internal/app/client/app.go index c4ca9ee..3ccc2cb 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -9,6 +9,7 @@ import ( "github.com/bjlag/go-keeper/internal/cli/model/item/bank_card" formCreate "github.com/bjlag/go-keeper/internal/cli/model/item/create" + "github.com/bjlag/go-keeper/internal/cli/model/item/file" "github.com/bjlag/go-keeper/internal/cli/model/item/password" "github.com/bjlag/go-keeper/internal/cli/model/item/text" "github.com/bjlag/go-keeper/internal/cli/model/list" @@ -97,6 +98,7 @@ func (a *App) Run(ctx context.Context) error { master.WithPasswordItemForm(password.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem)), master.WithTextItemForm(text.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem)), master.WithBankCardItemForm(bank_card.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem)), + master.WithFileItemForm(file.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem)), ) f, err := tea.LogToFile("debug.log", "debug") diff --git a/internal/cli/model/item/create/model.go b/internal/cli/model/item/create/model.go index eb4b777..e78fe67 100644 --- a/internal/cli/model/item/create/model.go +++ b/internal/cli/model/item/create/model.go @@ -34,7 +34,7 @@ func InitModel() *Model { categories: elist.CreateDefaultList("Выберите категорию:", defaultWidth, listHeight, elist.CategoryDelegate{}, elist.Category{Category: client.CategoryPassword, Title: client.CategoryPassword.String()}, elist.Category{Category: client.CategoryText, Title: client.CategoryText.String()}, - elist.Category{Category: client.CategoryBlob, Title: client.CategoryBlob.String()}, + elist.Category{Category: client.CategoryFile, Title: client.CategoryFile.String()}, elist.Category{Category: client.CategoryBankCard, Title: client.CategoryBankCard.String()}, ), } diff --git a/internal/cli/model/item/file/action.go b/internal/cli/model/item/file/action.go new file mode 100644 index 0000000..293fac0 --- /dev/null +++ b/internal/cli/model/item/file/action.go @@ -0,0 +1,55 @@ +package file + +import ( + "context" + "errors" + "os" + "path/filepath" + + "github.com/bjlag/go-keeper/internal/cli/element" + "github.com/bjlag/go-keeper/internal/domain/client" +) + +var ( + errNoFileSelected = errors.New("no file selected") + errFileNotExist = errors.New("file does not exist") +) + +func (m *Model) createAction() error { + if m.selectedFile == "" { + return errNoFileSelected + } + + data, err := os.ReadFile(m.selectedFile) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return errFileNotExist + } + return err + } + + item := client.NewFileItem( + element.GetValue(m.elements, posCreateTitle), + filepath.Base(m.selectedFile), + data, + element.GetValue(m.elements, posCreateNotes), + ) + + return m.usecaseCreate.Do(context.TODO(), item) +} + +func (m *Model) editAction() error { + item := client.NewFileItem( + element.GetValue(m.elements, posEditTitle), + m.selectedFile, + m.fileData, + element.GetValue(m.elements, posEditNotes), + ) + item.GUID = m.guid + + return m.usecaseEdit.Do(context.TODO(), item) +} + +func (m *Model) deleteAction() error { + return m.usecaseDelete.Do(context.TODO(), m.guid) +} diff --git a/internal/cli/model/item/file/message.go b/internal/cli/model/item/file/message.go new file mode 100644 index 0000000..df70cb7 --- /dev/null +++ b/internal/cli/model/item/file/message.go @@ -0,0 +1,15 @@ +package file + +import ( + tea "github.com/charmbracelet/bubbletea" + + "github.com/bjlag/go-keeper/internal/cli/element/list" +) + +type OpenMsg struct { + BackModel tea.Model + BackState int + Item *list.Item +} + +type clearErrorMsg struct{} diff --git a/internal/cli/model/item/file/model.go b/internal/cli/model/item/file/model.go new file mode 100644 index 0000000..39d5179 --- /dev/null +++ b/internal/cli/model/item/file/model.go @@ -0,0 +1,393 @@ +package file + +import ( + "errors" + "os" + "strings" + "time" + + "github.com/charmbracelet/bubbles/filepicker" + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/textarea" + "github.com/charmbracelet/bubbles/textinput" + tea "github.com/charmbracelet/bubbletea" + "github.com/google/uuid" + + "github.com/bjlag/go-keeper/internal/cli/common" + "github.com/bjlag/go-keeper/internal/cli/element/button" + "github.com/bjlag/go-keeper/internal/cli/element/list" + tarea "github.com/bjlag/go-keeper/internal/cli/element/textarea" + tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" + "github.com/bjlag/go-keeper/internal/cli/style" + "github.com/bjlag/go-keeper/internal/domain/client" + "github.com/bjlag/go-keeper/internal/usecase/client/item/create" + "github.com/bjlag/go-keeper/internal/usecase/client/item/edit" + "github.com/bjlag/go-keeper/internal/usecase/client/item/remove" +) + +const ( + posEditTitle int = iota + posEditNotes + posEditEditBtn + posEditDeleteBtn + posEditBackBtn +) + +const ( + posCreateTitle int = iota + posCreateNotes + posCreateSelectFileBtn + posCreateSaveBtn + posCreateBackBtn +) + +type state int + +const ( + stateCreate state = iota + stateEdit +) + +var ( + errUnsupportedCommand = errors.New("unsupported command") + errInvalidValue = errors.New("invalid value") +) + +func clearErrorAfter(t time.Duration) tea.Cmd { + return tea.Tick(t, func(_ time.Time) tea.Msg { + return clearErrorMsg{} + }) +} + +type Model struct { + main tea.Model + help help.Model + header string + state state + elements []interface{} + pos int + err error + + filepicker filepicker.Model + selectedFile string + selectFileMode bool + fileData []byte + + backModel tea.Model + backState int + item *list.Item + + guid uuid.UUID + category client.Category + + usecaseCreate *create.Usecase + usecaseEdit *edit.Usecase + usecaseDelete *remove.Usecase +} + +func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *remove.Usecase) *Model { + fp := filepicker.New() + fp.AllowedTypes = []string{".txt", ".md", ".jpg", ".jpeg", ".png"} + fp.CurrentDirectory, _ = os.UserHomeDir() + fp.Height = 30 + + return &Model{ + help: help.New(), + header: "Файл", + state: stateCreate, + filepicker: fp, + + usecaseCreate: usecaseCreate, + usecaseEdit: usecaseSave, + usecaseDelete: usecaseDelete, + } +} + +func (m *Model) SetMainModel(model tea.Model) { + m.main = model +} + +func (m *Model) Init() tea.Cmd { + return nil +} + +func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case clearErrorMsg: + m.err = nil + case tea.WindowSizeMsg: + for i := range m.elements { + if e, ok := m.elements[i].(textinput.Model); ok { + e.Width = msg.Width + } + } + return m, nil + case OpenMsg: + m.backState = msg.BackState + m.backModel = msg.BackModel + + if msg.Item != nil { + m.state = stateEdit + m.header = msg.Item.Title + m.guid = msg.Item.GUID + m.category = msg.Item.Category + m.item = msg.Item + + value, ok := msg.Item.Value.(*client.File) + if !ok { + m.err = errInvalidValue + return m, nil + } + + m.selectedFile = value.Name + m.fileData = value.Data + + m.elements = []interface{}{ + posEditTitle: tinput.CreateDefaultTextInput("Название", tinput.WithValue(msg.Item.Title), tinput.WithFocused()), + posEditNotes: tarea.CreateDefaultTextArea("Заметки", tarea.WithValue(msg.Item.Notes)), + posEditEditBtn: button.CreateDefaultButton("Изменить"), + posEditDeleteBtn: button.CreateDefaultButton("Удалить"), + posEditBackBtn: button.CreateDefaultButton("Назад"), + } + + return m, nil + } + + m.state = stateCreate + m.header = "Новый файл" + m.elements = []interface{}{ + posCreateTitle: tinput.CreateDefaultTextInput("Название", tinput.WithFocused()), + posCreateNotes: tarea.CreateDefaultTextArea("Заметки"), + posCreateSelectFileBtn: button.CreateDefaultButton("Выбрать файл"), + posCreateSaveBtn: button.CreateDefaultButton("Сохранить"), + posCreateBackBtn: button.CreateDefaultButton("Назад"), + } + + return m, nil + case tea.KeyMsg: + switch { + case key.Matches(msg, common.Keys.Quit): + return m, tea.Quit + case key.Matches(msg, common.Keys.Navigation): + if !m.selectFileMode { + m.navigate(msg) + return m, nil + } + case key.Matches(msg, common.Keys.Enter): + m.err = nil + + if m.selectFileMode { + var cmd tea.Cmd + m.filepicker, cmd = m.filepicker.Update(msg) + + if didSelect, path := m.filepicker.DidSelectFile(msg); didSelect { + m.selectedFile = path + m.selectFileMode = false + } + + if didSelect, path := m.filepicker.DidSelectDisabledFile(msg); didSelect { + m.err = errors.New(path + " не поддерживается") + m.selectedFile = "" + return m, tea.Batch(cmd, clearErrorAfter(2*time.Second)) + } + + return m, cmd + } + + if m.state == stateCreate { + switch m.pos { + case posCreateSelectFileBtn: + m.selectFileMode = true + return m, m.filepicker.Init() + case posCreateSaveBtn: + m.err = m.createAction() + return m, nil + case posCreateBackBtn: + return m.backModel.Update(common.BackMsg{ + State: m.backState, + }) + default: + m.err = errUnsupportedCommand + } + + return m, nil + } + + switch m.pos { + case posEditEditBtn: + m.err = m.editAction() + return m, nil + case posEditDeleteBtn: + m.err = m.deleteAction() + return m, nil + case posEditBackBtn: + return m.backModel.Update(common.BackMsg{ + State: m.backState, + }) + default: + m.err = errUnsupportedCommand + } + + return m, nil + case key.Matches(msg, common.Keys.Back): + if m.selectFileMode { + m.selectFileMode = false + return m.Update(OpenMsg{ + BackModel: m.backModel, + BackState: m.backState, + Item: m.item, + }) + } + + return m.backModel.Update(common.BackMsg{ + State: m.backState, + }) + } + } + + if m.selectFileMode { + var cmd tea.Cmd + m.filepicker, cmd = m.filepicker.Update(msg) + return m, cmd + } + + return m, m.updateInputs(msg) +} + +func (m *Model) View() string { + var b strings.Builder + + b.WriteString(style.TitleStyle.Render(m.header)) + b.WriteRune('\n') + + if m.selectFileMode { + switch { + case m.err != nil: + b.WriteString(m.err.Error()) + case m.selectedFile == "": + b.WriteString("Выберите файл:") + default: + b.WriteString("Выбранный файл: " + m.selectedFile) + } + + b.WriteString("\n\n" + m.filepicker.View() + "\n") + return b.String() + } + + b.WriteString("Категория: ") + b.WriteString(m.category.String()) + b.WriteRune('\n') + + for i := range m.elements { + switch e := m.elements[i].(type) { + case textinput.Model: + b.WriteString(e.Placeholder) + b.WriteRune('\n') + b.WriteString(e.View()) + b.WriteRune('\n') + b.WriteRune('\n') + case textarea.Model: + b.WriteString(e.Placeholder) + b.WriteRune('\n') + b.WriteString(e.View()) + b.WriteRune('\n') + b.WriteRune('\n') + } + } + + if m.selectedFile != "" { + b.WriteString("Файл: " + m.selectedFile) + b.WriteRune('\n') + b.WriteRune('\n') + } + + for i := range m.elements { + if e, ok := m.elements[i].(button.Button); ok { + b.WriteString(e.String()) + b.WriteRune('\n') + } + } + + var ( + errValidate *common.ValidateError + errForm *common.FormError + ) + + // выводим ошибки валидации + if m.err != nil && (errors.As(m.err, &errValidate) || errors.As(m.err, &errForm)) { + b.WriteString(style.ErrorBlockStyle.Render(m.err.Error())) + b.WriteRune('\n') + } + + b.WriteRune('\n') + b.WriteString(m.help.View(common.Keys)) + + // выводим прочие ошибки + if m.err != nil && !(errors.As(m.err, &errValidate) || errors.As(m.err, &errForm)) { + b.WriteRune('\n') + b.WriteString(style.ErrorBlockStyle.Render(m.err.Error())) + } + + return b.String() +} + +func (m *Model) updateInputs(msg tea.Msg) tea.Cmd { + cmds := make([]tea.Cmd, len(m.elements)) + + for i := range m.elements { + switch e := m.elements[i].(type) { + case textinput.Model: + m.elements[i], cmds[i] = e.Update(msg) + case textarea.Model: + m.elements[i], cmds[i] = e.Update(msg) + } + } + + return tea.Batch(cmds...) +} + +func (m *Model) navigate(msg tea.KeyMsg) { + if key.Matches(msg, common.Keys.Down, common.Keys.Tab) { + m.pos++ + } else { + m.pos-- + } + + if m.pos > len(m.elements)-1 { + m.pos = 0 + } else if m.pos < 0 { + m.pos = len(m.elements) - 1 + } + + for i := range m.elements { + switch e := m.elements[i].(type) { + case textinput.Model: + if i == m.pos { + e.Focus() + m.elements[i] = style.SetFocusStyle(e) + continue + } + + e.Blur() + m.elements[i] = style.SetNoStyle(e) + case textarea.Model: + if i == m.pos { + e.Focus() + m.elements[i] = e + continue + } + + e.Blur() + m.elements[i] = e + case button.Button: + if i == m.pos { + e.Focus() + m.elements[i] = e + continue + } + e.Blur() + m.elements[i] = e + } + } +} diff --git a/internal/cli/model/list/model.go b/internal/cli/model/list/model.go index 66bab44..28f20e8 100644 --- a/internal/cli/model/list/model.go +++ b/internal/cli/model/list/model.go @@ -89,7 +89,7 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { f.categories.SetItems(nil) f.categories.InsertItem(len(f.categories.Items()), elist.Category{Category: client.CategoryPassword, Title: client.CategoryPassword.String()}) f.categories.InsertItem(len(f.categories.Items()), elist.Category{Category: client.CategoryText, Title: client.CategoryText.String()}) - f.categories.InsertItem(len(f.categories.Items()), elist.Category{Category: client.CategoryBlob, Title: client.CategoryBlob.String()}) + f.categories.InsertItem(len(f.categories.Items()), elist.Category{Category: client.CategoryFile, Title: client.CategoryFile.String()}) f.categories.InsertItem(len(f.categories.Items()), elist.Category{Category: client.CategoryBankCard, Title: client.CategoryBankCard.String()}) return f, nil diff --git a/internal/cli/model/master/model.go b/internal/cli/model/master/model.go index 4848c71..d8d2505 100644 --- a/internal/cli/model/master/model.go +++ b/internal/cli/model/master/model.go @@ -2,6 +2,7 @@ package master import ( "errors" + "github.com/bjlag/go-keeper/internal/cli/model/item/file" "strings" "github.com/charmbracelet/bubbles/help" @@ -43,6 +44,7 @@ type Model struct { formPassword *password.Model formText *text.Model formBankCard *bank_card.Model + formFile *file.Model } func InitModel(opts ...Option) *Model { @@ -148,6 +150,12 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { BackState: msg.BackState, Item: msg.Item, }) + case client.CategoryFile: + return m.formFile.Update(file.OpenMsg{ + BackModel: msg.BackModel, + BackState: msg.BackState, + Item: msg.Item, + }) default: m.err = errUnsupportedCategory } diff --git a/internal/cli/model/master/option.go b/internal/cli/model/master/option.go index f671f58..12a67a2 100644 --- a/internal/cli/model/master/option.go +++ b/internal/cli/model/master/option.go @@ -3,6 +3,7 @@ package master import ( "github.com/bjlag/go-keeper/internal/cli/model/item/bank_card" "github.com/bjlag/go-keeper/internal/cli/model/item/create" + "github.com/bjlag/go-keeper/internal/cli/model/item/file" "github.com/bjlag/go-keeper/internal/cli/model/item/password" "github.com/bjlag/go-keeper/internal/cli/model/item/text" "github.com/bjlag/go-keeper/internal/cli/model/list" @@ -60,3 +61,10 @@ func WithBankCardItemForm(form *bank_card.Model) Option { m.formBankCard = form } } + +func WithFileItemForm(form *file.Model) Option { + return func(m *Model) { + form.SetMainModel(m) + m.formFile = form + } +} diff --git a/internal/domain/client/item.go b/internal/domain/client/item.go index dab7a2c..62c3add 100644 --- a/internal/domain/client/item.go +++ b/internal/domain/client/item.go @@ -11,7 +11,7 @@ type Category int const ( CategoryPassword Category = iota CategoryText - CategoryBlob + CategoryFile CategoryBankCard ) @@ -21,7 +21,7 @@ func (c Category) String() string { return "Пароль" case CategoryText: return "Текст" - case CategoryBlob: + case CategoryFile: return "Файл" case CategoryBankCard: return "Банковская карта" @@ -96,13 +96,26 @@ func NewBankCardItem(title, number, cvv, expiry, note string) Item { ) } +func NewFileItem(title, name string, data []byte, note string) Item { + return NewItem( + CategoryFile, + title, + &File{ + Name: name, + Data: data, + }, + note, + ) +} + type Password struct { Login string `json:"login"` Password string `json:"password"` } -type Blob struct { - Data string `json:"data"` +type File struct { + Name string `json:"path"` + Data []byte `json:"data"` } type BankCard struct { diff --git a/internal/fetcher/item/fetcher.go b/internal/fetcher/item/fetcher.go index 796a7ce..16175d1 100644 --- a/internal/fetcher/item/fetcher.go +++ b/internal/fetcher/item/fetcher.go @@ -40,8 +40,8 @@ func (u *Fetcher) ItemsByCategory(ctx context.Context, category model.Category) v = &model.Password{} case model.CategoryText: break - case model.CategoryBlob: - v = &model.Blob{} + case model.CategoryFile: + v = &model.File{} case model.CategoryBankCard: v = &model.BankCard{} default: From 76a82ee7e5e4cfca175b42aeec94849f3d898b72 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Wed, 12 Mar 2025 18:44:29 +0300 Subject: [PATCH 60/94] client: refactoring proto file --- internal/generated/rpc/keeper.pb.go | 351 ++++++++++------------- internal/generated/rpc/keeper_grpc.pb.go | 52 ++-- proto/keeper.proto | 14 +- 3 files changed, 185 insertions(+), 232 deletions(-) diff --git a/internal/generated/rpc/keeper.pb.go b/internal/generated/rpc/keeper.pb.go index 851ffe4..d241e3a 100644 --- a/internal/generated/rpc/keeper.pb.go +++ b/internal/generated/rpc/keeper.pb.go @@ -329,29 +329,28 @@ func (x *RefreshTokensOut) GetRefreshToken() string { } // data -type CreateItemIn struct { +type GetAllItemsIn struct { state protoimpl.MessageState `protogen:"open.v1"` - Guid string `protobuf:"bytes,1,opt,name=guid,proto3" json:"guid,omitempty"` - EncryptedData []byte `protobuf:"bytes,2,opt,name=encryptedData,proto3" json:"encryptedData,omitempty"` - CreatedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=CreatedAt,proto3" json:"CreatedAt,omitempty"` + Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` + Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *CreateItemIn) Reset() { - *x = CreateItemIn{} +func (x *GetAllItemsIn) Reset() { + *x = GetAllItemsIn{} mi := &file_proto_keeper_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *CreateItemIn) String() string { +func (x *GetAllItemsIn) String() string { return protoimpl.X.MessageStringOf(x) } -func (*CreateItemIn) ProtoMessage() {} +func (*GetAllItemsIn) ProtoMessage() {} -func (x *CreateItemIn) ProtoReflect() protoreflect.Message { +func (x *GetAllItemsIn) ProtoReflect() protoreflect.Message { mi := &file_proto_keeper_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -363,53 +362,46 @@ func (x *CreateItemIn) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CreateItemIn.ProtoReflect.Descriptor instead. -func (*CreateItemIn) Descriptor() ([]byte, []int) { +// Deprecated: Use GetAllItemsIn.ProtoReflect.Descriptor instead. +func (*GetAllItemsIn) Descriptor() ([]byte, []int) { return file_proto_keeper_proto_rawDescGZIP(), []int{6} } -func (x *CreateItemIn) GetGuid() string { - if x != nil { - return x.Guid - } - return "" -} - -func (x *CreateItemIn) GetEncryptedData() []byte { +func (x *GetAllItemsIn) GetLimit() uint32 { if x != nil { - return x.EncryptedData + return x.Limit } - return nil + return 0 } -func (x *CreateItemIn) GetCreatedAt() *timestamppb.Timestamp { +func (x *GetAllItemsIn) GetOffset() uint32 { if x != nil { - return x.CreatedAt + return x.Offset } - return nil + return 0 } -type CreateItemOut struct { +type GetAllItemsOut struct { state protoimpl.MessageState `protogen:"open.v1"` - Guid string `protobuf:"bytes,1,opt,name=guid,proto3" json:"guid,omitempty"` + Items []*Item `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *CreateItemOut) Reset() { - *x = CreateItemOut{} +func (x *GetAllItemsOut) Reset() { + *x = GetAllItemsOut{} mi := &file_proto_keeper_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *CreateItemOut) String() string { +func (x *GetAllItemsOut) String() string { return protoimpl.X.MessageStringOf(x) } -func (*CreateItemOut) ProtoMessage() {} +func (*GetAllItemsOut) ProtoMessage() {} -func (x *CreateItemOut) ProtoReflect() protoreflect.Message { +func (x *GetAllItemsOut) ProtoReflect() protoreflect.Message { mi := &file_proto_keeper_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -421,40 +413,42 @@ func (x *CreateItemOut) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CreateItemOut.ProtoReflect.Descriptor instead. -func (*CreateItemOut) Descriptor() ([]byte, []int) { +// Deprecated: Use GetAllItemsOut.ProtoReflect.Descriptor instead. +func (*GetAllItemsOut) Descriptor() ([]byte, []int) { return file_proto_keeper_proto_rawDescGZIP(), []int{7} } -func (x *CreateItemOut) GetGuid() string { +func (x *GetAllItemsOut) GetItems() []*Item { if x != nil { - return x.Guid + return x.Items } - return "" + return nil } -type GetAllItemsIn struct { +type Item struct { state protoimpl.MessageState `protogen:"open.v1"` - Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` - Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` + Guid string `protobuf:"bytes,1,opt,name=guid,proto3" json:"guid,omitempty"` + EncryptedData []byte `protobuf:"bytes,2,opt,name=encryptedData,proto3" json:"encryptedData,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=CreatedAt,proto3" json:"CreatedAt,omitempty"` + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=UpdatedAt,proto3" json:"UpdatedAt,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *GetAllItemsIn) Reset() { - *x = GetAllItemsIn{} +func (x *Item) Reset() { + *x = Item{} mi := &file_proto_keeper_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *GetAllItemsIn) String() string { +func (x *Item) String() string { return protoimpl.X.MessageStringOf(x) } -func (*GetAllItemsIn) ProtoMessage() {} +func (*Item) ProtoMessage() {} -func (x *GetAllItemsIn) ProtoReflect() protoreflect.Message { +func (x *Item) ProtoReflect() protoreflect.Message { mi := &file_proto_keeper_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -466,94 +460,63 @@ func (x *GetAllItemsIn) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use GetAllItemsIn.ProtoReflect.Descriptor instead. -func (*GetAllItemsIn) Descriptor() ([]byte, []int) { +// Deprecated: Use Item.ProtoReflect.Descriptor instead. +func (*Item) Descriptor() ([]byte, []int) { return file_proto_keeper_proto_rawDescGZIP(), []int{8} } -func (x *GetAllItemsIn) GetLimit() uint32 { +func (x *Item) GetGuid() string { if x != nil { - return x.Limit + return x.Guid } - return 0 + return "" } -func (x *GetAllItemsIn) GetOffset() uint32 { +func (x *Item) GetEncryptedData() []byte { if x != nil { - return x.Offset + return x.EncryptedData } - return 0 -} - -type GetAllItemsOut struct { - state protoimpl.MessageState `protogen:"open.v1"` - Items []*Item `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *GetAllItemsOut) Reset() { - *x = GetAllItemsOut{} - mi := &file_proto_keeper_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *GetAllItemsOut) String() string { - return protoimpl.X.MessageStringOf(x) + return nil } -func (*GetAllItemsOut) ProtoMessage() {} - -func (x *GetAllItemsOut) ProtoReflect() protoreflect.Message { - mi := &file_proto_keeper_proto_msgTypes[9] +func (x *Item) GetCreatedAt() *timestamppb.Timestamp { if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms + return x.CreatedAt } - return mi.MessageOf(x) -} - -// Deprecated: Use GetAllItemsOut.ProtoReflect.Descriptor instead. -func (*GetAllItemsOut) Descriptor() ([]byte, []int) { - return file_proto_keeper_proto_rawDescGZIP(), []int{9} + return nil } -func (x *GetAllItemsOut) GetItems() []*Item { +func (x *Item) GetUpdatedAt() *timestamppb.Timestamp { if x != nil { - return x.Items + return x.UpdatedAt } return nil } -type Item struct { +type CreateItemIn struct { state protoimpl.MessageState `protogen:"open.v1"` Guid string `protobuf:"bytes,1,opt,name=guid,proto3" json:"guid,omitempty"` EncryptedData []byte `protobuf:"bytes,2,opt,name=encryptedData,proto3" json:"encryptedData,omitempty"` CreatedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=CreatedAt,proto3" json:"CreatedAt,omitempty"` - UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=UpdatedAt,proto3" json:"UpdatedAt,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *Item) Reset() { - *x = Item{} - mi := &file_proto_keeper_proto_msgTypes[10] +func (x *CreateItemIn) Reset() { + *x = CreateItemIn{} + mi := &file_proto_keeper_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *Item) String() string { +func (x *CreateItemIn) String() string { return protoimpl.X.MessageStringOf(x) } -func (*Item) ProtoMessage() {} +func (*CreateItemIn) ProtoMessage() {} -func (x *Item) ProtoReflect() protoreflect.Message { - mi := &file_proto_keeper_proto_msgTypes[10] +func (x *CreateItemIn) ProtoReflect() protoreflect.Message { + mi := &file_proto_keeper_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -564,39 +527,32 @@ func (x *Item) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use Item.ProtoReflect.Descriptor instead. -func (*Item) Descriptor() ([]byte, []int) { - return file_proto_keeper_proto_rawDescGZIP(), []int{10} +// Deprecated: Use CreateItemIn.ProtoReflect.Descriptor instead. +func (*CreateItemIn) Descriptor() ([]byte, []int) { + return file_proto_keeper_proto_rawDescGZIP(), []int{9} } -func (x *Item) GetGuid() string { +func (x *CreateItemIn) GetGuid() string { if x != nil { return x.Guid } return "" } -func (x *Item) GetEncryptedData() []byte { +func (x *CreateItemIn) GetEncryptedData() []byte { if x != nil { return x.EncryptedData } return nil } -func (x *Item) GetCreatedAt() *timestamppb.Timestamp { +func (x *CreateItemIn) GetCreatedAt() *timestamppb.Timestamp { if x != nil { return x.CreatedAt } return nil } -func (x *Item) GetUpdatedAt() *timestamppb.Timestamp { - if x != nil { - return x.UpdatedAt - } - return nil -} - type UpdateItemIn struct { state protoimpl.MessageState `protogen:"open.v1"` Guid string `protobuf:"bytes,1,opt,name=guid,proto3" json:"guid,omitempty"` @@ -607,7 +563,7 @@ type UpdateItemIn struct { func (x *UpdateItemIn) Reset() { *x = UpdateItemIn{} - mi := &file_proto_keeper_proto_msgTypes[11] + mi := &file_proto_keeper_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -619,7 +575,7 @@ func (x *UpdateItemIn) String() string { func (*UpdateItemIn) ProtoMessage() {} func (x *UpdateItemIn) ProtoReflect() protoreflect.Message { - mi := &file_proto_keeper_proto_msgTypes[11] + mi := &file_proto_keeper_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -632,7 +588,7 @@ func (x *UpdateItemIn) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateItemIn.ProtoReflect.Descriptor instead. func (*UpdateItemIn) Descriptor() ([]byte, []int) { - return file_proto_keeper_proto_rawDescGZIP(), []int{11} + return file_proto_keeper_proto_rawDescGZIP(), []int{10} } func (x *UpdateItemIn) GetGuid() string { @@ -658,7 +614,7 @@ type DeleteItemIn struct { func (x *DeleteItemIn) Reset() { *x = DeleteItemIn{} - mi := &file_proto_keeper_proto_msgTypes[12] + mi := &file_proto_keeper_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -670,7 +626,7 @@ func (x *DeleteItemIn) String() string { func (*DeleteItemIn) ProtoMessage() {} func (x *DeleteItemIn) ProtoReflect() protoreflect.Message { - mi := &file_proto_keeper_proto_msgTypes[12] + mi := &file_proto_keeper_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -683,7 +639,7 @@ func (x *DeleteItemIn) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteItemIn.ProtoReflect.Descriptor instead. func (*DeleteItemIn) Descriptor() ([]byte, []int) { - return file_proto_keeper_proto_rawDescGZIP(), []int{12} + return file_proto_keeper_proto_rawDescGZIP(), []int{11} } func (x *DeleteItemIn) GetGuid() string { @@ -729,71 +685,69 @@ var file_proto_keeper_proto_rawDesc = string([]byte{ 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, - 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x82, 0x01, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x3d, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, + 0x49, 0x74, 0x65, 0x6d, 0x73, 0x49, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, + 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6f, + 0x66, 0x66, 0x73, 0x65, 0x74, 0x22, 0x34, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, + 0x74, 0x65, 0x6d, 0x73, 0x4f, 0x75, 0x74, 0x12, 0x22, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, + 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0xb4, 0x01, 0x0a, 0x04, + 0x49, 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x65, 0x6e, 0x63, 0x72, + 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x12, 0x38, + 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x03, 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, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x64, 0x41, 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, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, + 0x41, 0x74, 0x22, 0x82, 0x01, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, + 0x6d, 0x49, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, + 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, + 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x12, 0x38, 0x0a, + 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x03, 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, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x48, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, - 0x61, 0x12, 0x38, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x03, - 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, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x23, 0x0a, 0x0d, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x4f, 0x75, 0x74, 0x12, 0x12, 0x0a, 0x04, - 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, - 0x22, 0x3d, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x49, - 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x22, - 0x34, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x4f, 0x75, - 0x74, 0x12, 0x22, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x0c, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, - 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0xb4, 0x01, 0x0a, 0x04, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x12, - 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, - 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, - 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, - 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x12, 0x38, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x03, 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, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, - 0x41, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 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, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x48, 0x0a, 0x0c, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x12, 0x12, 0x0a, 0x04, - 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, - 0x12, 0x24, 0x0a, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, - 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, - 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x22, 0x22, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, 0x32, 0x9f, 0x03, 0x0a, 0x06, 0x4b, - 0x65, 0x65, 0x70, 0x65, 0x72, 0x12, 0x33, 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, - 0x72, 0x12, 0x12, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, - 0x74, 0x65, 0x72, 0x49, 0x6e, 0x1a, 0x13, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x12, 0x2a, 0x0a, 0x05, 0x4c, 0x6f, - 0x67, 0x69, 0x6e, 0x12, 0x0f, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, - 0x69, 0x6e, 0x49, 0x6e, 0x1a, 0x10, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, - 0x67, 0x69, 0x6e, 0x4f, 0x75, 0x74, 0x12, 0x42, 0x0a, 0x0d, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, - 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x17, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, - 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x49, 0x6e, - 0x1a, 0x18, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, - 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x4f, 0x75, 0x74, 0x12, 0x3a, 0x0a, 0x0a, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x14, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, - 0x72, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x1a, 0x16, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3c, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, - 0x49, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x15, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x47, - 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x49, 0x6e, 0x1a, 0x16, 0x2e, 0x6b, - 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, - 0x73, 0x4f, 0x75, 0x74, 0x12, 0x3a, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x74, - 0x65, 0x6d, 0x12, 0x14, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x12, 0x3a, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x14, - 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x74, - 0x65, 0x6d, 0x49, 0x6e, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x18, 0x5a, 0x16, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, - 0x65, 0x64, 0x2f, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x61, 0x22, 0x22, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, + 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x67, 0x75, 0x69, 0x64, 0x32, 0x9f, 0x03, 0x0a, 0x06, 0x4b, 0x65, 0x65, 0x70, 0x65, 0x72, + 0x12, 0x33, 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x12, 0x2e, 0x6b, + 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, + 0x1a, 0x13, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x65, 0x72, 0x4f, 0x75, 0x74, 0x12, 0x2a, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x0f, + 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x1a, + 0x10, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x4f, 0x75, + 0x74, 0x12, 0x42, 0x0a, 0x0d, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x73, 0x12, 0x17, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, + 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x49, 0x6e, 0x1a, 0x18, 0x2e, 0x6b, 0x65, + 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x73, 0x4f, 0x75, 0x74, 0x12, 0x3c, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, + 0x74, 0x65, 0x6d, 0x73, 0x12, 0x15, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x47, 0x65, + 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x49, 0x6e, 0x1a, 0x16, 0x2e, 0x6b, 0x65, + 0x65, 0x70, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, + 0x4f, 0x75, 0x74, 0x12, 0x3a, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, + 0x6d, 0x12, 0x14, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, + 0x3a, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x14, 0x2e, + 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, + 0x6d, 0x49, 0x6e, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3a, 0x0a, 0x0a, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x14, 0x2e, 0x6b, 0x65, 0x65, 0x70, + 0x65, 0x72, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x1a, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x18, 0x5a, 0x16, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x72, 0x70, + 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -808,7 +762,7 @@ func file_proto_keeper_proto_rawDescGZIP() []byte { return file_proto_keeper_proto_rawDescData } -var file_proto_keeper_proto_msgTypes = make([]protoimpl.MessageInfo, 13) +var file_proto_keeper_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_proto_keeper_proto_goTypes = []any{ (*RegisterIn)(nil), // 0: keeper.RegisterIn (*RegisterOut)(nil), // 1: keeper.RegisterOut @@ -816,35 +770,34 @@ var file_proto_keeper_proto_goTypes = []any{ (*LoginOut)(nil), // 3: keeper.LoginOut (*RefreshTokensIn)(nil), // 4: keeper.RefreshTokensIn (*RefreshTokensOut)(nil), // 5: keeper.RefreshTokensOut - (*CreateItemIn)(nil), // 6: keeper.CreateItemIn - (*CreateItemOut)(nil), // 7: keeper.CreateItemOut - (*GetAllItemsIn)(nil), // 8: keeper.GetAllItemsIn - (*GetAllItemsOut)(nil), // 9: keeper.GetAllItemsOut - (*Item)(nil), // 10: keeper.Item - (*UpdateItemIn)(nil), // 11: keeper.UpdateItemIn - (*DeleteItemIn)(nil), // 12: keeper.DeleteItemIn - (*timestamppb.Timestamp)(nil), // 13: google.protobuf.Timestamp - (*emptypb.Empty)(nil), // 14: google.protobuf.Empty + (*GetAllItemsIn)(nil), // 6: keeper.GetAllItemsIn + (*GetAllItemsOut)(nil), // 7: keeper.GetAllItemsOut + (*Item)(nil), // 8: keeper.Item + (*CreateItemIn)(nil), // 9: keeper.CreateItemIn + (*UpdateItemIn)(nil), // 10: keeper.UpdateItemIn + (*DeleteItemIn)(nil), // 11: keeper.DeleteItemIn + (*timestamppb.Timestamp)(nil), // 12: google.protobuf.Timestamp + (*emptypb.Empty)(nil), // 13: google.protobuf.Empty } var file_proto_keeper_proto_depIdxs = []int32{ - 13, // 0: keeper.CreateItemIn.CreatedAt:type_name -> google.protobuf.Timestamp - 10, // 1: keeper.GetAllItemsOut.items:type_name -> keeper.Item - 13, // 2: keeper.Item.CreatedAt:type_name -> google.protobuf.Timestamp - 13, // 3: keeper.Item.UpdatedAt:type_name -> google.protobuf.Timestamp + 8, // 0: keeper.GetAllItemsOut.items:type_name -> keeper.Item + 12, // 1: keeper.Item.CreatedAt:type_name -> google.protobuf.Timestamp + 12, // 2: keeper.Item.UpdatedAt:type_name -> google.protobuf.Timestamp + 12, // 3: keeper.CreateItemIn.CreatedAt:type_name -> google.protobuf.Timestamp 0, // 4: keeper.Keeper.Register:input_type -> keeper.RegisterIn 2, // 5: keeper.Keeper.Login:input_type -> keeper.LoginIn 4, // 6: keeper.Keeper.RefreshTokens:input_type -> keeper.RefreshTokensIn - 6, // 7: keeper.Keeper.CreateItem:input_type -> keeper.CreateItemIn - 8, // 8: keeper.Keeper.GetAllItems:input_type -> keeper.GetAllItemsIn - 11, // 9: keeper.Keeper.UpdateItem:input_type -> keeper.UpdateItemIn - 12, // 10: keeper.Keeper.DeleteItem:input_type -> keeper.DeleteItemIn + 6, // 7: keeper.Keeper.GetAllItems:input_type -> keeper.GetAllItemsIn + 9, // 8: keeper.Keeper.CreateItem:input_type -> keeper.CreateItemIn + 10, // 9: keeper.Keeper.UpdateItem:input_type -> keeper.UpdateItemIn + 11, // 10: keeper.Keeper.DeleteItem:input_type -> keeper.DeleteItemIn 1, // 11: keeper.Keeper.Register:output_type -> keeper.RegisterOut 3, // 12: keeper.Keeper.Login:output_type -> keeper.LoginOut 5, // 13: keeper.Keeper.RefreshTokens:output_type -> keeper.RefreshTokensOut - 14, // 14: keeper.Keeper.CreateItem:output_type -> google.protobuf.Empty - 9, // 15: keeper.Keeper.GetAllItems:output_type -> keeper.GetAllItemsOut - 14, // 16: keeper.Keeper.UpdateItem:output_type -> google.protobuf.Empty - 14, // 17: keeper.Keeper.DeleteItem:output_type -> google.protobuf.Empty + 7, // 14: keeper.Keeper.GetAllItems:output_type -> keeper.GetAllItemsOut + 13, // 15: keeper.Keeper.CreateItem:output_type -> google.protobuf.Empty + 13, // 16: keeper.Keeper.UpdateItem:output_type -> google.protobuf.Empty + 13, // 17: keeper.Keeper.DeleteItem:output_type -> google.protobuf.Empty 11, // [11:18] is the sub-list for method output_type 4, // [4:11] is the sub-list for method input_type 4, // [4:4] is the sub-list for extension type_name @@ -863,7 +816,7 @@ func file_proto_keeper_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_keeper_proto_rawDesc), len(file_proto_keeper_proto_rawDesc)), NumEnums: 0, - NumMessages: 13, + NumMessages: 12, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/generated/rpc/keeper_grpc.pb.go b/internal/generated/rpc/keeper_grpc.pb.go index 3316cf4..55c9859 100644 --- a/internal/generated/rpc/keeper_grpc.pb.go +++ b/internal/generated/rpc/keeper_grpc.pb.go @@ -23,8 +23,8 @@ const ( Keeper_Register_FullMethodName = "/keeper.Keeper/Register" Keeper_Login_FullMethodName = "/keeper.Keeper/Login" Keeper_RefreshTokens_FullMethodName = "/keeper.Keeper/RefreshTokens" - Keeper_CreateItem_FullMethodName = "/keeper.Keeper/CreateItem" Keeper_GetAllItems_FullMethodName = "/keeper.Keeper/GetAllItems" + Keeper_CreateItem_FullMethodName = "/keeper.Keeper/CreateItem" Keeper_UpdateItem_FullMethodName = "/keeper.Keeper/UpdateItem" Keeper_DeleteItem_FullMethodName = "/keeper.Keeper/DeleteItem" ) @@ -38,8 +38,8 @@ type KeeperClient interface { Login(ctx context.Context, in *LoginIn, opts ...grpc.CallOption) (*LoginOut, error) RefreshTokens(ctx context.Context, in *RefreshTokensIn, opts ...grpc.CallOption) (*RefreshTokensOut, error) // data - CreateItem(ctx context.Context, in *CreateItemIn, opts ...grpc.CallOption) (*emptypb.Empty, error) GetAllItems(ctx context.Context, in *GetAllItemsIn, opts ...grpc.CallOption) (*GetAllItemsOut, error) + CreateItem(ctx context.Context, in *CreateItemIn, opts ...grpc.CallOption) (*emptypb.Empty, error) UpdateItem(ctx context.Context, in *UpdateItemIn, opts ...grpc.CallOption) (*emptypb.Empty, error) DeleteItem(ctx context.Context, in *DeleteItemIn, opts ...grpc.CallOption) (*emptypb.Empty, error) } @@ -82,20 +82,20 @@ func (c *keeperClient) RefreshTokens(ctx context.Context, in *RefreshTokensIn, o return out, nil } -func (c *keeperClient) CreateItem(ctx context.Context, in *CreateItemIn, opts ...grpc.CallOption) (*emptypb.Empty, error) { +func (c *keeperClient) GetAllItems(ctx context.Context, in *GetAllItemsIn, opts ...grpc.CallOption) (*GetAllItemsOut, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(emptypb.Empty) - err := c.cc.Invoke(ctx, Keeper_CreateItem_FullMethodName, in, out, cOpts...) + out := new(GetAllItemsOut) + err := c.cc.Invoke(ctx, Keeper_GetAllItems_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } -func (c *keeperClient) GetAllItems(ctx context.Context, in *GetAllItemsIn, opts ...grpc.CallOption) (*GetAllItemsOut, error) { +func (c *keeperClient) CreateItem(ctx context.Context, in *CreateItemIn, opts ...grpc.CallOption) (*emptypb.Empty, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(GetAllItemsOut) - err := c.cc.Invoke(ctx, Keeper_GetAllItems_FullMethodName, in, out, cOpts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, Keeper_CreateItem_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -131,8 +131,8 @@ type KeeperServer interface { Login(context.Context, *LoginIn) (*LoginOut, error) RefreshTokens(context.Context, *RefreshTokensIn) (*RefreshTokensOut, error) // data - CreateItem(context.Context, *CreateItemIn) (*emptypb.Empty, error) GetAllItems(context.Context, *GetAllItemsIn) (*GetAllItemsOut, error) + CreateItem(context.Context, *CreateItemIn) (*emptypb.Empty, error) UpdateItem(context.Context, *UpdateItemIn) (*emptypb.Empty, error) DeleteItem(context.Context, *DeleteItemIn) (*emptypb.Empty, error) } @@ -153,12 +153,12 @@ func (UnimplementedKeeperServer) Login(context.Context, *LoginIn) (*LoginOut, er func (UnimplementedKeeperServer) RefreshTokens(context.Context, *RefreshTokensIn) (*RefreshTokensOut, error) { return nil, status.Errorf(codes.Unimplemented, "method RefreshTokens not implemented") } -func (UnimplementedKeeperServer) CreateItem(context.Context, *CreateItemIn) (*emptypb.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method CreateItem not implemented") -} func (UnimplementedKeeperServer) GetAllItems(context.Context, *GetAllItemsIn) (*GetAllItemsOut, error) { return nil, status.Errorf(codes.Unimplemented, "method GetAllItems not implemented") } +func (UnimplementedKeeperServer) CreateItem(context.Context, *CreateItemIn) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateItem not implemented") +} func (UnimplementedKeeperServer) UpdateItem(context.Context, *UpdateItemIn) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateItem not implemented") } @@ -239,38 +239,38 @@ func _Keeper_RefreshTokens_Handler(srv interface{}, ctx context.Context, dec fun return interceptor(ctx, in, info, handler) } -func _Keeper_CreateItem_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(CreateItemIn) +func _Keeper_GetAllItems_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetAllItemsIn) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(KeeperServer).CreateItem(ctx, in) + return srv.(KeeperServer).GetAllItems(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: Keeper_CreateItem_FullMethodName, + FullMethod: Keeper_GetAllItems_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(KeeperServer).CreateItem(ctx, req.(*CreateItemIn)) + return srv.(KeeperServer).GetAllItems(ctx, req.(*GetAllItemsIn)) } return interceptor(ctx, in, info, handler) } -func _Keeper_GetAllItems_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetAllItemsIn) +func _Keeper_CreateItem_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateItemIn) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(KeeperServer).GetAllItems(ctx, in) + return srv.(KeeperServer).CreateItem(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: Keeper_GetAllItems_FullMethodName, + FullMethod: Keeper_CreateItem_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(KeeperServer).GetAllItems(ctx, req.(*GetAllItemsIn)) + return srv.(KeeperServer).CreateItem(ctx, req.(*CreateItemIn)) } return interceptor(ctx, in, info, handler) } @@ -330,14 +330,14 @@ var Keeper_ServiceDesc = grpc.ServiceDesc{ MethodName: "RefreshTokens", Handler: _Keeper_RefreshTokens_Handler, }, - { - MethodName: "CreateItem", - Handler: _Keeper_CreateItem_Handler, - }, { MethodName: "GetAllItems", Handler: _Keeper_GetAllItems_Handler, }, + { + MethodName: "CreateItem", + Handler: _Keeper_CreateItem_Handler, + }, { MethodName: "UpdateItem", Handler: _Keeper_UpdateItem_Handler, diff --git a/proto/keeper.proto b/proto/keeper.proto index 4ed7d91..f3e03f9 100644 --- a/proto/keeper.proto +++ b/proto/keeper.proto @@ -14,8 +14,8 @@ service Keeper { rpc RefreshTokens(RefreshTokensIn) returns (RefreshTokensOut); // data - rpc CreateItem(CreateItemIn) returns (google.protobuf.Empty); rpc GetAllItems(GetAllItemsIn) returns (GetAllItemsOut); + rpc CreateItem(CreateItemIn) returns (google.protobuf.Empty); rpc UpdateItem(UpdateItemIn) returns (google.protobuf.Empty); rpc DeleteItem(DeleteItemIn) returns (google.protobuf.Empty); } @@ -51,12 +51,6 @@ message RefreshTokensOut { } // data -message CreateItemIn { - string guid = 1; - bytes encryptedData = 2; - google.protobuf.Timestamp CreatedAt = 3; -} - message GetAllItemsIn { uint32 limit = 1; uint32 offset = 2; @@ -73,6 +67,12 @@ message Item { google.protobuf.Timestamp UpdatedAt = 4; } +message CreateItemIn { + string guid = 1; + bytes encryptedData = 2; + google.protobuf.Timestamp CreatedAt = 3; +} + message UpdateItemIn { string guid = 1; bytes encryptedData = 2; From 4a65f523021322cf6ac9392d34ff17b491e5c318 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Wed, 12 Mar 2025 23:34:33 +0300 Subject: [PATCH 61/94] client: sync password --- internal/app/client/app.go | 7 +- internal/app/server/app.go | 8 +- internal/cli/common/message.go | 3 +- internal/cli/element/list/list.go | 14 +- .../item/bank_card/message.go | 4 +- .../{model => message}/item/create/message.go | 0 .../{model => message}/item/file/message.go | 6 +- .../item/password/message.go | 4 +- internal/cli/message/item/sync/message.go | 13 + .../{model => message}/item/text/message.go | 4 +- .../cli/{model => message}/list/message.go | 0 .../cli/{model => message}/login/message.go | 0 internal/cli/model/item/bank_card/model.go | 3 +- internal/cli/model/item/create/model.go | 3 +- internal/cli/model/item/file/model.go | 10 +- internal/cli/model/item/password/action.go | 2 + internal/cli/model/item/password/model.go | 26 +- internal/cli/model/item/sync/action.go | 14 + internal/cli/model/item/sync/model.go | 224 ++++++++++ internal/cli/model/item/text/model.go | 3 +- internal/cli/model/list/model.go | 31 +- internal/cli/model/login/model.go | 5 +- internal/cli/model/master/model.go | 31 +- internal/domain/client/item.go | 6 +- internal/generated/rpc/keeper.pb.go | 417 +++++++++++++----- internal/generated/rpc/keeper_grpc.pb.go | 48 +- .../infrastructure/rpc/client/get_by_guid.go | 47 ++ .../infrastructure/rpc/client/update_item.go | 10 +- internal/infrastructure/rpc/server/method.go | 19 +- .../infrastructure/store/server/item/store.go | 50 ++- internal/rpc/get_all_items/handler.go | 2 +- internal/rpc/get_by_guid/contract.go | 12 + internal/rpc/get_by_guid/handler.go | 59 +++ internal/rpc/update_item/contract.go | 2 +- internal/rpc/update_item/handler.go | 14 +- internal/usecase/client/item/edit/contract.go | 2 +- internal/usecase/client/item/edit/usecase.go | 17 +- internal/usecase/client/item/sync/contract.go | 24 + internal/usecase/client/item/sync/usecase.go | 95 ++++ .../server/item/get_by_guid/contract.go | 13 + .../usecase/server/item/get_by_guid/model.go | 19 + .../server/item/get_by_guid/usecase.go | 37 ++ .../usecase/server/item/update/contract.go | 1 + internal/usecase/server/item/update/model.go | 1 + .../usecase/server/item/update/usecase.go | 29 +- proto/keeper.proto | 19 +- 46 files changed, 1149 insertions(+), 209 deletions(-) rename internal/cli/{model => message}/item/bank_card/message.go (63%) rename internal/cli/{model => message}/item/create/message.go (100%) rename internal/cli/{model => message}/item/file/message.go (54%) rename internal/cli/{model => message}/item/password/message.go (63%) create mode 100644 internal/cli/message/item/sync/message.go rename internal/cli/{model => message}/item/text/message.go (62%) rename internal/cli/{model => message}/list/message.go (100%) rename internal/cli/{model => message}/login/message.go (100%) create mode 100644 internal/cli/model/item/sync/action.go create mode 100644 internal/cli/model/item/sync/model.go create mode 100644 internal/infrastructure/rpc/client/get_by_guid.go create mode 100644 internal/rpc/get_by_guid/contract.go create mode 100644 internal/rpc/get_by_guid/handler.go create mode 100644 internal/usecase/client/item/sync/contract.go create mode 100644 internal/usecase/client/item/sync/usecase.go create mode 100644 internal/usecase/server/item/get_by_guid/contract.go create mode 100644 internal/usecase/server/item/get_by_guid/model.go create mode 100644 internal/usecase/server/item/get_by_guid/usecase.go diff --git a/internal/app/client/app.go b/internal/app/client/app.go index 3ccc2cb..627a647 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -3,6 +3,7 @@ package client import ( "context" "fmt" + itemSync "github.com/bjlag/go-keeper/internal/usecase/client/item/sync" tea "github.com/charmbracelet/bubbletea" "go.uber.org/zap" @@ -11,6 +12,7 @@ import ( formCreate "github.com/bjlag/go-keeper/internal/cli/model/item/create" "github.com/bjlag/go-keeper/internal/cli/model/item/file" "github.com/bjlag/go-keeper/internal/cli/model/item/password" + syncItem "github.com/bjlag/go-keeper/internal/cli/model/item/sync" "github.com/bjlag/go-keeper/internal/cli/model/item/text" "github.com/bjlag/go-keeper/internal/cli/model/list" formLogin "github.com/bjlag/go-keeper/internal/cli/model/login" @@ -84,18 +86,21 @@ func (a *App) Run(ctx context.Context) error { ucRegister := register.NewUsecase(rpcClient, storeTokens) ucMasterKey := mkey.NewUsecase(storeTokens, storeOption, salter, keymaker) ucSync := sync.NewUsecase(rpcClient, storeItem, storeTokens, cipher) + ucItemSync := itemSync.NewUsecase(rpcClient, storeItem, storeTokens, cipher) ucCreateItem := create.NewUsecase(rpcClient, storeItem, storeTokens, cipher) ucSaveItem := edit.NewUsecase(rpcClient, storeItem, storeTokens, cipher) ucRemoveItem := remove.NewUsecase(rpcClient, storeItem) fetchItem := item.NewFetcher(storeItem) + formSync := syncItem.InitModel(ucItemSync) + m := master.InitModel( master.WithLoginForm(formLogin.InitModel(ucLogin, ucMasterKey)), master.WithRegisterForm(formRegister.InitModel(ucRegister, ucMasterKey)), master.WithCreatForm(formCreate.InitModel()), master.WithListForm(list.InitModel(ucSync, fetchItem)), - master.WithPasswordItemForm(password.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem)), + master.WithPasswordItemForm(password.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem, formSync)), master.WithTextItemForm(text.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem)), master.WithBankCardItemForm(bank_card.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem)), master.WithFileItemForm(file.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem)), diff --git a/internal/app/server/app.go b/internal/app/server/app.go index 2fce32b..828dcba 100644 --- a/internal/app/server/app.go +++ b/internal/app/server/app.go @@ -14,12 +14,14 @@ import ( rpcCreateItem "github.com/bjlag/go-keeper/internal/rpc/create_item" rpcDeleteItem "github.com/bjlag/go-keeper/internal/rpc/delete_item" rpcGetAllItems "github.com/bjlag/go-keeper/internal/rpc/get_all_items" + rpcGetByGUID "github.com/bjlag/go-keeper/internal/rpc/get_by_guid" rpcLogin "github.com/bjlag/go-keeper/internal/rpc/login" rpcRefreshTokens "github.com/bjlag/go-keeper/internal/rpc/refresh_tokens" rpcRegister "github.com/bjlag/go-keeper/internal/rpc/register" rpcUpdateItem "github.com/bjlag/go-keeper/internal/rpc/update_item" "github.com/bjlag/go-keeper/internal/usecase/server/item/create" "github.com/bjlag/go-keeper/internal/usecase/server/item/get_all" + "github.com/bjlag/go-keeper/internal/usecase/server/item/get_by_guid" "github.com/bjlag/go-keeper/internal/usecase/server/item/remove" "github.com/bjlag/go-keeper/internal/usecase/server/item/update" "github.com/bjlag/go-keeper/internal/usecase/server/user/login" @@ -59,11 +61,14 @@ func (a *App) Run(ctx context.Context) error { ucRegister := register.NewUsecase(userStore, jwt) ucLogin := login.NewUsecase(userStore, jwt) ucRefreshTokens := rt.NewUsecase(userStore, jwt) - ucGetAllData := get_all.NewUsecase(dataStore) ucCreateItem := create.NewUsecase(dataStore) ucUpdateItem := update.NewUsecase(dataStore) ucRemoveItem := remove.NewUsecase(dataStore) + // todo вынести в фетчер + ucGetByGUID := get_by_guid.NewUsecase(dataStore) + ucGetAllData := get_all.NewUsecase(dataStore) + s := server.NewRPCServer( server.WithAddress(a.cfg.Address.Host, a.cfg.Address.Port), server.WithJWT(jwt), @@ -72,6 +77,7 @@ func (a *App) Run(ctx context.Context) error { server.WithHandler(server.RegisterMethod, rpcRegister.New(ucRegister).Handle), server.WithHandler(server.LoginMethod, rpcLogin.New(ucLogin).Handle), server.WithHandler(server.RefreshTokensMethod, rpcRefreshTokens.New(ucRefreshTokens).Handle), + server.WithHandler(server.GetByGUIDMethod, rpcGetByGUID.New(ucGetByGUID).Handle), server.WithHandler(server.GetAllItemsMethod, rpcGetAllItems.New(ucGetAllData).Handle), server.WithHandler(server.CreateItemMethod, rpcCreateItem.New(ucCreateItem).Handle), server.WithHandler(server.UpdateItemMethod, rpcUpdateItem.New(ucUpdateItem).Handle), diff --git a/internal/cli/common/message.go b/internal/cli/common/message.go index e89f6c8..aa6f139 100644 --- a/internal/cli/common/message.go +++ b/internal/cli/common/message.go @@ -3,7 +3,6 @@ package common import ( tea "github.com/charmbracelet/bubbletea" - "github.com/bjlag/go-keeper/internal/cli/element/list" "github.com/bjlag/go-keeper/internal/domain/client" ) @@ -14,6 +13,6 @@ type BackMsg struct { type OpenItemMessage struct { BackModel tea.Model BackState int - Item *list.Item + Item *client.Item Category client.Category } diff --git a/internal/cli/element/list/list.go b/internal/cli/element/list/list.go index 737d186..ade7d68 100644 --- a/internal/cli/element/list/list.go +++ b/internal/cli/element/list/list.go @@ -5,12 +5,10 @@ import ( "io" "strings" - "github.com/charmbracelet/bubbles/list" - tea "github.com/charmbracelet/bubbletea" - "github.com/google/uuid" - "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/domain/client" + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" ) func CreateDefaultList(title string, with, height int, itemDelegate list.ItemDelegate, items ...list.Item) list.Model { @@ -66,11 +64,7 @@ func (d CategoryDelegate) Render(w io.Writer, m list.Model, index int, listItem } type Item struct { - GUID uuid.UUID - Category client.Category - Title string - Value interface{} - Notes string + Model client.Item } func (i Item) FilterValue() string { return "" } @@ -95,7 +89,7 @@ func (d ItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list return } - str := fmt.Sprintf("%d. %s", index+1, i.Title) + str := fmt.Sprintf("%d. %s", index+1, i.Model.Title) fn := style.ListItemStyle.Render if index == m.Index() { diff --git a/internal/cli/model/item/bank_card/message.go b/internal/cli/message/item/bank_card/message.go similarity index 63% rename from internal/cli/model/item/bank_card/message.go rename to internal/cli/message/item/bank_card/message.go index 3207f48..97c056e 100644 --- a/internal/cli/model/item/bank_card/message.go +++ b/internal/cli/message/item/bank_card/message.go @@ -3,11 +3,11 @@ package bank_card import ( tea "github.com/charmbracelet/bubbletea" - "github.com/bjlag/go-keeper/internal/cli/element/list" + "github.com/bjlag/go-keeper/internal/domain/client" ) type OpenMsg struct { BackModel tea.Model BackState int - Item *list.Item + Item *client.Item } diff --git a/internal/cli/model/item/create/message.go b/internal/cli/message/item/create/message.go similarity index 100% rename from internal/cli/model/item/create/message.go rename to internal/cli/message/item/create/message.go diff --git a/internal/cli/model/item/file/message.go b/internal/cli/message/item/file/message.go similarity index 54% rename from internal/cli/model/item/file/message.go rename to internal/cli/message/item/file/message.go index df70cb7..d524924 100644 --- a/internal/cli/model/item/file/message.go +++ b/internal/cli/message/item/file/message.go @@ -3,13 +3,11 @@ package file import ( tea "github.com/charmbracelet/bubbletea" - "github.com/bjlag/go-keeper/internal/cli/element/list" + "github.com/bjlag/go-keeper/internal/domain/client" ) type OpenMsg struct { BackModel tea.Model BackState int - Item *list.Item + Item *client.Item } - -type clearErrorMsg struct{} diff --git a/internal/cli/model/item/password/message.go b/internal/cli/message/item/password/message.go similarity index 63% rename from internal/cli/model/item/password/message.go rename to internal/cli/message/item/password/message.go index 5c9e14e..7cfd662 100644 --- a/internal/cli/model/item/password/message.go +++ b/internal/cli/message/item/password/message.go @@ -3,11 +3,11 @@ package password import ( tea "github.com/charmbracelet/bubbletea" - "github.com/bjlag/go-keeper/internal/cli/element/list" + "github.com/bjlag/go-keeper/internal/domain/client" ) type OpenMsg struct { BackModel tea.Model BackState int - Item *list.Item + Item *client.Item } diff --git a/internal/cli/message/item/sync/message.go b/internal/cli/message/item/sync/message.go new file mode 100644 index 0000000..82cc128 --- /dev/null +++ b/internal/cli/message/item/sync/message.go @@ -0,0 +1,13 @@ +package sync + +import ( + tea "github.com/charmbracelet/bubbletea" + + "github.com/bjlag/go-keeper/internal/domain/client" +) + +type OpenMsg struct { + BackModel tea.Model + BackState int + Item *client.Item +} diff --git a/internal/cli/model/item/text/message.go b/internal/cli/message/item/text/message.go similarity index 62% rename from internal/cli/model/item/text/message.go rename to internal/cli/message/item/text/message.go index ad10bfa..9b29052 100644 --- a/internal/cli/model/item/text/message.go +++ b/internal/cli/message/item/text/message.go @@ -3,11 +3,11 @@ package text import ( tea "github.com/charmbracelet/bubbletea" - "github.com/bjlag/go-keeper/internal/cli/element/list" + "github.com/bjlag/go-keeper/internal/domain/client" ) type OpenMsg struct { BackModel tea.Model BackState int - Item *list.Item + Item *client.Item } diff --git a/internal/cli/model/list/message.go b/internal/cli/message/list/message.go similarity index 100% rename from internal/cli/model/list/message.go rename to internal/cli/message/list/message.go diff --git a/internal/cli/model/login/message.go b/internal/cli/message/login/message.go similarity index 100% rename from internal/cli/model/login/message.go rename to internal/cli/message/login/message.go diff --git a/internal/cli/model/item/bank_card/model.go b/internal/cli/model/item/bank_card/model.go index 5a31361..90d4263 100644 --- a/internal/cli/model/item/bank_card/model.go +++ b/internal/cli/model/item/bank_card/model.go @@ -15,6 +15,7 @@ import ( "github.com/bjlag/go-keeper/internal/cli/element/button" tarea "github.com/bjlag/go-keeper/internal/cli/element/textarea" tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" + "github.com/bjlag/go-keeper/internal/cli/message/item/bank_card" "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/domain/client" "github.com/bjlag/go-keeper/internal/usecase/client/item/create" @@ -104,7 +105,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } return m, nil - case OpenMsg: + case bank_card.OpenMsg: m.backState = msg.BackState m.backModel = msg.BackModel diff --git a/internal/cli/model/item/create/model.go b/internal/cli/model/item/create/model.go index e78fe67..bc8fc33 100644 --- a/internal/cli/model/item/create/model.go +++ b/internal/cli/model/item/create/model.go @@ -1,6 +1,7 @@ package create import ( + create2 "github.com/bjlag/go-keeper/internal/cli/message/item/create" "strings" "github.com/charmbracelet/bubbles/help" @@ -52,7 +53,7 @@ func (f *Model) Init() tea.Cmd { func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { - case Open: + case create2.Open: return f, nil case tea.KeyMsg: switch { diff --git a/internal/cli/model/item/file/model.go b/internal/cli/model/item/file/model.go index 39d5179..b1c095e 100644 --- a/internal/cli/model/item/file/model.go +++ b/internal/cli/model/item/file/model.go @@ -2,6 +2,7 @@ package file import ( "errors" + file2 "github.com/bjlag/go-keeper/internal/cli/message/item/file" "os" "strings" "time" @@ -16,7 +17,6 @@ import ( "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/element/button" - "github.com/bjlag/go-keeper/internal/cli/element/list" tarea "github.com/bjlag/go-keeper/internal/cli/element/textarea" tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" "github.com/bjlag/go-keeper/internal/cli/style" @@ -54,6 +54,8 @@ var ( errInvalidValue = errors.New("invalid value") ) +type clearErrorMsg struct{} + func clearErrorAfter(t time.Duration) tea.Cmd { return tea.Tick(t, func(_ time.Time) tea.Msg { return clearErrorMsg{} @@ -76,7 +78,7 @@ type Model struct { backModel tea.Model backState int - item *list.Item + item *client.Item guid uuid.UUID category client.Category @@ -123,7 +125,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } return m, nil - case OpenMsg: + case file2.OpenMsg: m.backState = msg.BackState m.backModel = msg.BackModel @@ -233,7 +235,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, common.Keys.Back): if m.selectFileMode { m.selectFileMode = false - return m.Update(OpenMsg{ + return m.Update(file2.OpenMsg{ BackModel: m.backModel, BackState: m.backState, Item: m.item, diff --git a/internal/cli/model/item/password/action.go b/internal/cli/model/item/password/action.go index 78c917a..70a5ca2 100644 --- a/internal/cli/model/item/password/action.go +++ b/internal/cli/model/item/password/action.go @@ -26,6 +26,8 @@ func (m *Model) editAction() error { element.GetValue(m.elements, posEditNotes), ) item.GUID = m.guid + item.CreatedAt = m.item.CreatedAt + item.UpdatedAt = m.item.UpdatedAt return m.usecaseEdit.Do(context.TODO(), item) } diff --git a/internal/cli/model/item/password/model.go b/internal/cli/model/item/password/model.go index eb1adb4..4e47d00 100644 --- a/internal/cli/model/item/password/model.go +++ b/internal/cli/model/item/password/model.go @@ -2,7 +2,9 @@ package password import ( "errors" + sync2 "github.com/bjlag/go-keeper/internal/cli/model/item/sync" "strings" + "time" "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" @@ -15,6 +17,8 @@ import ( "github.com/bjlag/go-keeper/internal/cli/element/button" tarea "github.com/bjlag/go-keeper/internal/cli/element/textarea" tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" + message "github.com/bjlag/go-keeper/internal/cli/message/item/password" + "github.com/bjlag/go-keeper/internal/cli/message/item/sync" "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/domain/client" "github.com/bjlag/go-keeper/internal/usecase/client/item/create" @@ -66,19 +70,24 @@ type Model struct { backState int guid uuid.UUID + item *client.Item category client.Category + formSync *sync2.Model + usecaseCreate *create.Usecase usecaseEdit *edit.Usecase usecaseDelete *remove.Usecase } -func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *remove.Usecase) *Model { +func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *remove.Usecase, formSync *sync2.Model) *Model { return &Model{ help: help.New(), header: "Пароль", state: stateCreate, + formSync: formSync, + usecaseCreate: usecaseCreate, usecaseEdit: usecaseSave, usecaseDelete: usecaseDelete, @@ -102,13 +111,14 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } return m, nil - case OpenMsg: + case message.OpenMsg: m.backState = msg.BackState m.backModel = msg.BackModel if msg.Item != nil { m.state = stateEdit m.header = msg.Item.Title + m.item = msg.Item m.guid = msg.Item.GUID m.category = msg.Item.Category @@ -168,7 +178,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.elements[i] = style.SetFocusStyle(e) continue } - + time.Now().Unix() e.Blur() m.elements[i] = style.SetNoStyle(e) case textarea.Model: @@ -213,7 +223,15 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch m.pos { case posEditEditBtn: - m.err = m.editAction() + err := m.editAction() + if err != nil && errors.Is(err, edit.ErrConflict) { + return m.formSync.Update(sync.OpenMsg{ + BackModel: m, + Item: m.item, + }) + } + + m.err = err return m, nil case posEditDeleteBtn: m.err = m.deleteAction() diff --git a/internal/cli/model/item/sync/action.go b/internal/cli/model/item/sync/action.go new file mode 100644 index 0000000..1a2f7e9 --- /dev/null +++ b/internal/cli/model/item/sync/action.go @@ -0,0 +1,14 @@ +package sync + +import "context" + +func (m *Model) syncAction() error { + item, err := m.usecaseSync.Do(context.TODO(), m.item.GUID) + if err != nil { + return err + } + + m.item = *item + + return nil +} diff --git a/internal/cli/model/item/sync/model.go b/internal/cli/model/item/sync/model.go new file mode 100644 index 0000000..888eb01 --- /dev/null +++ b/internal/cli/model/item/sync/model.go @@ -0,0 +1,224 @@ +package sync + +import ( + "fmt" + "strings" + + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" + tea "github.com/charmbracelet/bubbletea" + + "github.com/bjlag/go-keeper/internal/cli/common" + "github.com/bjlag/go-keeper/internal/cli/element/button" + "github.com/bjlag/go-keeper/internal/cli/message/item/password" + "github.com/bjlag/go-keeper/internal/cli/message/item/sync" + "github.com/bjlag/go-keeper/internal/cli/style" + "github.com/bjlag/go-keeper/internal/domain/client" + itemSync "github.com/bjlag/go-keeper/internal/usecase/client/item/sync" +) + +const ( + posSyncBtn int = iota + posCancelBtn +) + +type Model struct { + main tea.Model + help help.Model + header string + elements []button.Button + pos int + err error + + item client.Item + prevModel tea.Model + + usecaseSync *itemSync.Usecase +} + +func InitModel(usecaseSync *itemSync.Usecase) *Model { + return &Model{ + help: help.New(), + usecaseSync: usecaseSync, + } +} + +func (m *Model) SetMainModel(model tea.Model) { + m.main = model +} + +func (m *Model) Init() tea.Cmd { + return nil +} + +func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case sync.OpenMsg: + m.prevModel = msg.BackModel + + if msg.Item == nil { + return m.prevModel.Update(msg) + } + + m.item = *msg.Item + m.header = "Синхронизация" + + switch m.item.Category { + case client.CategoryPassword: + m.header += fmt.Sprintf(" пароля: %s", m.item.Title) + case client.CategoryText: + m.header += fmt.Sprintf(" текста: %s", m.item.Title) + case client.CategoryFile: + m.header += fmt.Sprintf(" файла: %s", m.item.Title) + case client.CategoryBankCard: + m.header += fmt.Sprintf(" банковской карты: %s", m.item.Title) + } + + m.elements = []button.Button{ + posSyncBtn: button.CreateDefaultButton("Синхронизировать"), + posCancelBtn: button.CreateDefaultButton("Отмена"), + } + + return m, nil + case tea.KeyMsg: + switch { + case key.Matches(msg, common.Keys.Quit): + return m, tea.Quit + case key.Matches(msg, common.Keys.Navigation): + if key.Matches(msg, common.Keys.Down, common.Keys.Tab) { + m.pos++ + } else { + m.pos-- + } + + if m.pos > len(m.elements)-1 { + m.pos = 0 + } else if m.pos < 0 { + m.pos = len(m.elements) - 1 + } + + for i, e := range m.elements { + if i == m.pos { + e.Focus() + m.elements[i] = e + continue + } + e.Blur() + m.elements[i] = e + } + + return m, nil + case key.Matches(msg, common.Keys.Enter): + m.err = nil + + switch m.pos { + case posSyncBtn: + err := m.syncAction() + if err != nil { + m.err = err + return m, nil + } + + switch m.item.Category { + case client.CategoryPassword: + return m.prevModel.Update(password.OpenMsg{ + Item: &m.item, + }) + } + case posCancelBtn: + switch m.item.Category { + case client.CategoryPassword: + return m.prevModel.Update(password.OpenMsg{ + Item: &m.item, + }) + } + } + + return m, nil + case key.Matches(msg, common.Keys.Back): + return m.prevModel.Update(common.BackMsg{}) + } + } + + return m, nil +} + +func (m *Model) View() string { + var b strings.Builder + + b.WriteString(style.TitleStyle.Render(m.header)) + b.WriteRune('\n') + + b.WriteString("Версия на сервере отличается от версии на клиенте.\n") + b.WriteString("Синхронизируйте сначала данные с сервером, после меняйте запись.\n") + b.WriteRune('\n') + + b.WriteString("Запись на сервере:\n") + b.WriteRune('\n') + + b.WriteString("Название") + b.WriteRune('\n') + b.WriteString(m.item.Title) + b.WriteRune('\n') + b.WriteRune('\n') + + switch v := m.item.Value.(type) { + case *client.Password: + b.WriteString("Логин") + b.WriteRune('\n') + b.WriteString(v.Login) + b.WriteRune('\n') + b.WriteRune('\n') + + b.WriteString("Пароль") + b.WriteRune('\n') + b.WriteString(v.Password) + b.WriteRune('\n') + b.WriteRune('\n') + case client.File: + b.WriteString("Название файла") + b.WriteRune('\n') + b.WriteString(v.Name) + b.WriteRune('\n') + b.WriteRune('\n') + case client.BankCard: + b.WriteString("Номер карты") + b.WriteRune('\n') + b.WriteString(v.Number) + b.WriteRune('\n') + b.WriteRune('\n') + + b.WriteString("Истекает") + b.WriteRune('\n') + b.WriteString(v.Expiry) + b.WriteRune('\n') + b.WriteRune('\n') + + b.WriteString("CVV") + b.WriteRune('\n') + b.WriteString(v.CVV) + b.WriteRune('\n') + b.WriteRune('\n') + } + + b.WriteString("Заметки") + b.WriteRune('\n') + b.WriteString(m.item.Notes) + b.WriteRune('\n') + b.WriteRune('\n') + + b.WriteRune('\n') + + for _, e := range m.elements { + b.WriteString(e.String()) + b.WriteRune('\n') + } + + // выводим прочие ошибки + if m.err != nil { + b.WriteRune('\n') + b.WriteString(style.ErrorBlockStyle.Render(m.err.Error())) + } + + return b.String() +} diff --git a/internal/cli/model/item/text/model.go b/internal/cli/model/item/text/model.go index 1dbe283..4d00379 100644 --- a/internal/cli/model/item/text/model.go +++ b/internal/cli/model/item/text/model.go @@ -2,6 +2,7 @@ package text import ( "errors" + text2 "github.com/bjlag/go-keeper/internal/cli/message/item/text" "strings" "github.com/charmbracelet/bubbles/help" @@ -95,7 +96,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } return m, nil - case OpenMsg: + case text2.OpenMsg: m.backState = msg.BackState m.backModel = msg.BackModel diff --git a/internal/cli/model/list/model.go b/internal/cli/model/list/model.go index 28f20e8..c69865e 100644 --- a/internal/cli/model/list/model.go +++ b/internal/cli/model/list/model.go @@ -2,6 +2,7 @@ package list import ( "context" + list2 "github.com/bjlag/go-keeper/internal/cli/message/list" "strings" "github.com/charmbracelet/bubbles/help" @@ -73,17 +74,17 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case common.BackMsg: switch msg.State { case stateCategoryList: - return f.Update(OpenCategoriesMsg{}) + return f.Update(list2.OpenCategoriesMsg{}) case stateItemList: - return f.Update(OpenItemsMsg{}) + return f.Update(list2.OpenItemsMsg{}) } - case GetDataMsg: + case list2.GetDataMsg: f.state = stateCategoryList f.err = f.usecaseSync.Do(context.TODO()) - return f.Update(OpenCategoriesMsg{}) - case OpenCategoriesMsg: + return f.Update(list2.OpenCategoriesMsg{}) + case list2.OpenCategoriesMsg: f.state = stateCategoryList f.categories.SetItems(nil) @@ -93,7 +94,7 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { f.categories.InsertItem(len(f.categories.Items()), elist.Category{Category: client.CategoryBankCard, Title: client.CategoryBankCard.String()}) return f, nil - case OpenItemsMsg: + case list2.OpenItemsMsg: f.state = stateItemList if c, ok := f.categories.SelectedItem().(elist.Category); ok { @@ -107,13 +108,9 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } f.items.SetItems(nil) - for _, p := range items { + for _, model := range items { f.items.InsertItem(len(f.categories.Items()), elist.Item{ - GUID: p.GUID, - Category: p.Category, - Title: p.Title, - Value: p.Value, - Notes: p.Notes, + Model: model, }) } @@ -127,19 +124,19 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case stateCategoryList: if c, ok := f.categories.SelectedItem().(elist.Category); ok { f.selectedCategory = c.Category - return f.Update(OpenItemsMsg{ + return f.Update(list2.OpenItemsMsg{ Category: c.Category, }) } case stateItemList: if i, ok := f.items.SelectedItem().(elist.Item); ok { - f.selectedCategory = i.Category + f.selectedCategory = i.Model.Category return f.main.Update(common.OpenItemMessage{ BackModel: f, BackState: f.state, - Category: i.Category, - Item: &i, + Category: i.Model.Category, + Item: &i.Model, }) } } @@ -150,7 +147,7 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case stateCategoryList: return f.main.Update(common.BackMsg{}) case stateItemList: - return f.Update(OpenCategoriesMsg{}) + return f.Update(list2.OpenCategoriesMsg{}) } } } diff --git a/internal/cli/model/login/model.go b/internal/cli/model/login/model.go index b335a77..20f4f35 100644 --- a/internal/cli/model/login/model.go +++ b/internal/cli/model/login/model.go @@ -3,6 +3,7 @@ package login import ( "context" "errors" + login2 "github.com/bjlag/go-keeper/internal/cli/message/login" "strings" "github.com/charmbracelet/bubbles/help" @@ -93,7 +94,7 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } return f, nil - case OpenMsg: + case login2.OpenMsg: for i := range f.elements { if e, ok := f.elements[i].(textinput.Model); ok { e.SetValue("") @@ -261,7 +262,7 @@ func (f *Model) submit() (tea.Model, tea.Cmd) { return f, nil } - return f.main.Update(SuccessMsg{}) + return f.main.Update(login2.SuccessMsg{}) } func (f *Model) updateInputs(msg tea.Msg) tea.Cmd { diff --git a/internal/cli/model/master/model.go b/internal/cli/model/master/model.go index d8d2505..24f98bb 100644 --- a/internal/cli/model/master/model.go +++ b/internal/cli/model/master/model.go @@ -2,7 +2,13 @@ package master import ( "errors" - "github.com/bjlag/go-keeper/internal/cli/model/item/file" + bank_card2 "github.com/bjlag/go-keeper/internal/cli/message/item/bank_card" + create2 "github.com/bjlag/go-keeper/internal/cli/message/item/create" + file2 "github.com/bjlag/go-keeper/internal/cli/message/item/file" + password2 "github.com/bjlag/go-keeper/internal/cli/message/item/password" + text2 "github.com/bjlag/go-keeper/internal/cli/message/item/text" + "github.com/bjlag/go-keeper/internal/cli/message/list" + login2 "github.com/bjlag/go-keeper/internal/cli/message/login" "strings" "github.com/charmbracelet/bubbles/help" @@ -13,6 +19,7 @@ import ( "github.com/bjlag/go-keeper/internal/cli/element/button" "github.com/bjlag/go-keeper/internal/cli/model/item/bank_card" "github.com/bjlag/go-keeper/internal/cli/model/item/create" + "github.com/bjlag/go-keeper/internal/cli/model/item/file" "github.com/bjlag/go-keeper/internal/cli/model/item/password" "github.com/bjlag/go-keeper/internal/cli/model/item/text" listf "github.com/bjlag/go-keeper/internal/cli/model/list" @@ -68,7 +75,7 @@ func InitModel(opts ...Option) *Model { func (m *Model) Init() tea.Cmd { return tea.Batch( func() tea.Msg { - return login.OpenMsg{} + return login2.OpenMsg{} }, ) } @@ -82,9 +89,9 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, common.Keys.Enter): switch m.pos { case posViewBtn: - return m.formList.Update(listf.GetDataMsg{}) + return m.formList.Update(list.GetDataMsg{}) case posCreateBtn: - return m.formCreate.Update(create.Open{}) + return m.formCreate.Update(create2.Open{}) case posCloseBtn: return m, tea.Quit } @@ -122,36 +129,36 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m.Update(OpenMsg{}) // Forms - case login.OpenMsg: + case login2.OpenMsg: return m.formLogin.Update(msg) case register.OpenMessage: return m.formRegister.Update(msg) - case listf.OpenCategoriesMsg: + case list.OpenCategoriesMsg: return m.formList.Update(msg) - case listf.OpenItemsMsg: + case list.OpenItemsMsg: return m.formList.Update(msg) case common.OpenItemMessage: switch msg.Category { case client.CategoryPassword: - return m.formPassword.Update(password.OpenMsg{ + return m.formPassword.Update(password2.OpenMsg{ BackModel: msg.BackModel, BackState: msg.BackState, Item: msg.Item, }) case client.CategoryText: - return m.formText.Update(text.OpenMsg{ + return m.formText.Update(text2.OpenMsg{ BackModel: msg.BackModel, BackState: msg.BackState, Item: msg.Item, }) case client.CategoryBankCard: - return m.formBankCard.Update(bank_card.OpenMsg{ + return m.formBankCard.Update(bank_card2.OpenMsg{ BackModel: msg.BackModel, BackState: msg.BackState, Item: msg.Item, }) case client.CategoryFile: - return m.formFile.Update(file.OpenMsg{ + return m.formFile.Update(file2.OpenMsg{ BackModel: msg.BackModel, BackState: msg.BackState, Item: msg.Item, @@ -161,7 +168,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } // Success - case login.SuccessMsg: + case login2.SuccessMsg: return m.Update(OpenMsg{}) case register.SuccessMsg: return m.Update(OpenMsg{}) diff --git a/internal/domain/client/item.go b/internal/domain/client/item.go index 62c3add..6170462 100644 --- a/internal/domain/client/item.go +++ b/internal/domain/client/item.go @@ -66,7 +66,7 @@ func NewPasswordItem(title, login, password, note string) Item { return NewItem( CategoryPassword, title, - &Password{ + Password{ Login: login, Password: password, }, @@ -87,7 +87,7 @@ func NewBankCardItem(title, number, cvv, expiry, note string) Item { return NewItem( CategoryBankCard, title, - &BankCard{ + BankCard{ Number: number, CVV: cvv, Expiry: expiry, @@ -100,7 +100,7 @@ func NewFileItem(title, name string, data []byte, note string) Item { return NewItem( CategoryFile, title, - &File{ + File{ Name: name, Data: data, }, diff --git a/internal/generated/rpc/keeper.pb.go b/internal/generated/rpc/keeper.pb.go index d241e3a..620c1f9 100644 --- a/internal/generated/rpc/keeper.pb.go +++ b/internal/generated/rpc/keeper.pb.go @@ -329,6 +329,118 @@ func (x *RefreshTokensOut) GetRefreshToken() string { } // data +type GetByGuidIn struct { + state protoimpl.MessageState `protogen:"open.v1"` + Guid string `protobuf:"bytes,1,opt,name=guid,proto3" json:"guid,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetByGuidIn) Reset() { + *x = GetByGuidIn{} + mi := &file_proto_keeper_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetByGuidIn) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetByGuidIn) ProtoMessage() {} + +func (x *GetByGuidIn) ProtoReflect() protoreflect.Message { + mi := &file_proto_keeper_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetByGuidIn.ProtoReflect.Descriptor instead. +func (*GetByGuidIn) Descriptor() ([]byte, []int) { + return file_proto_keeper_proto_rawDescGZIP(), []int{6} +} + +func (x *GetByGuidIn) GetGuid() string { + if x != nil { + return x.Guid + } + return "" +} + +type GetByGuidOut struct { + state protoimpl.MessageState `protogen:"open.v1"` + Guid string `protobuf:"bytes,1,opt,name=guid,proto3" json:"guid,omitempty"` + EncryptedData []byte `protobuf:"bytes,2,opt,name=encryptedData,proto3" json:"encryptedData,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=CreatedAt,proto3" json:"CreatedAt,omitempty"` + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=UpdatedAt,proto3" json:"UpdatedAt,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetByGuidOut) Reset() { + *x = GetByGuidOut{} + mi := &file_proto_keeper_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetByGuidOut) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetByGuidOut) ProtoMessage() {} + +func (x *GetByGuidOut) ProtoReflect() protoreflect.Message { + mi := &file_proto_keeper_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetByGuidOut.ProtoReflect.Descriptor instead. +func (*GetByGuidOut) Descriptor() ([]byte, []int) { + return file_proto_keeper_proto_rawDescGZIP(), []int{7} +} + +func (x *GetByGuidOut) GetGuid() string { + if x != nil { + return x.Guid + } + return "" +} + +func (x *GetByGuidOut) GetEncryptedData() []byte { + if x != nil { + return x.EncryptedData + } + return nil +} + +func (x *GetByGuidOut) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +func (x *GetByGuidOut) GetUpdatedAt() *timestamppb.Timestamp { + if x != nil { + return x.UpdatedAt + } + return nil +} + type GetAllItemsIn struct { state protoimpl.MessageState `protogen:"open.v1"` Limit uint32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` @@ -339,7 +451,7 @@ type GetAllItemsIn struct { func (x *GetAllItemsIn) Reset() { *x = GetAllItemsIn{} - mi := &file_proto_keeper_proto_msgTypes[6] + mi := &file_proto_keeper_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -351,7 +463,7 @@ func (x *GetAllItemsIn) String() string { func (*GetAllItemsIn) ProtoMessage() {} func (x *GetAllItemsIn) ProtoReflect() protoreflect.Message { - mi := &file_proto_keeper_proto_msgTypes[6] + mi := &file_proto_keeper_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -364,7 +476,7 @@ func (x *GetAllItemsIn) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAllItemsIn.ProtoReflect.Descriptor instead. func (*GetAllItemsIn) Descriptor() ([]byte, []int) { - return file_proto_keeper_proto_rawDescGZIP(), []int{6} + return file_proto_keeper_proto_rawDescGZIP(), []int{8} } func (x *GetAllItemsIn) GetLimit() uint32 { @@ -390,7 +502,7 @@ type GetAllItemsOut struct { func (x *GetAllItemsOut) Reset() { *x = GetAllItemsOut{} - mi := &file_proto_keeper_proto_msgTypes[7] + mi := &file_proto_keeper_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -402,7 +514,7 @@ func (x *GetAllItemsOut) String() string { func (*GetAllItemsOut) ProtoMessage() {} func (x *GetAllItemsOut) ProtoReflect() protoreflect.Message { - mi := &file_proto_keeper_proto_msgTypes[7] + mi := &file_proto_keeper_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -415,7 +527,7 @@ func (x *GetAllItemsOut) ProtoReflect() protoreflect.Message { // Deprecated: Use GetAllItemsOut.ProtoReflect.Descriptor instead. func (*GetAllItemsOut) Descriptor() ([]byte, []int) { - return file_proto_keeper_proto_rawDescGZIP(), []int{7} + return file_proto_keeper_proto_rawDescGZIP(), []int{9} } func (x *GetAllItemsOut) GetItems() []*Item { @@ -437,7 +549,7 @@ type Item struct { func (x *Item) Reset() { *x = Item{} - mi := &file_proto_keeper_proto_msgTypes[8] + mi := &file_proto_keeper_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -449,7 +561,7 @@ func (x *Item) String() string { func (*Item) ProtoMessage() {} func (x *Item) ProtoReflect() protoreflect.Message { - mi := &file_proto_keeper_proto_msgTypes[8] + mi := &file_proto_keeper_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -462,7 +574,7 @@ func (x *Item) ProtoReflect() protoreflect.Message { // Deprecated: Use Item.ProtoReflect.Descriptor instead. func (*Item) Descriptor() ([]byte, []int) { - return file_proto_keeper_proto_rawDescGZIP(), []int{8} + return file_proto_keeper_proto_rawDescGZIP(), []int{10} } func (x *Item) GetGuid() string { @@ -504,7 +616,7 @@ type CreateItemIn struct { func (x *CreateItemIn) Reset() { *x = CreateItemIn{} - mi := &file_proto_keeper_proto_msgTypes[9] + mi := &file_proto_keeper_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -516,7 +628,7 @@ func (x *CreateItemIn) String() string { func (*CreateItemIn) ProtoMessage() {} func (x *CreateItemIn) ProtoReflect() protoreflect.Message { - mi := &file_proto_keeper_proto_msgTypes[9] + mi := &file_proto_keeper_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -529,7 +641,7 @@ func (x *CreateItemIn) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateItemIn.ProtoReflect.Descriptor instead. func (*CreateItemIn) Descriptor() ([]byte, []int) { - return file_proto_keeper_proto_rawDescGZIP(), []int{9} + return file_proto_keeper_proto_rawDescGZIP(), []int{11} } func (x *CreateItemIn) GetGuid() string { @@ -557,13 +669,14 @@ type UpdateItemIn struct { state protoimpl.MessageState `protogen:"open.v1"` Guid string `protobuf:"bytes,1,opt,name=guid,proto3" json:"guid,omitempty"` EncryptedData []byte `protobuf:"bytes,2,opt,name=encryptedData,proto3" json:"encryptedData,omitempty"` + Version int64 `protobuf:"varint,3,opt,name=version,proto3" json:"version,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *UpdateItemIn) Reset() { *x = UpdateItemIn{} - mi := &file_proto_keeper_proto_msgTypes[10] + mi := &file_proto_keeper_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -575,7 +688,7 @@ func (x *UpdateItemIn) String() string { func (*UpdateItemIn) ProtoMessage() {} func (x *UpdateItemIn) ProtoReflect() protoreflect.Message { - mi := &file_proto_keeper_proto_msgTypes[10] + mi := &file_proto_keeper_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -588,7 +701,7 @@ func (x *UpdateItemIn) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateItemIn.ProtoReflect.Descriptor instead. func (*UpdateItemIn) Descriptor() ([]byte, []int) { - return file_proto_keeper_proto_rawDescGZIP(), []int{10} + return file_proto_keeper_proto_rawDescGZIP(), []int{12} } func (x *UpdateItemIn) GetGuid() string { @@ -605,6 +718,57 @@ func (x *UpdateItemIn) GetEncryptedData() []byte { return nil } +func (x *UpdateItemIn) GetVersion() int64 { + if x != nil { + return x.Version + } + return 0 +} + +type UpdateItemOut struct { + state protoimpl.MessageState `protogen:"open.v1"` + NewVersion int64 `protobuf:"varint,1,opt,name=newVersion,proto3" json:"newVersion,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateItemOut) Reset() { + *x = UpdateItemOut{} + mi := &file_proto_keeper_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateItemOut) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateItemOut) ProtoMessage() {} + +func (x *UpdateItemOut) ProtoReflect() protoreflect.Message { + mi := &file_proto_keeper_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateItemOut.ProtoReflect.Descriptor instead. +func (*UpdateItemOut) Descriptor() ([]byte, []int) { + return file_proto_keeper_proto_rawDescGZIP(), []int{13} +} + +func (x *UpdateItemOut) GetNewVersion() int64 { + if x != nil { + return x.NewVersion + } + return 0 +} + type DeleteItemIn struct { state protoimpl.MessageState `protogen:"open.v1"` Guid string `protobuf:"bytes,1,opt,name=guid,proto3" json:"guid,omitempty"` @@ -614,7 +778,7 @@ type DeleteItemIn struct { func (x *DeleteItemIn) Reset() { *x = DeleteItemIn{} - mi := &file_proto_keeper_proto_msgTypes[11] + mi := &file_proto_keeper_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -626,7 +790,7 @@ func (x *DeleteItemIn) String() string { func (*DeleteItemIn) ProtoMessage() {} func (x *DeleteItemIn) ProtoReflect() protoreflect.Message { - mi := &file_proto_keeper_proto_msgTypes[11] + mi := &file_proto_keeper_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -639,7 +803,7 @@ func (x *DeleteItemIn) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteItemIn.ProtoReflect.Descriptor instead. func (*DeleteItemIn) Descriptor() ([]byte, []int) { - return file_proto_keeper_proto_rawDescGZIP(), []int{11} + return file_proto_keeper_proto_rawDescGZIP(), []int{14} } func (x *DeleteItemIn) GetGuid() string { @@ -685,69 +849,91 @@ var file_proto_keeper_proto_rawDesc = string([]byte{ 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, - 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x3d, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, - 0x49, 0x74, 0x65, 0x6d, 0x73, 0x49, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, - 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6f, - 0x66, 0x66, 0x73, 0x65, 0x74, 0x22, 0x34, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, - 0x74, 0x65, 0x6d, 0x73, 0x4f, 0x75, 0x74, 0x12, 0x22, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, - 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0xb4, 0x01, 0x0a, 0x04, - 0x49, 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x65, 0x6e, 0x63, 0x72, - 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x12, 0x38, - 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x21, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x42, 0x79, 0x47, + 0x75, 0x69, 0x64, 0x49, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, 0x22, 0xbc, 0x01, 0x0a, 0x0c, 0x47, 0x65, + 0x74, 0x42, 0x79, 0x47, 0x75, 0x69, 0x64, 0x4f, 0x75, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x75, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, 0x12, 0x24, + 0x0a, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, + 0x44, 0x61, 0x74, 0x61, 0x12, 0x38, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, + 0x74, 0x18, 0x03, 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, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x38, + 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 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, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x64, 0x41, 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, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, - 0x41, 0x74, 0x22, 0x82, 0x01, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, - 0x6d, 0x49, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, - 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, - 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x12, 0x38, 0x0a, - 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x03, 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, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x48, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x65, - 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, - 0x61, 0x22, 0x22, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, - 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x67, 0x75, 0x69, 0x64, 0x32, 0x9f, 0x03, 0x0a, 0x06, 0x4b, 0x65, 0x65, 0x70, 0x65, 0x72, - 0x12, 0x33, 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x12, 0x2e, 0x6b, - 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, - 0x1a, 0x13, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, - 0x65, 0x72, 0x4f, 0x75, 0x74, 0x12, 0x2a, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x0f, - 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x1a, - 0x10, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x4f, 0x75, - 0x74, 0x12, 0x42, 0x0a, 0x0d, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x73, 0x12, 0x17, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, - 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x49, 0x6e, 0x1a, 0x18, 0x2e, 0x6b, 0x65, - 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x73, 0x4f, 0x75, 0x74, 0x12, 0x3c, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, - 0x74, 0x65, 0x6d, 0x73, 0x12, 0x15, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x47, 0x65, - 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x49, 0x6e, 0x1a, 0x16, 0x2e, 0x6b, 0x65, - 0x65, 0x70, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, - 0x4f, 0x75, 0x74, 0x12, 0x3a, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, - 0x6d, 0x12, 0x14, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, - 0x3a, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x14, 0x2e, - 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x3d, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x41, + 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x49, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, + 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, + 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x22, 0x34, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x41, 0x6c, + 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x4f, 0x75, 0x74, 0x12, 0x22, 0x0a, 0x05, 0x69, 0x74, 0x65, + 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, + 0x72, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0xb4, 0x01, + 0x0a, 0x04, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x65, 0x6e, + 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, + 0x12, 0x38, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x03, 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, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x38, 0x0a, 0x09, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 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, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x64, 0x41, 0x74, 0x22, 0x82, 0x01, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, + 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x65, 0x6e, 0x63, + 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x12, + 0x38, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x03, 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, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x62, 0x0a, 0x0c, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, 0x12, 0x24, 0x0a, + 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, 0x61, 0x74, 0x61, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x44, + 0x61, 0x74, 0x61, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x2f, 0x0a, + 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x4f, 0x75, 0x74, 0x12, 0x1e, + 0x0a, 0x0a, 0x6e, 0x65, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0a, 0x6e, 0x65, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x22, + 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x12, 0x12, + 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, + 0x69, 0x64, 0x32, 0xd6, 0x03, 0x0a, 0x06, 0x4b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x12, 0x33, 0x0a, + 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x12, 0x2e, 0x6b, 0x65, 0x65, 0x70, + 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x1a, 0x13, 0x2e, + 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, + 0x75, 0x74, 0x12, 0x2a, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x0f, 0x2e, 0x6b, 0x65, + 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x1a, 0x10, 0x2e, 0x6b, + 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x4f, 0x75, 0x74, 0x12, 0x42, + 0x0a, 0x0d, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, + 0x17, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x49, 0x6e, 0x1a, 0x18, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, + 0x72, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x4f, + 0x75, 0x74, 0x12, 0x36, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x42, 0x79, 0x47, 0x75, 0x69, 0x64, 0x12, + 0x13, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x79, 0x47, 0x75, + 0x69, 0x64, 0x49, 0x6e, 0x1a, 0x14, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x47, 0x65, + 0x74, 0x42, 0x79, 0x47, 0x75, 0x69, 0x64, 0x4f, 0x75, 0x74, 0x12, 0x3c, 0x0a, 0x0b, 0x47, 0x65, + 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x15, 0x2e, 0x6b, 0x65, 0x65, 0x70, + 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x49, 0x6e, + 0x1a, 0x16, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x41, 0x6c, 0x6c, + 0x49, 0x74, 0x65, 0x6d, 0x73, 0x4f, 0x75, 0x74, 0x12, 0x3a, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x14, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x1a, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x12, 0x39, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x74, + 0x65, 0x6d, 0x12, 0x14, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x1a, 0x15, 0x2e, 0x6b, 0x65, 0x65, 0x70, 0x65, + 0x72, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x4f, 0x75, 0x74, 0x12, + 0x3a, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x14, 0x2e, + 0x6b, 0x65, 0x65, 0x70, 0x65, 0x72, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3a, 0x0a, 0x0a, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x14, 0x2e, 0x6b, 0x65, 0x65, 0x70, - 0x65, 0x72, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x49, 0x6e, 0x1a, - 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x18, 0x5a, 0x16, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2f, 0x72, 0x70, - 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x18, 0x5a, 0x16, 0x69, + 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, + 0x64, 0x2f, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -762,7 +948,7 @@ func file_proto_keeper_proto_rawDescGZIP() []byte { return file_proto_keeper_proto_rawDescData } -var file_proto_keeper_proto_msgTypes = make([]protoimpl.MessageInfo, 12) +var file_proto_keeper_proto_msgTypes = make([]protoimpl.MessageInfo, 15) var file_proto_keeper_proto_goTypes = []any{ (*RegisterIn)(nil), // 0: keeper.RegisterIn (*RegisterOut)(nil), // 1: keeper.RegisterOut @@ -770,39 +956,46 @@ var file_proto_keeper_proto_goTypes = []any{ (*LoginOut)(nil), // 3: keeper.LoginOut (*RefreshTokensIn)(nil), // 4: keeper.RefreshTokensIn (*RefreshTokensOut)(nil), // 5: keeper.RefreshTokensOut - (*GetAllItemsIn)(nil), // 6: keeper.GetAllItemsIn - (*GetAllItemsOut)(nil), // 7: keeper.GetAllItemsOut - (*Item)(nil), // 8: keeper.Item - (*CreateItemIn)(nil), // 9: keeper.CreateItemIn - (*UpdateItemIn)(nil), // 10: keeper.UpdateItemIn - (*DeleteItemIn)(nil), // 11: keeper.DeleteItemIn - (*timestamppb.Timestamp)(nil), // 12: google.protobuf.Timestamp - (*emptypb.Empty)(nil), // 13: google.protobuf.Empty + (*GetByGuidIn)(nil), // 6: keeper.GetByGuidIn + (*GetByGuidOut)(nil), // 7: keeper.GetByGuidOut + (*GetAllItemsIn)(nil), // 8: keeper.GetAllItemsIn + (*GetAllItemsOut)(nil), // 9: keeper.GetAllItemsOut + (*Item)(nil), // 10: keeper.Item + (*CreateItemIn)(nil), // 11: keeper.CreateItemIn + (*UpdateItemIn)(nil), // 12: keeper.UpdateItemIn + (*UpdateItemOut)(nil), // 13: keeper.UpdateItemOut + (*DeleteItemIn)(nil), // 14: keeper.DeleteItemIn + (*timestamppb.Timestamp)(nil), // 15: google.protobuf.Timestamp + (*emptypb.Empty)(nil), // 16: google.protobuf.Empty } var file_proto_keeper_proto_depIdxs = []int32{ - 8, // 0: keeper.GetAllItemsOut.items:type_name -> keeper.Item - 12, // 1: keeper.Item.CreatedAt:type_name -> google.protobuf.Timestamp - 12, // 2: keeper.Item.UpdatedAt:type_name -> google.protobuf.Timestamp - 12, // 3: keeper.CreateItemIn.CreatedAt:type_name -> google.protobuf.Timestamp - 0, // 4: keeper.Keeper.Register:input_type -> keeper.RegisterIn - 2, // 5: keeper.Keeper.Login:input_type -> keeper.LoginIn - 4, // 6: keeper.Keeper.RefreshTokens:input_type -> keeper.RefreshTokensIn - 6, // 7: keeper.Keeper.GetAllItems:input_type -> keeper.GetAllItemsIn - 9, // 8: keeper.Keeper.CreateItem:input_type -> keeper.CreateItemIn - 10, // 9: keeper.Keeper.UpdateItem:input_type -> keeper.UpdateItemIn - 11, // 10: keeper.Keeper.DeleteItem:input_type -> keeper.DeleteItemIn - 1, // 11: keeper.Keeper.Register:output_type -> keeper.RegisterOut - 3, // 12: keeper.Keeper.Login:output_type -> keeper.LoginOut - 5, // 13: keeper.Keeper.RefreshTokens:output_type -> keeper.RefreshTokensOut - 7, // 14: keeper.Keeper.GetAllItems:output_type -> keeper.GetAllItemsOut - 13, // 15: keeper.Keeper.CreateItem:output_type -> google.protobuf.Empty - 13, // 16: keeper.Keeper.UpdateItem:output_type -> google.protobuf.Empty - 13, // 17: keeper.Keeper.DeleteItem:output_type -> google.protobuf.Empty - 11, // [11:18] is the sub-list for method output_type - 4, // [4:11] is the sub-list for method input_type - 4, // [4:4] is the sub-list for extension type_name - 4, // [4:4] is the sub-list for extension extendee - 0, // [0:4] is the sub-list for field type_name + 15, // 0: keeper.GetByGuidOut.CreatedAt:type_name -> google.protobuf.Timestamp + 15, // 1: keeper.GetByGuidOut.UpdatedAt:type_name -> google.protobuf.Timestamp + 10, // 2: keeper.GetAllItemsOut.items:type_name -> keeper.Item + 15, // 3: keeper.Item.CreatedAt:type_name -> google.protobuf.Timestamp + 15, // 4: keeper.Item.UpdatedAt:type_name -> google.protobuf.Timestamp + 15, // 5: keeper.CreateItemIn.CreatedAt:type_name -> google.protobuf.Timestamp + 0, // 6: keeper.Keeper.Register:input_type -> keeper.RegisterIn + 2, // 7: keeper.Keeper.Login:input_type -> keeper.LoginIn + 4, // 8: keeper.Keeper.RefreshTokens:input_type -> keeper.RefreshTokensIn + 6, // 9: keeper.Keeper.GetByGuid:input_type -> keeper.GetByGuidIn + 8, // 10: keeper.Keeper.GetAllItems:input_type -> keeper.GetAllItemsIn + 11, // 11: keeper.Keeper.CreateItem:input_type -> keeper.CreateItemIn + 12, // 12: keeper.Keeper.UpdateItem:input_type -> keeper.UpdateItemIn + 14, // 13: keeper.Keeper.DeleteItem:input_type -> keeper.DeleteItemIn + 1, // 14: keeper.Keeper.Register:output_type -> keeper.RegisterOut + 3, // 15: keeper.Keeper.Login:output_type -> keeper.LoginOut + 5, // 16: keeper.Keeper.RefreshTokens:output_type -> keeper.RefreshTokensOut + 7, // 17: keeper.Keeper.GetByGuid:output_type -> keeper.GetByGuidOut + 9, // 18: keeper.Keeper.GetAllItems:output_type -> keeper.GetAllItemsOut + 16, // 19: keeper.Keeper.CreateItem:output_type -> google.protobuf.Empty + 13, // 20: keeper.Keeper.UpdateItem:output_type -> keeper.UpdateItemOut + 16, // 21: keeper.Keeper.DeleteItem:output_type -> google.protobuf.Empty + 14, // [14:22] is the sub-list for method output_type + 6, // [6:14] 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_proto_keeper_proto_init() } @@ -816,7 +1009,7 @@ func file_proto_keeper_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_keeper_proto_rawDesc), len(file_proto_keeper_proto_rawDesc)), NumEnums: 0, - NumMessages: 12, + NumMessages: 15, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/generated/rpc/keeper_grpc.pb.go b/internal/generated/rpc/keeper_grpc.pb.go index 55c9859..c235924 100644 --- a/internal/generated/rpc/keeper_grpc.pb.go +++ b/internal/generated/rpc/keeper_grpc.pb.go @@ -23,6 +23,7 @@ const ( Keeper_Register_FullMethodName = "/keeper.Keeper/Register" Keeper_Login_FullMethodName = "/keeper.Keeper/Login" Keeper_RefreshTokens_FullMethodName = "/keeper.Keeper/RefreshTokens" + Keeper_GetByGuid_FullMethodName = "/keeper.Keeper/GetByGuid" Keeper_GetAllItems_FullMethodName = "/keeper.Keeper/GetAllItems" Keeper_CreateItem_FullMethodName = "/keeper.Keeper/CreateItem" Keeper_UpdateItem_FullMethodName = "/keeper.Keeper/UpdateItem" @@ -38,9 +39,10 @@ type KeeperClient interface { Login(ctx context.Context, in *LoginIn, opts ...grpc.CallOption) (*LoginOut, error) RefreshTokens(ctx context.Context, in *RefreshTokensIn, opts ...grpc.CallOption) (*RefreshTokensOut, error) // data + GetByGuid(ctx context.Context, in *GetByGuidIn, opts ...grpc.CallOption) (*GetByGuidOut, error) GetAllItems(ctx context.Context, in *GetAllItemsIn, opts ...grpc.CallOption) (*GetAllItemsOut, error) CreateItem(ctx context.Context, in *CreateItemIn, opts ...grpc.CallOption) (*emptypb.Empty, error) - UpdateItem(ctx context.Context, in *UpdateItemIn, opts ...grpc.CallOption) (*emptypb.Empty, error) + UpdateItem(ctx context.Context, in *UpdateItemIn, opts ...grpc.CallOption) (*UpdateItemOut, error) DeleteItem(ctx context.Context, in *DeleteItemIn, opts ...grpc.CallOption) (*emptypb.Empty, error) } @@ -82,6 +84,16 @@ func (c *keeperClient) RefreshTokens(ctx context.Context, in *RefreshTokensIn, o return out, nil } +func (c *keeperClient) GetByGuid(ctx context.Context, in *GetByGuidIn, opts ...grpc.CallOption) (*GetByGuidOut, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetByGuidOut) + err := c.cc.Invoke(ctx, Keeper_GetByGuid_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *keeperClient) GetAllItems(ctx context.Context, in *GetAllItemsIn, opts ...grpc.CallOption) (*GetAllItemsOut, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetAllItemsOut) @@ -102,9 +114,9 @@ func (c *keeperClient) CreateItem(ctx context.Context, in *CreateItemIn, opts .. return out, nil } -func (c *keeperClient) UpdateItem(ctx context.Context, in *UpdateItemIn, opts ...grpc.CallOption) (*emptypb.Empty, error) { +func (c *keeperClient) UpdateItem(ctx context.Context, in *UpdateItemIn, opts ...grpc.CallOption) (*UpdateItemOut, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(emptypb.Empty) + out := new(UpdateItemOut) err := c.cc.Invoke(ctx, Keeper_UpdateItem_FullMethodName, in, out, cOpts...) if err != nil { return nil, err @@ -131,9 +143,10 @@ type KeeperServer interface { Login(context.Context, *LoginIn) (*LoginOut, error) RefreshTokens(context.Context, *RefreshTokensIn) (*RefreshTokensOut, error) // data + GetByGuid(context.Context, *GetByGuidIn) (*GetByGuidOut, error) GetAllItems(context.Context, *GetAllItemsIn) (*GetAllItemsOut, error) CreateItem(context.Context, *CreateItemIn) (*emptypb.Empty, error) - UpdateItem(context.Context, *UpdateItemIn) (*emptypb.Empty, error) + UpdateItem(context.Context, *UpdateItemIn) (*UpdateItemOut, error) DeleteItem(context.Context, *DeleteItemIn) (*emptypb.Empty, error) } @@ -153,13 +166,16 @@ func (UnimplementedKeeperServer) Login(context.Context, *LoginIn) (*LoginOut, er func (UnimplementedKeeperServer) RefreshTokens(context.Context, *RefreshTokensIn) (*RefreshTokensOut, error) { return nil, status.Errorf(codes.Unimplemented, "method RefreshTokens not implemented") } +func (UnimplementedKeeperServer) GetByGuid(context.Context, *GetByGuidIn) (*GetByGuidOut, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetByGuid not implemented") +} func (UnimplementedKeeperServer) GetAllItems(context.Context, *GetAllItemsIn) (*GetAllItemsOut, error) { return nil, status.Errorf(codes.Unimplemented, "method GetAllItems not implemented") } func (UnimplementedKeeperServer) CreateItem(context.Context, *CreateItemIn) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method CreateItem not implemented") } -func (UnimplementedKeeperServer) UpdateItem(context.Context, *UpdateItemIn) (*emptypb.Empty, error) { +func (UnimplementedKeeperServer) UpdateItem(context.Context, *UpdateItemIn) (*UpdateItemOut, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateItem not implemented") } func (UnimplementedKeeperServer) DeleteItem(context.Context, *DeleteItemIn) (*emptypb.Empty, error) { @@ -239,6 +255,24 @@ func _Keeper_RefreshTokens_Handler(srv interface{}, ctx context.Context, dec fun return interceptor(ctx, in, info, handler) } +func _Keeper_GetByGuid_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetByGuidIn) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(KeeperServer).GetByGuid(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Keeper_GetByGuid_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(KeeperServer).GetByGuid(ctx, req.(*GetByGuidIn)) + } + return interceptor(ctx, in, info, handler) +} + func _Keeper_GetAllItems_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetAllItemsIn) if err := dec(in); err != nil { @@ -330,6 +364,10 @@ var Keeper_ServiceDesc = grpc.ServiceDesc{ MethodName: "RefreshTokens", Handler: _Keeper_RefreshTokens_Handler, }, + { + MethodName: "GetByGuid", + Handler: _Keeper_GetByGuid_Handler, + }, { MethodName: "GetAllItems", Handler: _Keeper_GetAllItems_Handler, diff --git a/internal/infrastructure/rpc/client/get_by_guid.go b/internal/infrastructure/rpc/client/get_by_guid.go new file mode 100644 index 0000000..6dda229 --- /dev/null +++ b/internal/infrastructure/rpc/client/get_by_guid.go @@ -0,0 +1,47 @@ +package client + +import ( + "context" + "fmt" + "time" + + "github.com/google/uuid" + + "github.com/bjlag/go-keeper/internal/generated/rpc" +) + +type GetByGUIDIn struct { + GUID uuid.UUID +} + +type GetByGUIDOut struct { + GUID uuid.UUID + EncryptedData []byte + CreatedAt time.Time + UpdatedAt time.Time +} + +func (c RPCClient) GetByGUID(ctx context.Context, in *GetByGUIDIn) (*GetByGUIDOut, error) { + const op = "client.rpc.GetByGUID" + + rpcIn := &rpc.GetByGuidIn{ + Guid: in.GUID.String(), + } + + out, err := c.client.GetByGuid(ctx, rpcIn) + if err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + + itemGUID, err := uuid.Parse(out.GetGuid()) + if err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + + return &GetByGUIDOut{ + GUID: itemGUID, + EncryptedData: out.GetEncryptedData(), + CreatedAt: out.CreatedAt.AsTime(), + UpdatedAt: out.UpdatedAt.AsTime(), + }, nil +} diff --git a/internal/infrastructure/rpc/client/update_item.go b/internal/infrastructure/rpc/client/update_item.go index 4fddce3..9ec0622 100644 --- a/internal/infrastructure/rpc/client/update_item.go +++ b/internal/infrastructure/rpc/client/update_item.go @@ -12,20 +12,22 @@ import ( type UpdateItemIn struct { GUID uuid.UUID EncryptedData []byte + Version int64 } -func (c RPCClient) UpdateItem(ctx context.Context, in *UpdateItemIn) error { +func (c RPCClient) UpdateItem(ctx context.Context, in *UpdateItemIn) (int64, error) { const op = "client.rpc.UpdateItem" rpcIn := &rpc.UpdateItemIn{ Guid: in.GUID.String(), EncryptedData: in.EncryptedData, + Version: in.Version, } - _, err := c.client.UpdateItem(ctx, rpcIn) + rpcOut, err := c.client.UpdateItem(ctx, rpcIn) if err != nil { - return fmt.Errorf("%s: %w", op, err) + return 0, fmt.Errorf("%s: %w", op, err) } - return nil + return rpcOut.GetNewVersion(), nil } diff --git a/internal/infrastructure/rpc/server/method.go b/internal/infrastructure/rpc/server/method.go index c1c3e83..5ac97d2 100644 --- a/internal/infrastructure/rpc/server/method.go +++ b/internal/infrastructure/rpc/server/method.go @@ -14,6 +14,7 @@ const ( RegisterMethod = "Register" LoginMethod = "Login" RefreshTokensMethod = "RefreshTokens" + GetByGUIDMethod = "GetByGUID" GetAllItemsMethod = "GetAllItems" CreateItemMethod = "CreateItem" UpdateItemMethod = "UpdateItem" @@ -62,6 +63,20 @@ func (s RPCServer) RefreshTokens(ctx context.Context, in *pb.RefreshTokensIn) (* return h(ctx, in) } +func (s RPCServer) GetByGuid(ctx context.Context, in *pb.GetByGuidIn) (*pb.GetByGuidOut, error) { + handler, err := s.getHandler(GetByGUIDMethod) + if err != nil { + return nil, err + } + + h, ok := handler.(func(context.Context, *pb.GetByGuidIn) (*pb.GetByGuidOut, error)) + if !ok { + return nil, status.Errorf(codes.Internal, "handler for %s method not found", GetByGUIDMethod) + } + + return h(ctx, in) +} + func (s RPCServer) GetAllItems(ctx context.Context, in *pb.GetAllItemsIn) (*pb.GetAllItemsOut, error) { handler, err := s.getHandler(GetAllItemsMethod) if err != nil { @@ -90,13 +105,13 @@ func (s RPCServer) CreateItem(ctx context.Context, in *pb.CreateItemIn) (*emptyp return h(ctx, in) } -func (s RPCServer) UpdateItem(ctx context.Context, in *pb.UpdateItemIn) (*emptypb.Empty, error) { +func (s RPCServer) UpdateItem(ctx context.Context, in *pb.UpdateItemIn) (*pb.UpdateItemOut, error) { handler, err := s.getHandler(UpdateItemMethod) if err != nil { return nil, err } - h, ok := handler.(func(context.Context, *pb.UpdateItemIn) (*emptypb.Empty, error)) + h, ok := handler.(func(context.Context, *pb.UpdateItemIn) (*pb.UpdateItemOut, error)) if !ok { return nil, status.Errorf(codes.Internal, "handler for %s method not found", UpdateItemMethod) } diff --git a/internal/infrastructure/store/server/item/store.go b/internal/infrastructure/store/server/item/store.go index 885429a..12d6cb4 100644 --- a/internal/infrastructure/store/server/item/store.go +++ b/internal/infrastructure/store/server/item/store.go @@ -2,6 +2,7 @@ package item import ( "context" + "database/sql" "errors" "fmt" @@ -11,7 +12,10 @@ import ( model "github.com/bjlag/go-keeper/internal/domain/server/data" ) -var ErrNotAffectedRows = errors.New("not affected") +var ( + ErrNotAffectedRows = errors.New("not affected") + ErrNotFound = errors.New("not found") +) type Store struct { db *sqlx.DB @@ -43,6 +47,50 @@ func (s *Store) GetAllByUser(ctx context.Context, userGUID uuid.UUID, limit, off return convertToModels(rows), nil } +func (s *Store) ItemByGUID(ctx context.Context, guid uuid.UUID) (*model.Item, error) { + const op = "store.item.ItemByGUID" + + query := ` + SELECT guid, user_guid, encrypted_data, created_at, updated_at + FROM items + WHERE guid = $1 + ` + + var r row + if err := s.db.GetContext(ctx, &r, query, guid); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, ErrNotFound + } + return nil, fmt.Errorf("%s: %w", op, err) + } + + result := r.convertToModel() + + return &result, nil +} + +func (s *Store) UserItemByGUID(ctx context.Context, userGUID, itemGUID uuid.UUID) (*model.Item, error) { + const op = "store.item.UserItemByGUID" + + query := ` + SELECT guid, user_guid, encrypted_data, created_at, updated_at + FROM items + WHERE guid = $1 AND user_guid = $2 + ` + + var r row + if err := s.db.GetContext(ctx, &r, query, itemGUID, userGUID); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, ErrNotFound + } + return nil, fmt.Errorf("%s: %w", op, err) + } + + result := r.convertToModel() + + return &result, nil +} + func (s *Store) Create(ctx context.Context, item model.Item) error { const op = "store.item.Create" diff --git a/internal/rpc/get_all_items/handler.go b/internal/rpc/get_all_items/handler.go index b2100be..2455efd 100644 --- a/internal/rpc/get_all_items/handler.go +++ b/internal/rpc/get_all_items/handler.go @@ -69,7 +69,7 @@ func (h *Handler) Handle(ctx context.Context, in *pb.GetAllItemsIn) (*pb.GetAllI Guid: item.GUID.String(), EncryptedData: item.EncryptedData, CreatedAt: timestamppb.New(item.CreatedAt), - UpdatedAt: timestamppb.New(item.CreatedAt), + UpdatedAt: timestamppb.New(item.UpdatedAt), }) } diff --git a/internal/rpc/get_by_guid/contract.go b/internal/rpc/get_by_guid/contract.go new file mode 100644 index 0000000..7abb07a --- /dev/null +++ b/internal/rpc/get_by_guid/contract.go @@ -0,0 +1,12 @@ +package get_by_guid + +import ( + "context" + + "github.com/bjlag/go-keeper/internal/domain/server/data" + "github.com/bjlag/go-keeper/internal/usecase/server/item/get_by_guid" +) + +type usecase interface { + Do(ctx context.Context, data get_by_guid.Data) (*data.Item, error) +} diff --git a/internal/rpc/get_by_guid/handler.go b/internal/rpc/get_by_guid/handler.go new file mode 100644 index 0000000..53df500 --- /dev/null +++ b/internal/rpc/get_by_guid/handler.go @@ -0,0 +1,59 @@ +package get_by_guid + +import ( + "context" + "errors" + + "github.com/google/uuid" + "go.uber.org/zap" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/timestamppb" + + pb "github.com/bjlag/go-keeper/internal/generated/rpc" + "github.com/bjlag/go-keeper/internal/infrastructure/auth" + "github.com/bjlag/go-keeper/internal/infrastructure/logger" + "github.com/bjlag/go-keeper/internal/usecase/server/item/get_by_guid" +) + +type Handler struct { + usecase usecase +} + +func New(usecase usecase) *Handler { + return &Handler{ + usecase: usecase, + } +} + +func (h *Handler) Handle(ctx context.Context, in *pb.GetByGuidIn) (*pb.GetByGuidOut, error) { + log := logger.FromCtx(ctx) + + userGUID := auth.UserGUIDFromCtx(ctx) + if userGUID == uuid.Nil { + return nil, status.Error(codes.PermissionDenied, "permission denied") + } + + itemGUID, err := uuid.Parse(in.GetGuid()) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + result, err := h.usecase.Do(ctx, get_by_guid.Data{ + GUID: itemGUID, + UserGUID: userGUID, + }) + if err != nil { + if !errors.Is(err, get_by_guid.ErrNoData) { + log.Error("Failed to get user item by guid", zap.Error(err)) + return nil, status.Error(codes.Internal, "internal error") + } + } + + return &pb.GetByGuidOut{ + Guid: result.GUID.String(), + EncryptedData: result.EncryptedData, + CreatedAt: timestamppb.New(result.CreatedAt), + UpdatedAt: timestamppb.New(result.UpdatedAt), + }, nil +} diff --git a/internal/rpc/update_item/contract.go b/internal/rpc/update_item/contract.go index 78bb006..35dd013 100644 --- a/internal/rpc/update_item/contract.go +++ b/internal/rpc/update_item/contract.go @@ -7,5 +7,5 @@ import ( ) type usecase interface { - Do(ctx context.Context, data update.In) error + Do(ctx context.Context, data update.In) (newVersion int64, err error) } diff --git a/internal/rpc/update_item/handler.go b/internal/rpc/update_item/handler.go index 84c131d..e8709cc 100644 --- a/internal/rpc/update_item/handler.go +++ b/internal/rpc/update_item/handler.go @@ -8,7 +8,6 @@ import ( "go.uber.org/zap" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "google.golang.org/protobuf/types/known/emptypb" pb "github.com/bjlag/go-keeper/internal/generated/rpc" "github.com/bjlag/go-keeper/internal/infrastructure/auth" @@ -26,7 +25,7 @@ func New(usecase usecase) *Handler { } } -func (h *Handler) Handle(ctx context.Context, in *pb.UpdateItemIn) (*emptypb.Empty, error) { +func (h *Handler) Handle(ctx context.Context, in *pb.UpdateItemIn) (*pb.UpdateItemOut, error) { log := logger.FromCtx(ctx) userGUID := auth.UserGUIDFromCtx(ctx) @@ -43,12 +42,17 @@ func (h *Handler) Handle(ctx context.Context, in *pb.UpdateItemIn) (*emptypb.Emp return nil, status.Error(codes.InvalidArgument, "invalid item guid") } - err = h.usecase.Do(ctx, update.In{ + newVersion, err := h.usecase.Do(ctx, update.In{ UserGUID: userGUID, ItemGUID: itemGUID, EncryptedData: in.GetEncryptedData(), + Version: in.GetVersion(), }) if err != nil { + if errors.Is(err, update.ErrConflict) { + return nil, status.Error(codes.FailedPrecondition, "data outdated") + } + if errors.Is(err, update.ErrNotFoundUpdatedData) { return nil, status.Error(codes.NotFound, "item not found") } @@ -57,5 +61,7 @@ func (h *Handler) Handle(ctx context.Context, in *pb.UpdateItemIn) (*emptypb.Emp return nil, status.Error(codes.Internal, "internal error") } - return &emptypb.Empty{}, nil + return &pb.UpdateItemOut{ + NewVersion: newVersion, + }, nil } diff --git a/internal/usecase/client/item/edit/contract.go b/internal/usecase/client/item/edit/contract.go index c9d043e..0146a44 100644 --- a/internal/usecase/client/item/edit/contract.go +++ b/internal/usecase/client/item/edit/contract.go @@ -8,7 +8,7 @@ import ( ) type rpc interface { - UpdateItem(ctx context.Context, in *dto.UpdateItemIn) error + UpdateItem(ctx context.Context, in *dto.UpdateItemIn) (int64, error) } type itemStore interface { diff --git a/internal/usecase/client/item/edit/usecase.go b/internal/usecase/client/item/edit/usecase.go index 87d8c93..bdaf6af 100644 --- a/internal/usecase/client/item/edit/usecase.go +++ b/internal/usecase/client/item/edit/usecase.go @@ -3,12 +3,19 @@ package edit import ( "context" "encoding/json" + "errors" "fmt" + "time" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" model "github.com/bjlag/go-keeper/internal/domain/client" dto "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" ) +var ErrConflict = errors.New("conflict") + type Usecase struct { rpc rpc itemStore itemStore @@ -55,14 +62,22 @@ func (u *Usecase) Do(ctx context.Context, item model.Item) error { return fmt.Errorf("%s: %w", op, err) } - err = u.rpc.UpdateItem(ctx, &dto.UpdateItemIn{ + newVersion, err := u.rpc.UpdateItem(ctx, &dto.UpdateItemIn{ GUID: item.GUID, EncryptedData: encryptedData, + Version: item.UpdatedAt.UTC().UnixMicro(), }) if err != nil { + if s, ok := status.FromError(err); ok && s.Code() == codes.FailedPrecondition { + return ErrConflict + } return fmt.Errorf("%s: %w", op, err) } + sec := newVersion / 1000000 + nsec := (newVersion % 1000000) * time.Microsecond.Nanoseconds() + item.UpdatedAt = time.Unix(sec, nsec) + err = u.itemStore.SaveItem(ctx, item) if err != nil { return fmt.Errorf("%s: %w", op, err) diff --git a/internal/usecase/client/item/sync/contract.go b/internal/usecase/client/item/sync/contract.go new file mode 100644 index 0000000..aaae52c --- /dev/null +++ b/internal/usecase/client/item/sync/contract.go @@ -0,0 +1,24 @@ +package sync + +import ( + "context" + + model "github.com/bjlag/go-keeper/internal/domain/client" + rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" +) + +type client interface { + GetByGUID(ctx context.Context, in *rpc.GetByGUIDIn) (*rpc.GetByGUIDOut, error) +} + +type itemStore interface { + SaveItem(ctx context.Context, item model.Item) error +} + +type keyStore interface { + MasterKey() []byte +} + +type cipher interface { + Decrypt(data, key []byte) ([]byte, error) +} diff --git a/internal/usecase/client/item/sync/usecase.go b/internal/usecase/client/item/sync/usecase.go new file mode 100644 index 0000000..19345d0 --- /dev/null +++ b/internal/usecase/client/item/sync/usecase.go @@ -0,0 +1,95 @@ +package sync + +import ( + "context" + "encoding/json" + "errors" + "fmt" + + "github.com/google/uuid" + + model "github.com/bjlag/go-keeper/internal/domain/client" + rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" +) + +const prefixOp = "usecase.item.sync." + +var ErrUnknownCategory = errors.New("unknown category") + +type Usecase struct { + client client + itemStore itemStore + keyStore keyStore + cipher cipher +} + +func NewUsecase(client client, itemStore itemStore, keyStore keyStore, cipher cipher) *Usecase { + return &Usecase{ + client: client, + itemStore: itemStore, + keyStore: keyStore, + cipher: cipher, + } +} + +func (u *Usecase) Do(ctx context.Context, guid uuid.UUID) (*model.Item, error) { + const op = prefixOp + "Do" + + key := u.keyStore.MasterKey() + + in := &rpc.GetByGUIDIn{ + GUID: guid, + } + item, err := u.client.GetByGUID(ctx, in) + if err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + + decrypted, err := u.cipher.Decrypt(item.EncryptedData, key) + if err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + + var data model.EncryptedData + err = json.Unmarshal(decrypted, &data) + if err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + + var value interface{} + if data.Value != nil { + switch data.Category { + case model.CategoryPassword: + value = &model.Password{} + case model.CategoryText: + break + case model.CategoryFile: + value = &model.File{} + case model.CategoryBankCard: + value = &model.BankCard{} + default: + return nil, fmt.Errorf("%w: %d", ErrUnknownCategory, data.Category) + } + + err = json.Unmarshal(*data.Value, &value) + if err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + } + + itemModel := model.Item{ + GUID: item.GUID, + Category: data.Category, + Title: data.Title, + Value: value, + CreatedAt: item.CreatedAt, + UpdatedAt: item.UpdatedAt, + } + + err = u.itemStore.SaveItem(ctx, itemModel) + if err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + + return &itemModel, nil +} diff --git a/internal/usecase/server/item/get_by_guid/contract.go b/internal/usecase/server/item/get_by_guid/contract.go new file mode 100644 index 0000000..69a4d15 --- /dev/null +++ b/internal/usecase/server/item/get_by_guid/contract.go @@ -0,0 +1,13 @@ +package get_by_guid + +import ( + "context" + + "github.com/google/uuid" + + model "github.com/bjlag/go-keeper/internal/domain/server/data" +) + +type dataStore interface { + UserItemByGUID(ctx context.Context, userGUID, itemGUID uuid.UUID) (*model.Item, error) +} diff --git a/internal/usecase/server/item/get_by_guid/model.go b/internal/usecase/server/item/get_by_guid/model.go new file mode 100644 index 0000000..6da0f29 --- /dev/null +++ b/internal/usecase/server/item/get_by_guid/model.go @@ -0,0 +1,19 @@ +package get_by_guid + +import ( + "time" + + "github.com/google/uuid" +) + +type Data struct { + GUID uuid.UUID + UserGUID uuid.UUID +} + +type Result struct { + GUID uuid.UUID + EncryptedData []byte + CreatedAt time.Time + UpdatedAt time.Time +} diff --git a/internal/usecase/server/item/get_by_guid/usecase.go b/internal/usecase/server/item/get_by_guid/usecase.go new file mode 100644 index 0000000..a2417d6 --- /dev/null +++ b/internal/usecase/server/item/get_by_guid/usecase.go @@ -0,0 +1,37 @@ +package get_by_guid + +import ( + "context" + "errors" + "fmt" + + "github.com/bjlag/go-keeper/internal/domain/server/data" + storeUser "github.com/bjlag/go-keeper/internal/infrastructure/store/server/user" +) + +var ErrNoData = errors.New("no data") + +type Usecase struct { + dataStore dataStore +} + +func NewUsecase(dataStore dataStore) *Usecase { + return &Usecase{ + dataStore: dataStore, + } +} + +func (u Usecase) Do(ctx context.Context, data Data) (*data.Item, error) { + const op = "usecase.item.getByGuid.Do" + + model, err := u.dataStore.UserItemByGUID(ctx, data.UserGUID, data.GUID) + if err != nil { + if errors.Is(err, storeUser.ErrNotFound) { + return nil, ErrNoData + } + + return nil, fmt.Errorf("%s: %w", op, err) + } + + return model, nil +} diff --git a/internal/usecase/server/item/update/contract.go b/internal/usecase/server/item/update/contract.go index eeb6092..e1bdadf 100644 --- a/internal/usecase/server/item/update/contract.go +++ b/internal/usecase/server/item/update/contract.go @@ -9,5 +9,6 @@ import ( ) type store interface { + ItemByGUID(ctx context.Context, guid uuid.UUID) (*model.Item, error) Update(ctx context.Context, guid uuid.UUID, userGUID uuid.UUID, updatedData model.UpdatedItem) error } diff --git a/internal/usecase/server/item/update/model.go b/internal/usecase/server/item/update/model.go index 74411dd..db7b92c 100644 --- a/internal/usecase/server/item/update/model.go +++ b/internal/usecase/server/item/update/model.go @@ -8,4 +8,5 @@ type In struct { UserGUID uuid.UUID ItemGUID uuid.UUID EncryptedData []byte + Version int64 } diff --git a/internal/usecase/server/item/update/usecase.go b/internal/usecase/server/item/update/usecase.go index dfcd552..f296546 100644 --- a/internal/usecase/server/item/update/usecase.go +++ b/internal/usecase/server/item/update/usecase.go @@ -10,7 +10,10 @@ import ( "github.com/bjlag/go-keeper/internal/infrastructure/store/server/item" ) -var ErrNotFoundUpdatedData = errors.New("updated data is not found") +var ( + ErrNotFoundUpdatedData = errors.New("updated data is not found") + ErrConflict = errors.New("conflict") +) type Usecase struct { store store @@ -22,21 +25,33 @@ func NewUsecase(store store) *Usecase { } } -func (u Usecase) Do(ctx context.Context, in In) error { +func (u Usecase) Do(ctx context.Context, in In) (newVersion int64, err error) { const op = "usecase.item.update.Do" + currentItem, err := u.store.ItemByGUID(ctx, in.ItemGUID) + if err != nil { + if errors.Is(err, item.ErrNotFound) { + return 0, ErrNotFoundUpdatedData + } + } + + if in.Version != currentItem.UpdatedAt.UTC().UnixMicro() { + return 0, ErrConflict + } + + updatedAt := time.Now() data := model.UpdatedItem{ EncryptedData: in.EncryptedData, - UpdatedAt: time.Now(), + UpdatedAt: updatedAt, } - err := u.store.Update(ctx, in.ItemGUID, in.UserGUID, data) + err = u.store.Update(ctx, in.ItemGUID, in.UserGUID, data) if err != nil { if errors.Is(err, item.ErrNotAffectedRows) { - return ErrNotFoundUpdatedData + return 0, ErrNotFoundUpdatedData } - return fmt.Errorf("%s: %w", op, err) + return 0, fmt.Errorf("%s: %w", op, err) } - return nil + return updatedAt.UnixMicro(), nil } diff --git a/proto/keeper.proto b/proto/keeper.proto index f3e03f9..e48adcf 100644 --- a/proto/keeper.proto +++ b/proto/keeper.proto @@ -14,9 +14,10 @@ service Keeper { rpc RefreshTokens(RefreshTokensIn) returns (RefreshTokensOut); // data + rpc GetByGuid(GetByGuidIn) returns (GetByGuidOut); rpc GetAllItems(GetAllItemsIn) returns (GetAllItemsOut); rpc CreateItem(CreateItemIn) returns (google.protobuf.Empty); - rpc UpdateItem(UpdateItemIn) returns (google.protobuf.Empty); + rpc UpdateItem(UpdateItemIn) returns (UpdateItemOut); rpc DeleteItem(DeleteItemIn) returns (google.protobuf.Empty); } @@ -51,6 +52,17 @@ message RefreshTokensOut { } // data +message GetByGuidIn { + string guid = 1; +} + +message GetByGuidOut { + string guid = 1; + bytes encryptedData = 2; + google.protobuf.Timestamp CreatedAt = 3; + google.protobuf.Timestamp UpdatedAt = 4; +} + message GetAllItemsIn { uint32 limit = 1; uint32 offset = 2; @@ -76,6 +88,11 @@ message CreateItemIn { message UpdateItemIn { string guid = 1; bytes encryptedData = 2; + int64 version = 3; +} + +message UpdateItemOut { + int64 newVersion = 1; } message DeleteItemIn { From 1fcd2cfc6e3db1905f90ab74e0ebcba64bd7cf78 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Fri, 14 Mar 2025 18:22:38 +0300 Subject: [PATCH 62/94] client: sync text --- internal/app/client/app.go | 2 +- internal/cli/model/item/password/model.go | 12 +++++------- internal/cli/model/item/sync/model.go | 9 +++++++++ internal/cli/model/item/text/action.go | 3 ++- internal/cli/model/item/text/model.go | 20 ++++++++++++++++++-- internal/usecase/client/item/sync/usecase.go | 1 + 6 files changed, 36 insertions(+), 11 deletions(-) diff --git a/internal/app/client/app.go b/internal/app/client/app.go index 627a647..559e152 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -101,7 +101,7 @@ func (a *App) Run(ctx context.Context) error { master.WithCreatForm(formCreate.InitModel()), master.WithListForm(list.InitModel(ucSync, fetchItem)), master.WithPasswordItemForm(password.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem, formSync)), - master.WithTextItemForm(text.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem)), + master.WithTextItemForm(text.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem, formSync)), master.WithBankCardItemForm(bank_card.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem)), master.WithFileItemForm(file.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem)), ) diff --git a/internal/cli/model/item/password/model.go b/internal/cli/model/item/password/model.go index 4e47d00..e78357b 100644 --- a/internal/cli/model/item/password/model.go +++ b/internal/cli/model/item/password/model.go @@ -2,16 +2,13 @@ package password import ( "errors" - sync2 "github.com/bjlag/go-keeper/internal/cli/model/item/sync" - "strings" - "time" - "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/textarea" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/google/uuid" + "strings" "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/element/button" @@ -19,6 +16,7 @@ import ( tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" message "github.com/bjlag/go-keeper/internal/cli/message/item/password" "github.com/bjlag/go-keeper/internal/cli/message/item/sync" + modelSync "github.com/bjlag/go-keeper/internal/cli/model/item/sync" "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/domain/client" "github.com/bjlag/go-keeper/internal/usecase/client/item/create" @@ -73,14 +71,14 @@ type Model struct { item *client.Item category client.Category - formSync *sync2.Model + formSync *modelSync.Model usecaseCreate *create.Usecase usecaseEdit *edit.Usecase usecaseDelete *remove.Usecase } -func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *remove.Usecase, formSync *sync2.Model) *Model { +func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *remove.Usecase, formSync *modelSync.Model) *Model { return &Model{ help: help.New(), header: "Пароль", @@ -178,7 +176,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.elements[i] = style.SetFocusStyle(e) continue } - time.Now().Unix() + e.Blur() m.elements[i] = style.SetNoStyle(e) case textarea.Model: diff --git a/internal/cli/model/item/sync/model.go b/internal/cli/model/item/sync/model.go index 888eb01..795bb55 100644 --- a/internal/cli/model/item/sync/model.go +++ b/internal/cli/model/item/sync/model.go @@ -12,6 +12,7 @@ import ( "github.com/bjlag/go-keeper/internal/cli/element/button" "github.com/bjlag/go-keeper/internal/cli/message/item/password" "github.com/bjlag/go-keeper/internal/cli/message/item/sync" + "github.com/bjlag/go-keeper/internal/cli/message/item/text" "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/domain/client" itemSync "github.com/bjlag/go-keeper/internal/usecase/client/item/sync" @@ -124,6 +125,10 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m.prevModel.Update(password.OpenMsg{ Item: &m.item, }) + case client.CategoryText: + return m.prevModel.Update(text.OpenMsg{ + Item: &m.item, + }) } case posCancelBtn: switch m.item.Category { @@ -131,6 +136,10 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m.prevModel.Update(password.OpenMsg{ Item: &m.item, }) + case client.CategoryText: + return m.prevModel.Update(text.OpenMsg{ + Item: &m.item, + }) } } diff --git a/internal/cli/model/item/text/action.go b/internal/cli/model/item/text/action.go index 319b209..e79b6d2 100644 --- a/internal/cli/model/item/text/action.go +++ b/internal/cli/model/item/text/action.go @@ -21,8 +21,9 @@ func (m *Model) editAction() error { element.GetValue(m.elements, posEditTitle), element.GetValue(m.elements, posEditNotes), ) - item.GUID = m.guid + item.CreatedAt = m.item.CreatedAt + item.UpdatedAt = m.item.UpdatedAt return m.usecaseEdit.Do(context.TODO(), item) } diff --git a/internal/cli/model/item/text/model.go b/internal/cli/model/item/text/model.go index 4d00379..c65f71b 100644 --- a/internal/cli/model/item/text/model.go +++ b/internal/cli/model/item/text/model.go @@ -2,7 +2,9 @@ package text import ( "errors" + "github.com/bjlag/go-keeper/internal/cli/message/item/sync" text2 "github.com/bjlag/go-keeper/internal/cli/message/item/text" + modelSync "github.com/bjlag/go-keeper/internal/cli/model/item/sync" "strings" "github.com/charmbracelet/bubbles/help" @@ -60,19 +62,24 @@ type Model struct { backState int guid uuid.UUID + item *client.Item category client.Category + formSync *modelSync.Model + usecaseCreate *create.Usecase usecaseEdit *edit.Usecase usecaseDelete *remove.Usecase } -func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *remove.Usecase) *Model { +func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *remove.Usecase, formSync *modelSync.Model) *Model { return &Model{ help: help.New(), header: "Текст", state: stateCreate, + formSync: formSync, + usecaseCreate: usecaseCreate, usecaseEdit: usecaseSave, usecaseDelete: usecaseDelete, @@ -103,6 +110,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if msg.Item != nil { m.state = stateEdit m.header = msg.Item.Title + m.item = msg.Item m.guid = msg.Item.GUID m.category = msg.Item.Category @@ -195,7 +203,15 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch m.pos { case posEditEditBtn: - m.err = m.editAction() + err := m.editAction() + if err != nil && errors.Is(err, edit.ErrConflict) { + return m.formSync.Update(sync.OpenMsg{ + BackModel: m, + Item: m.item, + }) + } + + m.err = err return m, nil case posEditDeleteBtn: m.err = m.deleteAction() diff --git a/internal/usecase/client/item/sync/usecase.go b/internal/usecase/client/item/sync/usecase.go index 19345d0..af3ad8d 100644 --- a/internal/usecase/client/item/sync/usecase.go +++ b/internal/usecase/client/item/sync/usecase.go @@ -82,6 +82,7 @@ func (u *Usecase) Do(ctx context.Context, guid uuid.UUID) (*model.Item, error) { Category: data.Category, Title: data.Title, Value: value, + Notes: data.Notes, CreatedAt: item.CreatedAt, UpdatedAt: item.UpdatedAt, } From 544a9b4391ec7df13f8be07c2a799373fd79ce97 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Fri, 14 Mar 2025 18:38:09 +0300 Subject: [PATCH 63/94] client: sync file --- internal/app/client/app.go | 2 +- internal/cli/model/item/file/action.go | 2 ++ internal/cli/model/item/file/model.go | 24 ++++++++++++++++++----- internal/cli/model/item/password/model.go | 3 ++- internal/cli/model/item/sync/model.go | 22 +++++++++++++++++++-- 5 files changed, 44 insertions(+), 9 deletions(-) diff --git a/internal/app/client/app.go b/internal/app/client/app.go index 559e152..505622a 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -103,7 +103,7 @@ func (a *App) Run(ctx context.Context) error { master.WithPasswordItemForm(password.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem, formSync)), master.WithTextItemForm(text.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem, formSync)), master.WithBankCardItemForm(bank_card.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem)), - master.WithFileItemForm(file.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem)), + master.WithFileItemForm(file.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem, formSync)), ) f, err := tea.LogToFile("debug.log", "debug") diff --git a/internal/cli/model/item/file/action.go b/internal/cli/model/item/file/action.go index 293fac0..9c15581 100644 --- a/internal/cli/model/item/file/action.go +++ b/internal/cli/model/item/file/action.go @@ -46,6 +46,8 @@ func (m *Model) editAction() error { element.GetValue(m.elements, posEditNotes), ) item.GUID = m.guid + item.CreatedAt = m.item.CreatedAt + item.UpdatedAt = m.item.UpdatedAt return m.usecaseEdit.Do(context.TODO(), item) } diff --git a/internal/cli/model/item/file/model.go b/internal/cli/model/item/file/model.go index b1c095e..e7d29c5 100644 --- a/internal/cli/model/item/file/model.go +++ b/internal/cli/model/item/file/model.go @@ -2,7 +2,6 @@ package file import ( "errors" - file2 "github.com/bjlag/go-keeper/internal/cli/message/item/file" "os" "strings" "time" @@ -19,6 +18,9 @@ import ( "github.com/bjlag/go-keeper/internal/cli/element/button" tarea "github.com/bjlag/go-keeper/internal/cli/element/textarea" tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" + "github.com/bjlag/go-keeper/internal/cli/message/item/file" + "github.com/bjlag/go-keeper/internal/cli/message/item/sync" + modelSync "github.com/bjlag/go-keeper/internal/cli/model/item/sync" "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/domain/client" "github.com/bjlag/go-keeper/internal/usecase/client/item/create" @@ -83,12 +85,14 @@ type Model struct { guid uuid.UUID category client.Category + formSync *modelSync.Model + usecaseCreate *create.Usecase usecaseEdit *edit.Usecase usecaseDelete *remove.Usecase } -func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *remove.Usecase) *Model { +func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *remove.Usecase, formSync *modelSync.Model) *Model { fp := filepicker.New() fp.AllowedTypes = []string{".txt", ".md", ".jpg", ".jpeg", ".png"} fp.CurrentDirectory, _ = os.UserHomeDir() @@ -100,6 +104,8 @@ func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecase state: stateCreate, filepicker: fp, + formSync: formSync, + usecaseCreate: usecaseCreate, usecaseEdit: usecaseSave, usecaseDelete: usecaseDelete, @@ -125,7 +131,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } return m, nil - case file2.OpenMsg: + case file.OpenMsg: m.backState = msg.BackState m.backModel = msg.BackModel @@ -218,7 +224,15 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch m.pos { case posEditEditBtn: - m.err = m.editAction() + err := m.editAction() + if err != nil && errors.Is(err, edit.ErrConflict) { + return m.formSync.Update(sync.OpenMsg{ + BackModel: m, + Item: m.item, + }) + } + + m.err = err return m, nil case posEditDeleteBtn: m.err = m.deleteAction() @@ -235,7 +249,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, common.Keys.Back): if m.selectFileMode { m.selectFileMode = false - return m.Update(file2.OpenMsg{ + return m.Update(file.OpenMsg{ BackModel: m.backModel, BackState: m.backState, Item: m.item, diff --git a/internal/cli/model/item/password/model.go b/internal/cli/model/item/password/model.go index e78357b..b4b7d1c 100644 --- a/internal/cli/model/item/password/model.go +++ b/internal/cli/model/item/password/model.go @@ -2,13 +2,14 @@ package password import ( "errors" + "strings" + "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/textarea" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/google/uuid" - "strings" "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/element/button" diff --git a/internal/cli/model/item/sync/model.go b/internal/cli/model/item/sync/model.go index 795bb55..0fb2f38 100644 --- a/internal/cli/model/item/sync/model.go +++ b/internal/cli/model/item/sync/model.go @@ -10,6 +10,8 @@ import ( "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/element/button" + "github.com/bjlag/go-keeper/internal/cli/message/item/bank_card" + "github.com/bjlag/go-keeper/internal/cli/message/item/file" "github.com/bjlag/go-keeper/internal/cli/message/item/password" "github.com/bjlag/go-keeper/internal/cli/message/item/sync" "github.com/bjlag/go-keeper/internal/cli/message/item/text" @@ -129,6 +131,14 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m.prevModel.Update(text.OpenMsg{ Item: &m.item, }) + case client.CategoryFile: + return m.prevModel.Update(file.OpenMsg{ + Item: &m.item, + }) + case client.CategoryBankCard: + return m.prevModel.Update(bank_card.OpenMsg{ + Item: &m.item, + }) } case posCancelBtn: switch m.item.Category { @@ -140,6 +150,14 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m.prevModel.Update(text.OpenMsg{ Item: &m.item, }) + case client.CategoryFile: + return m.prevModel.Update(file.OpenMsg{ + Item: &m.item, + }) + case client.CategoryBankCard: + return m.prevModel.Update(bank_card.OpenMsg{ + Item: &m.item, + }) } } @@ -184,13 +202,13 @@ func (m *Model) View() string { b.WriteString(v.Password) b.WriteRune('\n') b.WriteRune('\n') - case client.File: + case *client.File: b.WriteString("Название файла") b.WriteRune('\n') b.WriteString(v.Name) b.WriteRune('\n') b.WriteRune('\n') - case client.BankCard: + case *client.BankCard: b.WriteString("Номер карты") b.WriteRune('\n') b.WriteString(v.Number) From d964b2f8e319d9885a0c0ecb9ed75596317b43c8 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Fri, 14 Mar 2025 18:43:38 +0300 Subject: [PATCH 64/94] client: sync bank card --- internal/app/client/app.go | 2 +- internal/cli/model/item/bank_card/action.go | 2 ++ internal/cli/model/item/bank_card/model.go | 20 ++++++++++++++++++-- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/internal/app/client/app.go b/internal/app/client/app.go index 505622a..49d0d79 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -102,7 +102,7 @@ func (a *App) Run(ctx context.Context) error { master.WithListForm(list.InitModel(ucSync, fetchItem)), master.WithPasswordItemForm(password.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem, formSync)), master.WithTextItemForm(text.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem, formSync)), - master.WithBankCardItemForm(bank_card.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem)), + master.WithBankCardItemForm(bank_card.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem, formSync)), master.WithFileItemForm(file.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem, formSync)), ) diff --git a/internal/cli/model/item/bank_card/action.go b/internal/cli/model/item/bank_card/action.go index c1a774d..7c6c7f5 100644 --- a/internal/cli/model/item/bank_card/action.go +++ b/internal/cli/model/item/bank_card/action.go @@ -28,6 +28,8 @@ func (m *Model) editAction() error { element.GetValue(m.elements, posEditNotes), ) item.GUID = m.guid + item.CreatedAt = m.item.CreatedAt + item.UpdatedAt = m.item.UpdatedAt return m.usecaseEdit.Do(context.TODO(), item) } diff --git a/internal/cli/model/item/bank_card/model.go b/internal/cli/model/item/bank_card/model.go index 90d4263..1dc122d 100644 --- a/internal/cli/model/item/bank_card/model.go +++ b/internal/cli/model/item/bank_card/model.go @@ -16,6 +16,8 @@ import ( tarea "github.com/bjlag/go-keeper/internal/cli/element/textarea" tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" "github.com/bjlag/go-keeper/internal/cli/message/item/bank_card" + "github.com/bjlag/go-keeper/internal/cli/message/item/sync" + modelSync "github.com/bjlag/go-keeper/internal/cli/model/item/sync" "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/domain/client" "github.com/bjlag/go-keeper/internal/usecase/client/item/create" @@ -69,19 +71,24 @@ type Model struct { backState int guid uuid.UUID + item *client.Item category client.Category + formSync *modelSync.Model + usecaseCreate *create.Usecase usecaseEdit *edit.Usecase usecaseDelete *remove.Usecase } -func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *remove.Usecase) *Model { +func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *remove.Usecase, formSync *modelSync.Model) *Model { return &Model{ help: help.New(), header: "Банковская карта", state: stateCreate, + formSync: formSync, + usecaseCreate: usecaseCreate, usecaseEdit: usecaseSave, usecaseDelete: usecaseDelete, @@ -112,6 +119,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if msg.Item != nil { m.state = stateEdit m.header = msg.Item.Title + m.item = msg.Item m.guid = msg.Item.GUID m.category = msg.Item.Category @@ -218,7 +226,15 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch m.pos { case posEditEditBtn: - m.err = m.editAction() + err := m.editAction() + if err != nil && errors.Is(err, edit.ErrConflict) { + return m.formSync.Update(sync.OpenMsg{ + BackModel: m, + Item: m.item, + }) + } + + m.err = err return m, nil case posEditDeleteBtn: m.err = m.deleteAction() From 0cc902e7583fceec2bc83285dcc470674f0b9d8a Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Fri, 14 Mar 2025 19:00:52 +0300 Subject: [PATCH 65/94] client: refactoring --- .golangci.yml | 1 + internal/app/client/app.go | 22 ++--- internal/cli/common/message.go | 18 ---- internal/cli/element/list/list.go | 5 +- .../cli/message/item/bank_card/message.go | 13 --- internal/cli/message/item/create/message.go | 3 - internal/cli/message/item/file/message.go | 13 --- internal/cli/message/item/password/message.go | 13 --- internal/cli/message/item/sync/message.go | 13 --- internal/cli/message/item/text/message.go | 13 --- internal/cli/message/list/message.go | 13 --- internal/cli/message/login/message.go | 5 - internal/cli/message/message.go | 43 +++++++++ internal/cli/model/{item => }/create/model.go | 49 ++++++++-- internal/cli/model/item/bank_card/model.go | 28 +++--- internal/cli/model/item/file/model.go | 35 +++---- internal/cli/model/item/password/model.go | 28 +++--- internal/cli/model/item/sync/model.go | 57 ++---------- internal/cli/model/item/text/model.go | 28 +++--- internal/cli/model/list/model.go | 80 +++++++++++----- internal/cli/model/login/model.go | 19 ++-- internal/cli/model/master/message.go | 3 - internal/cli/model/master/model.go | 91 ++++--------------- internal/cli/model/master/option.go | 42 +-------- internal/cli/model/register/message.go | 12 --- internal/cli/model/register/model.go | 18 ++-- .../infrastructure/rpc/client/get_by_guid.go | 4 +- internal/infrastructure/rpc/server/method.go | 2 +- 28 files changed, 265 insertions(+), 406 deletions(-) delete mode 100644 internal/cli/common/message.go delete mode 100644 internal/cli/message/item/bank_card/message.go delete mode 100644 internal/cli/message/item/create/message.go delete mode 100644 internal/cli/message/item/file/message.go delete mode 100644 internal/cli/message/item/password/message.go delete mode 100644 internal/cli/message/item/sync/message.go delete mode 100644 internal/cli/message/item/text/message.go delete mode 100644 internal/cli/message/list/message.go delete mode 100644 internal/cli/message/login/message.go create mode 100644 internal/cli/message/message.go rename internal/cli/model/{item => }/create/model.go (67%) delete mode 100644 internal/cli/model/master/message.go delete mode 100644 internal/cli/model/register/message.go diff --git a/.golangci.yml b/.golangci.yml index deba70e..48ecc83 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -25,6 +25,7 @@ linters: - nilnil - mnd - godox + - gocyclo linters-settings: revive: diff --git a/internal/app/client/app.go b/internal/app/client/app.go index 49d0d79..bc5aca4 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -3,13 +3,12 @@ package client import ( "context" "fmt" - itemSync "github.com/bjlag/go-keeper/internal/usecase/client/item/sync" tea "github.com/charmbracelet/bubbletea" "go.uber.org/zap" + formCreate "github.com/bjlag/go-keeper/internal/cli/model/create" "github.com/bjlag/go-keeper/internal/cli/model/item/bank_card" - formCreate "github.com/bjlag/go-keeper/internal/cli/model/item/create" "github.com/bjlag/go-keeper/internal/cli/model/item/file" "github.com/bjlag/go-keeper/internal/cli/model/item/password" syncItem "github.com/bjlag/go-keeper/internal/cli/model/item/sync" @@ -29,6 +28,7 @@ import ( "github.com/bjlag/go-keeper/internal/usecase/client/item/create" "github.com/bjlag/go-keeper/internal/usecase/client/item/edit" "github.com/bjlag/go-keeper/internal/usecase/client/item/remove" + itemSync "github.com/bjlag/go-keeper/internal/usecase/client/item/sync" "github.com/bjlag/go-keeper/internal/usecase/client/login" mkey "github.com/bjlag/go-keeper/internal/usecase/client/master_key" "github.com/bjlag/go-keeper/internal/usecase/client/register" @@ -93,17 +93,17 @@ func (a *App) Run(ctx context.Context) error { fetchItem := item.NewFetcher(storeItem) - formSync := syncItem.InitModel(ucItemSync) + frmRegister := formRegister.InitModel(ucRegister, ucMasterKey) + frmSync := syncItem.InitModel(ucItemSync) + frmPasswordItem := password.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem, frmSync) + frmTextItem := text.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem, frmSync) + frmBankCardItem := bank_card.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem, frmSync) + frmFileItem := file.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem, frmSync) m := master.InitModel( - master.WithLoginForm(formLogin.InitModel(ucLogin, ucMasterKey)), - master.WithRegisterForm(formRegister.InitModel(ucRegister, ucMasterKey)), - master.WithCreatForm(formCreate.InitModel()), - master.WithListForm(list.InitModel(ucSync, fetchItem)), - master.WithPasswordItemForm(password.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem, formSync)), - master.WithTextItemForm(text.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem, formSync)), - master.WithBankCardItemForm(bank_card.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem, formSync)), - master.WithFileItemForm(file.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem, formSync)), + master.WithLoginForm(formLogin.InitModel(ucLogin, ucMasterKey, frmRegister)), + master.WithCreatForm(formCreate.InitModel(frmPasswordItem, frmTextItem, frmBankCardItem, frmFileItem)), + master.WithListForm(list.InitModel(ucSync, fetchItem, frmPasswordItem, frmTextItem, frmBankCardItem, frmFileItem)), ) f, err := tea.LogToFile("debug.log", "debug") diff --git a/internal/cli/common/message.go b/internal/cli/common/message.go deleted file mode 100644 index aa6f139..0000000 --- a/internal/cli/common/message.go +++ /dev/null @@ -1,18 +0,0 @@ -package common - -import ( - tea "github.com/charmbracelet/bubbletea" - - "github.com/bjlag/go-keeper/internal/domain/client" -) - -type BackMsg struct { - State int -} - -type OpenItemMessage struct { - BackModel tea.Model - BackState int - Item *client.Item - Category client.Category -} diff --git a/internal/cli/element/list/list.go b/internal/cli/element/list/list.go index ade7d68..7bf3aa0 100644 --- a/internal/cli/element/list/list.go +++ b/internal/cli/element/list/list.go @@ -5,10 +5,11 @@ import ( "io" "strings" - "github.com/bjlag/go-keeper/internal/cli/style" - "github.com/bjlag/go-keeper/internal/domain/client" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" + + "github.com/bjlag/go-keeper/internal/cli/style" + "github.com/bjlag/go-keeper/internal/domain/client" ) func CreateDefaultList(title string, with, height int, itemDelegate list.ItemDelegate, items ...list.Item) list.Model { diff --git a/internal/cli/message/item/bank_card/message.go b/internal/cli/message/item/bank_card/message.go deleted file mode 100644 index 97c056e..0000000 --- a/internal/cli/message/item/bank_card/message.go +++ /dev/null @@ -1,13 +0,0 @@ -package bank_card - -import ( - tea "github.com/charmbracelet/bubbletea" - - "github.com/bjlag/go-keeper/internal/domain/client" -) - -type OpenMsg struct { - BackModel tea.Model - BackState int - Item *client.Item -} diff --git a/internal/cli/message/item/create/message.go b/internal/cli/message/item/create/message.go deleted file mode 100644 index e938750..0000000 --- a/internal/cli/message/item/create/message.go +++ /dev/null @@ -1,3 +0,0 @@ -package create - -type Open struct{} diff --git a/internal/cli/message/item/file/message.go b/internal/cli/message/item/file/message.go deleted file mode 100644 index d524924..0000000 --- a/internal/cli/message/item/file/message.go +++ /dev/null @@ -1,13 +0,0 @@ -package file - -import ( - tea "github.com/charmbracelet/bubbletea" - - "github.com/bjlag/go-keeper/internal/domain/client" -) - -type OpenMsg struct { - BackModel tea.Model - BackState int - Item *client.Item -} diff --git a/internal/cli/message/item/password/message.go b/internal/cli/message/item/password/message.go deleted file mode 100644 index 7cfd662..0000000 --- a/internal/cli/message/item/password/message.go +++ /dev/null @@ -1,13 +0,0 @@ -package password - -import ( - tea "github.com/charmbracelet/bubbletea" - - "github.com/bjlag/go-keeper/internal/domain/client" -) - -type OpenMsg struct { - BackModel tea.Model - BackState int - Item *client.Item -} diff --git a/internal/cli/message/item/sync/message.go b/internal/cli/message/item/sync/message.go deleted file mode 100644 index 82cc128..0000000 --- a/internal/cli/message/item/sync/message.go +++ /dev/null @@ -1,13 +0,0 @@ -package sync - -import ( - tea "github.com/charmbracelet/bubbletea" - - "github.com/bjlag/go-keeper/internal/domain/client" -) - -type OpenMsg struct { - BackModel tea.Model - BackState int - Item *client.Item -} diff --git a/internal/cli/message/item/text/message.go b/internal/cli/message/item/text/message.go deleted file mode 100644 index 9b29052..0000000 --- a/internal/cli/message/item/text/message.go +++ /dev/null @@ -1,13 +0,0 @@ -package text - -import ( - tea "github.com/charmbracelet/bubbletea" - - "github.com/bjlag/go-keeper/internal/domain/client" -) - -type OpenMsg struct { - BackModel tea.Model - BackState int - Item *client.Item -} diff --git a/internal/cli/message/list/message.go b/internal/cli/message/list/message.go deleted file mode 100644 index edb33a9..0000000 --- a/internal/cli/message/list/message.go +++ /dev/null @@ -1,13 +0,0 @@ -package list - -import ( - "github.com/bjlag/go-keeper/internal/domain/client" -) - -type GetDataMsg struct{} - -type OpenCategoriesMsg struct{} - -type OpenItemsMsg struct { - Category client.Category -} diff --git a/internal/cli/message/login/message.go b/internal/cli/message/login/message.go deleted file mode 100644 index 5555650..0000000 --- a/internal/cli/message/login/message.go +++ /dev/null @@ -1,5 +0,0 @@ -package login - -type OpenMsg struct{} - -type SuccessMsg struct{} diff --git a/internal/cli/message/message.go b/internal/cli/message/message.go new file mode 100644 index 0000000..4ada97d --- /dev/null +++ b/internal/cli/message/message.go @@ -0,0 +1,43 @@ +package message + +import ( + tea "github.com/charmbracelet/bubbletea" + + "github.com/bjlag/go-keeper/internal/domain/client" +) + +type OpenLoginMsg struct{} + +type OpenRegisterMsg struct { + LoginModel tea.Model +} + +type SuccessMsg struct{} + +// List + +type OpenCategoriesMsg struct{} + +type OpenItemsMsg struct { + Category client.Category +} + +type BackMsg struct { + State int + Item *client.Item +} + +// Item + +type OpenItemMsg struct { + BackModel tea.Model + BackState int + Item *client.Item +} + +type OpenItemMessage struct { + BackModel tea.Model + BackState int + Item *client.Item + Category client.Category +} diff --git a/internal/cli/model/item/create/model.go b/internal/cli/model/create/model.go similarity index 67% rename from internal/cli/model/item/create/model.go rename to internal/cli/model/create/model.go index bc8fc33..e87f4f4 100644 --- a/internal/cli/model/item/create/model.go +++ b/internal/cli/model/create/model.go @@ -1,7 +1,7 @@ package create import ( - create2 "github.com/bjlag/go-keeper/internal/cli/message/item/create" + "errors" "strings" "github.com/charmbracelet/bubbles/help" @@ -11,10 +11,13 @@ import ( "github.com/bjlag/go-keeper/internal/cli/common" elist "github.com/bjlag/go-keeper/internal/cli/element/list" + "github.com/bjlag/go-keeper/internal/cli/message" "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/domain/client" ) +var errUnsupportedCategory = errors.New("unsupported category") + const ( defaultWidth = 40 listHeight = 14 @@ -26,10 +29,20 @@ type Model struct { header string categories list.Model err error + + formPassword tea.Model + formText tea.Model + formBankCard tea.Model + formFile tea.Model } -func InitModel() *Model { - f := &Model{ +func InitModel( + formPassword tea.Model, + formText tea.Model, + formBankCard tea.Model, + formFile tea.Model, +) *Model { + return &Model{ help: help.New(), header: "Создание", categories: elist.CreateDefaultList("Выберите категорию:", defaultWidth, listHeight, elist.CategoryDelegate{}, @@ -38,9 +51,12 @@ func InitModel() *Model { elist.Category{Category: client.CategoryFile, Title: client.CategoryFile.String()}, elist.Category{Category: client.CategoryBankCard, Title: client.CategoryBankCard.String()}, ), - } - return f + formPassword: formPassword, + formText: formText, + formBankCard: formBankCard, + formFile: formFile, + } } func (f *Model) SetMainModel(m tea.Model) { @@ -53,21 +69,34 @@ func (f *Model) Init() tea.Cmd { func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { - case create2.Open: + case message.OpenItemMsg: return f, nil case tea.KeyMsg: switch { case key.Matches(msg, common.Keys.Enter): if c, ok := f.categories.SelectedItem().(elist.Category); ok { - return f.main.Update(common.OpenItemMessage{ + itemMsg := message.OpenItemMsg{ BackModel: f, - Category: c.Category, - }) + } + + switch c.Category { + case client.CategoryPassword: + return f.formPassword.Update(itemMsg) + case client.CategoryText: + return f.formText.Update(itemMsg) + case client.CategoryFile: + return f.formFile.Update(itemMsg) + case client.CategoryBankCard: + return f.formBankCard.Update(itemMsg) + default: + f.err = errUnsupportedCategory + return f, nil + } } return f, nil case key.Matches(msg, common.Keys.Back): - return f.main.Update(common.BackMsg{}) + return f.main.Update(message.BackMsg{}) } } diff --git a/internal/cli/model/item/bank_card/model.go b/internal/cli/model/item/bank_card/model.go index 1dc122d..d05926d 100644 --- a/internal/cli/model/item/bank_card/model.go +++ b/internal/cli/model/item/bank_card/model.go @@ -15,9 +15,7 @@ import ( "github.com/bjlag/go-keeper/internal/cli/element/button" tarea "github.com/bjlag/go-keeper/internal/cli/element/textarea" tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" - "github.com/bjlag/go-keeper/internal/cli/message/item/bank_card" - "github.com/bjlag/go-keeper/internal/cli/message/item/sync" - modelSync "github.com/bjlag/go-keeper/internal/cli/model/item/sync" + "github.com/bjlag/go-keeper/internal/cli/message" "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/domain/client" "github.com/bjlag/go-keeper/internal/usecase/client/item/create" @@ -59,7 +57,6 @@ var ( ) type Model struct { - main tea.Model help help.Model header string state state @@ -74,14 +71,14 @@ type Model struct { item *client.Item category client.Category - formSync *modelSync.Model + formSync tea.Model usecaseCreate *create.Usecase usecaseEdit *edit.Usecase usecaseDelete *remove.Usecase } -func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *remove.Usecase, formSync *modelSync.Model) *Model { +func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *remove.Usecase, formSync tea.Model) *Model { return &Model{ help: help.New(), header: "Банковская карта", @@ -95,10 +92,6 @@ func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecase } } -func (m *Model) SetMainModel(model tea.Model) { - m.main = model -} - func (m *Model) Init() tea.Cmd { return nil } @@ -112,7 +105,12 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } return m, nil - case bank_card.OpenMsg: + case message.BackMsg: + if msg.Item != nil { + m.item = msg.Item + } + return m, nil + case message.OpenItemMsg: m.backState = msg.BackState m.backModel = msg.BackModel @@ -214,7 +212,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.err = m.createAction() return m, nil case posCreateBackBtn: - return m.backModel.Update(common.BackMsg{ + return m.backModel.Update(message.BackMsg{ State: m.backState, }) default: @@ -228,7 +226,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case posEditEditBtn: err := m.editAction() if err != nil && errors.Is(err, edit.ErrConflict) { - return m.formSync.Update(sync.OpenMsg{ + return m.formSync.Update(message.OpenItemMsg{ BackModel: m, Item: m.item, }) @@ -240,7 +238,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.err = m.deleteAction() return m, nil case posEditBackBtn: - return m.backModel.Update(common.BackMsg{ + return m.backModel.Update(message.BackMsg{ State: m.backState, }) default: @@ -249,7 +247,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil case key.Matches(msg, common.Keys.Back): - return m.backModel.Update(common.BackMsg{ + return m.backModel.Update(message.BackMsg{ State: m.backState, }) } diff --git a/internal/cli/model/item/file/model.go b/internal/cli/model/item/file/model.go index e7d29c5..fea4981 100644 --- a/internal/cli/model/item/file/model.go +++ b/internal/cli/model/item/file/model.go @@ -2,6 +2,7 @@ package file import ( "errors" + "fmt" "os" "strings" "time" @@ -18,9 +19,7 @@ import ( "github.com/bjlag/go-keeper/internal/cli/element/button" tarea "github.com/bjlag/go-keeper/internal/cli/element/textarea" tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" - "github.com/bjlag/go-keeper/internal/cli/message/item/file" - "github.com/bjlag/go-keeper/internal/cli/message/item/sync" - modelSync "github.com/bjlag/go-keeper/internal/cli/model/item/sync" + "github.com/bjlag/go-keeper/internal/cli/message" "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/domain/client" "github.com/bjlag/go-keeper/internal/usecase/client/item/create" @@ -28,6 +27,8 @@ import ( "github.com/bjlag/go-keeper/internal/usecase/client/item/remove" ) +var errFileNotSupported = errors.New("не поддерживается") + const ( posEditTitle int = iota posEditNotes @@ -65,7 +66,6 @@ func clearErrorAfter(t time.Duration) tea.Cmd { } type Model struct { - main tea.Model help help.Model header string state state @@ -85,14 +85,14 @@ type Model struct { guid uuid.UUID category client.Category - formSync *modelSync.Model + formSync tea.Model usecaseCreate *create.Usecase usecaseEdit *edit.Usecase usecaseDelete *remove.Usecase } -func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *remove.Usecase, formSync *modelSync.Model) *Model { +func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *remove.Usecase, formSync tea.Model) *Model { fp := filepicker.New() fp.AllowedTypes = []string{".txt", ".md", ".jpg", ".jpeg", ".png"} fp.CurrentDirectory, _ = os.UserHomeDir() @@ -112,10 +112,6 @@ func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecase } } -func (m *Model) SetMainModel(model tea.Model) { - m.main = model -} - func (m *Model) Init() tea.Cmd { return nil } @@ -131,7 +127,12 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } return m, nil - case file.OpenMsg: + case message.BackMsg: + if msg.Item != nil { + m.item = msg.Item + } + return m, nil + case message.OpenItemMsg: m.backState = msg.BackState m.backModel = msg.BackModel @@ -195,7 +196,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } if didSelect, path := m.filepicker.DidSelectDisabledFile(msg); didSelect { - m.err = errors.New(path + " не поддерживается") + m.err = fmt.Errorf("%s %w", path, errFileNotSupported) m.selectedFile = "" return m, tea.Batch(cmd, clearErrorAfter(2*time.Second)) } @@ -212,7 +213,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.err = m.createAction() return m, nil case posCreateBackBtn: - return m.backModel.Update(common.BackMsg{ + return m.backModel.Update(message.BackMsg{ State: m.backState, }) default: @@ -226,7 +227,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case posEditEditBtn: err := m.editAction() if err != nil && errors.Is(err, edit.ErrConflict) { - return m.formSync.Update(sync.OpenMsg{ + return m.formSync.Update(message.OpenItemMsg{ BackModel: m, Item: m.item, }) @@ -238,7 +239,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.err = m.deleteAction() return m, nil case posEditBackBtn: - return m.backModel.Update(common.BackMsg{ + return m.backModel.Update(message.BackMsg{ State: m.backState, }) default: @@ -249,14 +250,14 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, common.Keys.Back): if m.selectFileMode { m.selectFileMode = false - return m.Update(file.OpenMsg{ + return m.Update(message.OpenItemMsg{ BackModel: m.backModel, BackState: m.backState, Item: m.item, }) } - return m.backModel.Update(common.BackMsg{ + return m.backModel.Update(message.BackMsg{ State: m.backState, }) } diff --git a/internal/cli/model/item/password/model.go b/internal/cli/model/item/password/model.go index b4b7d1c..94ecd00 100644 --- a/internal/cli/model/item/password/model.go +++ b/internal/cli/model/item/password/model.go @@ -15,9 +15,7 @@ import ( "github.com/bjlag/go-keeper/internal/cli/element/button" tarea "github.com/bjlag/go-keeper/internal/cli/element/textarea" tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" - message "github.com/bjlag/go-keeper/internal/cli/message/item/password" - "github.com/bjlag/go-keeper/internal/cli/message/item/sync" - modelSync "github.com/bjlag/go-keeper/internal/cli/model/item/sync" + "github.com/bjlag/go-keeper/internal/cli/message" "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/domain/client" "github.com/bjlag/go-keeper/internal/usecase/client/item/create" @@ -57,7 +55,6 @@ var ( ) type Model struct { - main tea.Model help help.Model header string state state @@ -72,14 +69,14 @@ type Model struct { item *client.Item category client.Category - formSync *modelSync.Model + formSync tea.Model usecaseCreate *create.Usecase usecaseEdit *edit.Usecase usecaseDelete *remove.Usecase } -func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *remove.Usecase, formSync *modelSync.Model) *Model { +func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *remove.Usecase, formSync tea.Model) *Model { return &Model{ help: help.New(), header: "Пароль", @@ -93,10 +90,6 @@ func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecase } } -func (m *Model) SetMainModel(model tea.Model) { - m.main = model -} - func (m *Model) Init() tea.Cmd { return nil } @@ -110,7 +103,12 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } return m, nil - case message.OpenMsg: + case message.BackMsg: + if msg.Item != nil { + m.item = msg.Item + } + return m, nil + case message.OpenItemMsg: m.backState = msg.BackState m.backModel = msg.BackModel @@ -210,7 +208,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.err = m.createAction() return m, nil case posCreateBackBtn: - return m.backModel.Update(common.BackMsg{ + return m.backModel.Update(message.BackMsg{ State: m.backState, }) default: @@ -224,7 +222,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case posEditEditBtn: err := m.editAction() if err != nil && errors.Is(err, edit.ErrConflict) { - return m.formSync.Update(sync.OpenMsg{ + return m.formSync.Update(message.OpenItemMsg{ BackModel: m, Item: m.item, }) @@ -236,7 +234,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.err = m.deleteAction() return m, nil case posEditBackBtn: - return m.backModel.Update(common.BackMsg{ + return m.backModel.Update(message.BackMsg{ State: m.backState, }) default: @@ -245,7 +243,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil case key.Matches(msg, common.Keys.Back): - return m.backModel.Update(common.BackMsg{ + return m.backModel.Update(message.BackMsg{ State: m.backState, }) } diff --git a/internal/cli/model/item/sync/model.go b/internal/cli/model/item/sync/model.go index 0fb2f38..e9f5687 100644 --- a/internal/cli/model/item/sync/model.go +++ b/internal/cli/model/item/sync/model.go @@ -10,11 +10,7 @@ import ( "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/element/button" - "github.com/bjlag/go-keeper/internal/cli/message/item/bank_card" - "github.com/bjlag/go-keeper/internal/cli/message/item/file" - "github.com/bjlag/go-keeper/internal/cli/message/item/password" - "github.com/bjlag/go-keeper/internal/cli/message/item/sync" - "github.com/bjlag/go-keeper/internal/cli/message/item/text" + "github.com/bjlag/go-keeper/internal/cli/message" "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/domain/client" itemSync "github.com/bjlag/go-keeper/internal/usecase/client/item/sync" @@ -26,7 +22,6 @@ const ( ) type Model struct { - main tea.Model help help.Model header string elements []button.Button @@ -46,17 +41,13 @@ func InitModel(usecaseSync *itemSync.Usecase) *Model { } } -func (m *Model) SetMainModel(model tea.Model) { - m.main = model -} - func (m *Model) Init() tea.Cmd { return nil } func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { - case sync.OpenMsg: + case message.OpenItemMsg: m.prevModel = msg.BackModel if msg.Item == nil { @@ -122,48 +113,18 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } - switch m.item.Category { - case client.CategoryPassword: - return m.prevModel.Update(password.OpenMsg{ - Item: &m.item, - }) - case client.CategoryText: - return m.prevModel.Update(text.OpenMsg{ - Item: &m.item, - }) - case client.CategoryFile: - return m.prevModel.Update(file.OpenMsg{ - Item: &m.item, - }) - case client.CategoryBankCard: - return m.prevModel.Update(bank_card.OpenMsg{ - Item: &m.item, - }) - } + return m.prevModel.Update(message.BackMsg{ + Item: &m.item, + }) case posCancelBtn: - switch m.item.Category { - case client.CategoryPassword: - return m.prevModel.Update(password.OpenMsg{ - Item: &m.item, - }) - case client.CategoryText: - return m.prevModel.Update(text.OpenMsg{ - Item: &m.item, - }) - case client.CategoryFile: - return m.prevModel.Update(file.OpenMsg{ - Item: &m.item, - }) - case client.CategoryBankCard: - return m.prevModel.Update(bank_card.OpenMsg{ - Item: &m.item, - }) - } + return m.prevModel.Update(message.BackMsg{ + Item: &m.item, + }) } return m, nil case key.Matches(msg, common.Keys.Back): - return m.prevModel.Update(common.BackMsg{}) + return m.prevModel.Update(message.BackMsg{}) } } diff --git a/internal/cli/model/item/text/model.go b/internal/cli/model/item/text/model.go index c65f71b..e83368d 100644 --- a/internal/cli/model/item/text/model.go +++ b/internal/cli/model/item/text/model.go @@ -2,9 +2,6 @@ package text import ( "errors" - "github.com/bjlag/go-keeper/internal/cli/message/item/sync" - text2 "github.com/bjlag/go-keeper/internal/cli/message/item/text" - modelSync "github.com/bjlag/go-keeper/internal/cli/model/item/sync" "strings" "github.com/charmbracelet/bubbles/help" @@ -18,6 +15,7 @@ import ( "github.com/bjlag/go-keeper/internal/cli/element/button" tarea "github.com/bjlag/go-keeper/internal/cli/element/textarea" tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" + "github.com/bjlag/go-keeper/internal/cli/message" "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/domain/client" "github.com/bjlag/go-keeper/internal/usecase/client/item/create" @@ -50,7 +48,6 @@ const ( var errUnsupportedCommand = errors.New("unsupported command") type Model struct { - main tea.Model help help.Model header string state state @@ -65,14 +62,14 @@ type Model struct { item *client.Item category client.Category - formSync *modelSync.Model + formSync tea.Model usecaseCreate *create.Usecase usecaseEdit *edit.Usecase usecaseDelete *remove.Usecase } -func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *remove.Usecase, formSync *modelSync.Model) *Model { +func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecaseDelete *remove.Usecase, formSync tea.Model) *Model { return &Model{ help: help.New(), header: "Текст", @@ -86,10 +83,6 @@ func InitModel(usecaseCreate *create.Usecase, usecaseSave *edit.Usecase, usecase } } -func (m *Model) SetMainModel(model tea.Model) { - m.main = model -} - func (m *Model) Init() tea.Cmd { return nil } @@ -103,7 +96,12 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } return m, nil - case text2.OpenMsg: + case message.BackMsg: + if msg.Item != nil { + m.item = msg.Item + } + return m, nil + case message.OpenItemMsg: m.backState = msg.BackState m.backModel = msg.BackModel @@ -193,7 +191,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.err = m.createAction() return m, nil case posCreateBackBtn: - return m.backModel.Update(common.BackMsg{ + return m.backModel.Update(message.BackMsg{ State: m.backState, }) default: @@ -205,7 +203,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case posEditEditBtn: err := m.editAction() if err != nil && errors.Is(err, edit.ErrConflict) { - return m.formSync.Update(sync.OpenMsg{ + return m.formSync.Update(message.OpenItemMsg{ BackModel: m, Item: m.item, }) @@ -217,7 +215,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.err = m.deleteAction() return m, nil case posEditBackBtn: - return m.backModel.Update(common.BackMsg{ + return m.backModel.Update(message.BackMsg{ State: m.backState, }) default: @@ -226,7 +224,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil case key.Matches(msg, common.Keys.Back): - return m.backModel.Update(common.BackMsg{ + return m.backModel.Update(message.BackMsg{ State: m.backState, }) } diff --git a/internal/cli/model/list/model.go b/internal/cli/model/list/model.go index c69865e..1219528 100644 --- a/internal/cli/model/list/model.go +++ b/internal/cli/model/list/model.go @@ -2,7 +2,7 @@ package list import ( "context" - list2 "github.com/bjlag/go-keeper/internal/cli/message/list" + "errors" "strings" "github.com/charmbracelet/bubbles/help" @@ -12,12 +12,15 @@ import ( "github.com/bjlag/go-keeper/internal/cli/common" elist "github.com/bjlag/go-keeper/internal/cli/element/list" + "github.com/bjlag/go-keeper/internal/cli/message" "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/domain/client" "github.com/bjlag/go-keeper/internal/fetcher/item" "github.com/bjlag/go-keeper/internal/usecase/client/sync" ) +var errUnsupportedCategory = errors.New("unsupported category") + const ( stateCategoryList int = iota stateItemList @@ -39,11 +42,23 @@ type Model struct { selectedCategory client.Category + formPassword tea.Model + formText tea.Model + formBankCard tea.Model + formFile tea.Model + usecaseSync *sync.Usecase fetcherItem *item.Fetcher } -func InitModel(usecaseSync *sync.Usecase, fetcherItem *item.Fetcher) *Model { +func InitModel( + usecaseSync *sync.Usecase, + fetcherItem *item.Fetcher, + formPassword tea.Model, + formText tea.Model, + formBankCard tea.Model, + formFile tea.Model, +) *Model { f := &Model{ help: help.New(), header: "Категории", @@ -52,6 +67,11 @@ func InitModel(usecaseSync *sync.Usecase, fetcherItem *item.Fetcher) *Model { usecaseSync: usecaseSync, fetcherItem: fetcherItem, + + formPassword: formPassword, + formText: formText, + formBankCard: formBankCard, + formFile: formFile, } return f @@ -71,21 +91,17 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { f.categories.SetWidth(msg.Width) f.items.SetWidth(msg.Width) return f, nil - case common.BackMsg: + case message.BackMsg: switch msg.State { case stateCategoryList: - return f.Update(list2.OpenCategoriesMsg{}) + return f.Update(message.OpenCategoriesMsg{}) case stateItemList: - return f.Update(list2.OpenItemsMsg{}) + return f.Update(message.OpenItemsMsg{}) } - - case list2.GetDataMsg: + case message.OpenCategoriesMsg: f.state = stateCategoryList - f.err = f.usecaseSync.Do(context.TODO()) - return f.Update(list2.OpenCategoriesMsg{}) - case list2.OpenCategoriesMsg: - f.state = stateCategoryList + f.err = f.usecaseSync.Do(context.TODO()) f.categories.SetItems(nil) f.categories.InsertItem(len(f.categories.Items()), elist.Category{Category: client.CategoryPassword, Title: client.CategoryPassword.String()}) @@ -94,7 +110,7 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { f.categories.InsertItem(len(f.categories.Items()), elist.Category{Category: client.CategoryBankCard, Title: client.CategoryBankCard.String()}) return f, nil - case list2.OpenItemsMsg: + case message.OpenItemsMsg: f.state = stateItemList if c, ok := f.categories.SelectedItem().(elist.Category); ok { @@ -124,7 +140,7 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case stateCategoryList: if c, ok := f.categories.SelectedItem().(elist.Category); ok { f.selectedCategory = c.Category - return f.Update(list2.OpenItemsMsg{ + return f.Update(message.OpenItemsMsg{ Category: c.Category, }) } @@ -132,12 +148,34 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if i, ok := f.items.SelectedItem().(elist.Item); ok { f.selectedCategory = i.Model.Category - return f.main.Update(common.OpenItemMessage{ - BackModel: f, - BackState: f.state, - Category: i.Model.Category, - Item: &i.Model, - }) + switch i.Model.Category { + case client.CategoryPassword: + return f.formPassword.Update(message.OpenItemMsg{ + BackModel: f, + BackState: f.state, + Item: &i.Model, + }) + case client.CategoryText: + return f.formText.Update(message.OpenItemMsg{ + BackModel: f, + BackState: f.state, + Item: &i.Model, + }) + case client.CategoryBankCard: + return f.formBankCard.Update(message.OpenItemMsg{ + BackModel: f, + BackState: f.state, + Item: &i.Model, + }) + case client.CategoryFile: + return f.formFile.Update(message.OpenItemMsg{ + BackModel: f, + BackState: f.state, + Item: &i.Model, + }) + default: + f.err = errUnsupportedCategory + } } } @@ -145,9 +183,9 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, common.Keys.Back): switch f.state { case stateCategoryList: - return f.main.Update(common.BackMsg{}) + return f.main.Update(message.BackMsg{}) case stateItemList: - return f.Update(list2.OpenCategoriesMsg{}) + return f.Update(message.OpenCategoriesMsg{}) } } } diff --git a/internal/cli/model/login/model.go b/internal/cli/model/login/model.go index 20f4f35..e18a3ae 100644 --- a/internal/cli/model/login/model.go +++ b/internal/cli/model/login/model.go @@ -3,7 +3,6 @@ package login import ( "context" "errors" - login2 "github.com/bjlag/go-keeper/internal/cli/message/login" "strings" "github.com/charmbracelet/bubbles/help" @@ -16,7 +15,7 @@ import ( "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/element/button" tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" - "github.com/bjlag/go-keeper/internal/cli/model/register" + "github.com/bjlag/go-keeper/internal/cli/message" "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/infrastructure/validator" "github.com/bjlag/go-keeper/internal/usecase/client/login" @@ -41,11 +40,13 @@ type Model struct { pos int err error + fromRegister tea.Model + usecaseLogin *login.Usecase usecaseMasterKey *master_key.Usecase } -func InitModel(usecaseLogin *login.Usecase, usecaseMasterKey *master_key.Usecase) *Model { +func InitModel(usecaseLogin *login.Usecase, usecaseMasterKey *master_key.Usecase, fromRegister tea.Model) *Model { f := &Model{ help: help.New(), header: "Авторизация", @@ -57,6 +58,8 @@ func InitModel(usecaseLogin *login.Usecase, usecaseMasterKey *master_key.Usecase posCloseBtn: button.CreateDefaultButton("Закрыть"), }, + fromRegister: fromRegister, + usecaseLogin: usecaseLogin, usecaseMasterKey: usecaseMasterKey, } @@ -94,13 +97,15 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } return f, nil - case login2.OpenMsg: + case message.OpenLoginMsg: for i := range f.elements { if e, ok := f.elements[i].(textinput.Model); ok { e.SetValue("") f.elements[i] = e } } + case message.SuccessMsg: + return f.main.Update(msg) case tea.KeyMsg: switch { case key.Matches(msg, common.Keys.Quit): @@ -148,8 +153,8 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case f.pos == posSubmitBtn || f.pos == posEmail || f.pos == posPassword: return f.submit() case f.pos == posRegisterBtn: - return f.main.Update(register.OpenMessage{ - BackModel: f, + return f.fromRegister.Update(message.OpenRegisterMsg{ + LoginModel: f, }) case f.pos == posCloseBtn: return f, tea.Quit @@ -262,7 +267,7 @@ func (f *Model) submit() (tea.Model, tea.Cmd) { return f, nil } - return f.main.Update(login2.SuccessMsg{}) + return f.main.Update(message.SuccessMsg{}) } func (f *Model) updateInputs(msg tea.Msg) tea.Cmd { diff --git a/internal/cli/model/master/message.go b/internal/cli/model/master/message.go deleted file mode 100644 index a1b47bf..0000000 --- a/internal/cli/model/master/message.go +++ /dev/null @@ -1,3 +0,0 @@ -package master - -type OpenMsg struct{} diff --git a/internal/cli/model/master/model.go b/internal/cli/model/master/model.go index 24f98bb..a929881 100644 --- a/internal/cli/model/master/model.go +++ b/internal/cli/model/master/model.go @@ -1,14 +1,6 @@ package master import ( - "errors" - bank_card2 "github.com/bjlag/go-keeper/internal/cli/message/item/bank_card" - create2 "github.com/bjlag/go-keeper/internal/cli/message/item/create" - file2 "github.com/bjlag/go-keeper/internal/cli/message/item/file" - password2 "github.com/bjlag/go-keeper/internal/cli/message/item/password" - text2 "github.com/bjlag/go-keeper/internal/cli/message/item/text" - "github.com/bjlag/go-keeper/internal/cli/message/list" - login2 "github.com/bjlag/go-keeper/internal/cli/message/login" "strings" "github.com/charmbracelet/bubbles/help" @@ -17,20 +9,13 @@ import ( "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/element/button" - "github.com/bjlag/go-keeper/internal/cli/model/item/bank_card" - "github.com/bjlag/go-keeper/internal/cli/model/item/create" - "github.com/bjlag/go-keeper/internal/cli/model/item/file" - "github.com/bjlag/go-keeper/internal/cli/model/item/password" - "github.com/bjlag/go-keeper/internal/cli/model/item/text" + "github.com/bjlag/go-keeper/internal/cli/message" + "github.com/bjlag/go-keeper/internal/cli/model/create" listf "github.com/bjlag/go-keeper/internal/cli/model/list" "github.com/bjlag/go-keeper/internal/cli/model/login" - "github.com/bjlag/go-keeper/internal/cli/model/register" "github.com/bjlag/go-keeper/internal/cli/style" - "github.com/bjlag/go-keeper/internal/domain/client" ) -var errUnsupportedCategory = errors.New("unsupported category") - const ( posViewBtn int = iota posCreateBtn @@ -44,14 +29,9 @@ type Model struct { pos int err error - formLogin *login.Model - formRegister *register.Model - formCreate *create.Model - formList *listf.Model - formPassword *password.Model - formText *text.Model - formBankCard *bank_card.Model - formFile *file.Model + formLogin *login.Model + formCreate *create.Model + formList *listf.Model } func InitModel(opts ...Option) *Model { @@ -75,12 +55,16 @@ func InitModel(opts ...Option) *Model { func (m *Model) Init() tea.Cmd { return tea.Batch( func() tea.Msg { - return login2.OpenMsg{} + return message.OpenLoginMsg{} }, ) } func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + if msg == nil { + return m, nil + } + switch msg := msg.(type) { case tea.KeyMsg: switch { @@ -89,9 +73,9 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, common.Keys.Enter): switch m.pos { case posViewBtn: - return m.formList.Update(list.GetDataMsg{}) + return m.formList.Update(message.OpenCategoriesMsg{}) case posCreateBtn: - return m.formCreate.Update(create2.Open{}) + return m.formCreate.Update(message.OpenItemMsg{}) case posCloseBtn: return m, tea.Quit } @@ -123,58 +107,23 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } - case OpenMsg: - return m, nil - case common.BackMsg: - return m.Update(OpenMsg{}) + case message.BackMsg: + return m.Update(nil) // Forms - case login2.OpenMsg: + case message.OpenLoginMsg: return m.formLogin.Update(msg) - case register.OpenMessage: - return m.formRegister.Update(msg) - case list.OpenCategoriesMsg: + case message.OpenCategoriesMsg: return m.formList.Update(msg) - case list.OpenItemsMsg: + case message.OpenItemsMsg: return m.formList.Update(msg) - case common.OpenItemMessage: - switch msg.Category { - case client.CategoryPassword: - return m.formPassword.Update(password2.OpenMsg{ - BackModel: msg.BackModel, - BackState: msg.BackState, - Item: msg.Item, - }) - case client.CategoryText: - return m.formText.Update(text2.OpenMsg{ - BackModel: msg.BackModel, - BackState: msg.BackState, - Item: msg.Item, - }) - case client.CategoryBankCard: - return m.formBankCard.Update(bank_card2.OpenMsg{ - BackModel: msg.BackModel, - BackState: msg.BackState, - Item: msg.Item, - }) - case client.CategoryFile: - return m.formFile.Update(file2.OpenMsg{ - BackModel: msg.BackModel, - BackState: msg.BackState, - Item: msg.Item, - }) - default: - m.err = errUnsupportedCategory - } // Success - case login2.SuccessMsg: - return m.Update(OpenMsg{}) - case register.SuccessMsg: - return m.Update(OpenMsg{}) + case message.SuccessMsg: + return m.Update(nil) } - return m.Update(OpenMsg{}) + return m.Update(nil) } func (m *Model) View() string { diff --git a/internal/cli/model/master/option.go b/internal/cli/model/master/option.go index 12a67a2..7ff49cb 100644 --- a/internal/cli/model/master/option.go +++ b/internal/cli/model/master/option.go @@ -1,14 +1,9 @@ package master import ( - "github.com/bjlag/go-keeper/internal/cli/model/item/bank_card" - "github.com/bjlag/go-keeper/internal/cli/model/item/create" - "github.com/bjlag/go-keeper/internal/cli/model/item/file" - "github.com/bjlag/go-keeper/internal/cli/model/item/password" - "github.com/bjlag/go-keeper/internal/cli/model/item/text" + "github.com/bjlag/go-keeper/internal/cli/model/create" "github.com/bjlag/go-keeper/internal/cli/model/list" "github.com/bjlag/go-keeper/internal/cli/model/login" - "github.com/bjlag/go-keeper/internal/cli/model/register" ) type Option func(*Model) @@ -20,13 +15,6 @@ func WithLoginForm(form *login.Model) Option { } } -func WithRegisterForm(form *register.Model) Option { - return func(m *Model) { - form.SetMainModel(m) - m.formRegister = form - } -} - func WithCreatForm(form *create.Model) Option { return func(m *Model) { form.SetMainModel(m) @@ -40,31 +28,3 @@ func WithListForm(form *list.Model) Option { m.formList = form } } - -func WithPasswordItemForm(form *password.Model) Option { - return func(m *Model) { - form.SetMainModel(m) - m.formPassword = form - } -} - -func WithTextItemForm(form *text.Model) Option { - return func(m *Model) { - form.SetMainModel(m) - m.formText = form - } -} - -func WithBankCardItemForm(form *bank_card.Model) Option { - return func(m *Model) { - form.SetMainModel(m) - m.formBankCard = form - } -} - -func WithFileItemForm(form *file.Model) Option { - return func(m *Model) { - form.SetMainModel(m) - m.formFile = form - } -} diff --git a/internal/cli/model/register/message.go b/internal/cli/model/register/message.go deleted file mode 100644 index a31b1a4..0000000 --- a/internal/cli/model/register/message.go +++ /dev/null @@ -1,12 +0,0 @@ -package register - -import tea "github.com/charmbracelet/bubbletea" - -type SuccessMsg struct { - AccessToken string - RefreshToken string -} - -type OpenMessage struct { - BackModel tea.Model -} diff --git a/internal/cli/model/register/model.go b/internal/cli/model/register/model.go index fb04e9b..8c39413 100644 --- a/internal/cli/model/register/model.go +++ b/internal/cli/model/register/model.go @@ -15,6 +15,7 @@ import ( "github.com/bjlag/go-keeper/internal/cli/common" "github.com/bjlag/go-keeper/internal/cli/element/button" tinput "github.com/bjlag/go-keeper/internal/cli/element/textinput" + "github.com/bjlag/go-keeper/internal/cli/message" "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/infrastructure/validator" "github.com/bjlag/go-keeper/internal/usecase/client/master_key" @@ -31,14 +32,13 @@ const ( var errUserAlreadyRegistered = common.NewFormError("Пользователь уже зарегистрирован") type Model struct { - main tea.Model help help.Model header string elements []interface{} pos int err error - backModel tea.Model + loginModel tea.Model usecaseRegister *register.Usecase usecaseMasterKey *master_key.Usecase @@ -78,10 +78,6 @@ func InitModel(usecaseRegister *register.Usecase, usecaseMasterKey *master_key.U return f } -func (f *Model) SetMainModel(m tea.Model) { - f.main = m -} - func (f *Model) Init() tea.Cmd { return nil } @@ -95,8 +91,8 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } return f, nil - case OpenMessage: - f.backModel = msg.BackModel + case message.OpenRegisterMsg: + f.loginModel = msg.LoginModel case tea.KeyMsg: switch { case key.Matches(msg, common.Keys.Quit): @@ -138,7 +134,7 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return f, nil case key.Matches(msg, common.Keys.Back): - return f.backModel.Update(common.BackMsg{}) + return f.loginModel.Update(message.BackMsg{}) case key.Matches(msg, common.Keys.Enter): f.err = nil @@ -146,7 +142,7 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case f.pos == posSubmitBtn || f.pos == posEmail || f.pos == posPassword: return f.submit() case f.pos == posBackBtn: - return f.main.Update(common.BackMsg{}) + return f.loginModel.Update(message.OpenLoginMsg{}) } return f, nil @@ -256,7 +252,7 @@ func (f *Model) submit() (tea.Model, tea.Cmd) { return f, nil } - return f.main.Update(SuccessMsg{}) + return f.loginModel.Update(message.SuccessMsg{}) } func (f *Model) updateInputs(msg tea.Msg) tea.Cmd { diff --git a/internal/infrastructure/rpc/client/get_by_guid.go b/internal/infrastructure/rpc/client/get_by_guid.go index 6dda229..1421fa2 100644 --- a/internal/infrastructure/rpc/client/get_by_guid.go +++ b/internal/infrastructure/rpc/client/get_by_guid.go @@ -41,7 +41,7 @@ func (c RPCClient) GetByGUID(ctx context.Context, in *GetByGUIDIn) (*GetByGUIDOu return &GetByGUIDOut{ GUID: itemGUID, EncryptedData: out.GetEncryptedData(), - CreatedAt: out.CreatedAt.AsTime(), - UpdatedAt: out.UpdatedAt.AsTime(), + CreatedAt: out.GetCreatedAt().AsTime(), + UpdatedAt: out.GetUpdatedAt().AsTime(), }, nil } diff --git a/internal/infrastructure/rpc/server/method.go b/internal/infrastructure/rpc/server/method.go index 5ac97d2..472ad43 100644 --- a/internal/infrastructure/rpc/server/method.go +++ b/internal/infrastructure/rpc/server/method.go @@ -63,7 +63,7 @@ func (s RPCServer) RefreshTokens(ctx context.Context, in *pb.RefreshTokensIn) (* return h(ctx, in) } -func (s RPCServer) GetByGuid(ctx context.Context, in *pb.GetByGuidIn) (*pb.GetByGuidOut, error) { +func (s RPCServer) GetByGuid(ctx context.Context, in *pb.GetByGuidIn) (*pb.GetByGuidOut, error) { //nolint:revive handler, err := s.getHandler(GetByGUIDMethod) if err != nil { return nil, err From 42c754155a5a21b5080472b9c9f57bedca233452 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Fri, 14 Mar 2025 23:28:35 +0300 Subject: [PATCH 66/94] doc --- Makefile | 5 +++ cmd/client/main.go | 5 +++ cmd/migrator/config.go | 25 ++++++++++----- cmd/migrator/main.go | 7 +++++ cmd/server/main.go | 5 +++ config/client.yaml.dist | 5 +++ config/migrator_client.yaml.dist | 6 ++-- config/migrator_server.yaml.dist | 8 ++--- internal/app/client/app.go | 11 ++----- internal/app/client/config.go | 31 ++++++++++++++++--- internal/app/server/app.go | 1 + internal/app/server/config.go | 29 ++++++++++++----- internal/cli/element/button/button.go | 18 +++++++++-- internal/cli/element/list/list.go | 3 ++ internal/cli/element/textarea/textarea.go | 4 +++ internal/cli/element/textinput/textinput.go | 4 +++ internal/cli/element/util.go | 2 ++ internal/cli/message/message.go | 26 ++++++++++------ internal/cli/model/create/model.go | 1 + internal/cli/model/item/bank_card/model.go | 1 + internal/cli/model/item/file/model.go | 1 + internal/cli/model/item/password/model.go | 1 + internal/cli/model/item/sync/model.go | 1 + internal/cli/model/item/text/model.go | 1 + internal/cli/model/list/model.go | 1 + internal/cli/model/login/model.go | 1 + internal/cli/model/master/model.go | 1 + internal/cli/model/register/model.go | 1 + internal/cli/style/style.go | 1 + internal/domain/client/encrypted_data.go | 11 +++++-- internal/domain/client/item.go | 28 +++++++++++++++-- internal/domain/client/option.go | 6 +++- internal/domain/server/data/item.go | 15 ++++++--- internal/domain/server/user/user.go | 14 ++++++--- internal/fetcher/item/fetcher.go | 4 +++ internal/infrastructure/auth/jwt.go | 11 +++++++ .../infrastructure/crypt/cipher/cipher.go | 3 ++ .../infrastructure/crypt/master_key/keygen.go | 4 +++ .../infrastructure/crypt/master_key/salt.go | 5 +++ .../crypt/master_key/saltgen.go | 4 +++ internal/infrastructure/db/pg/dsn.go | 1 + internal/infrastructure/db/pg/pg.go | 2 ++ internal/infrastructure/db/sqlite/sqlite.go | 2 ++ internal/infrastructure/logger/logger.go | 6 ++++ internal/infrastructure/rpc/client/client.go | 5 +++ .../infrastructure/rpc/client/create_item.go | 8 +++-- .../infrastructure/rpc/client/delete_item.go | 4 ++- .../rpc/client/get_all_items.go | 18 ++++++----- .../infrastructure/rpc/client/get_by_guid.go | 13 +++++--- internal/infrastructure/rpc/client/login.go | 11 ++++--- .../rpc/client/refresh_tokens.go | 9 ++++-- .../infrastructure/rpc/client/register.go | 11 ++++--- .../infrastructure/rpc/client/update_item.go | 8 +++-- .../infrastructure/rpc/interceptor/auth.go | 15 ++++++--- .../infrastructure/rpc/interceptor/logger.go | 2 ++ internal/infrastructure/rpc/server/method.go | 9 ++++++ internal/infrastructure/rpc/server/option.go | 5 +++ internal/infrastructure/rpc/server/server.go | 1 + .../infrastructure/store/client/item/store.go | 6 ++++ .../store/client/option/store.go | 4 +++ .../store/client/token/store.go | 6 ++++ .../infrastructure/store/server/item/store.go | 11 ++++++- .../infrastructure/store/server/user/model.go | 2 +- .../infrastructure/store/server/user/store.go | 7 ++++- internal/infrastructure/validator/email.go | 1 + internal/infrastructure/validator/password.go | 1 + internal/rpc/create_item/handler.go | 1 + internal/rpc/delete_item/handler.go | 1 + internal/rpc/get_all_items/handler.go | 1 + internal/rpc/get_by_guid/handler.go | 1 + internal/rpc/login/handler.go | 1 + internal/rpc/refresh_tokens/handler.go | 1 + internal/rpc/register/handler.go | 1 + internal/rpc/update_item/handler.go | 1 + .../usecase/client/item/create/usecase.go | 1 + internal/usecase/client/item/edit/usecase.go | 1 + .../usecase/client/item/remove/usecase.go | 1 + internal/usecase/client/item/sync/usecase.go | 1 + internal/usecase/client/login/usecase.go | 1 + internal/usecase/client/master_key/usecase.go | 1 + internal/usecase/client/register/usecase.go | 1 + internal/usecase/client/sync/usecase.go | 1 + .../usecase/server/item/create/usecase.go | 1 + .../usecase/server/item/get_all/usecase.go | 1 + .../server/item/get_by_guid/usecase.go | 1 + .../usecase/server/item/remove/usecase.go | 1 + .../usecase/server/item/update/usecase.go | 1 + internal/usecase/server/user/login/usecase.go | 1 + .../server/user/refresh_tokens/usecase.go | 1 + .../usecase/server/user/register/contract.go | 2 +- .../usecase/server/user/register/usecase.go | 3 +- 91 files changed, 409 insertions(+), 98 deletions(-) diff --git a/Makefile b/Makefile index 688def5..5878462 100644 --- a/Makefile +++ b/Makefile @@ -81,6 +81,11 @@ proto: @echo " > Generate gRPC" @protoc --go_out=. --go_opt=paths=import --go-grpc_out=. --go-grpc_opt=paths=import --go-grpc_opt=require_unimplemented_servers=false proto/* +## doc: open documentation +.PHONY: doc +doc: + godoc -http=:8888 -play + .PHONY: help help: Makefile @echo diff --git a/cmd/client/main.go b/cmd/client/main.go index 3f6af36..d3e1e22 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -1,3 +1,7 @@ +// Отвечает за запуск клиента. +// +// Конфигурация указывается через флаг -c, описывается в YAML файле: +// - пример ./config/client.yaml.dist package main import ( @@ -15,6 +19,7 @@ import ( ) const ( + // Конфигурация по умолчанию, если не передать флаг конфигурации при старте приложения. configPathDefault = "./config/client.yaml" ) diff --git a/cmd/migrator/config.go b/cmd/migrator/config.go index b810d8e..a941244 100644 --- a/cmd/migrator/config.go +++ b/cmd/migrator/config.go @@ -1,16 +1,27 @@ package main +// Config хранит конфигурацию мигратора. type Config struct { - Env string `yaml:"env" env:"ENV" env-default:"dev" env-description:"Environment" json:"env"` - SourcePath string `yaml:"sourcePath" env:"MIGRATIONS_SOURCE_PATH" env-default:"./migrations" env-description:"Migrations source path" json:"source_path"` + // Env окружение. + Env string `yaml:"env" env:"ENV" env-default:"dev" env-description:"Environment" json:"env"` + // SourcePath путь до файлов миграций. + SourcePath string `yaml:"sourcePath" env:"MIGRATIONS_SOURCE_PATH" env-default:"./migrations" env-description:"Migrations source path" json:"source_path"` + // MigrationsTable название таблицы с примененными миграциями. MigrationsTable string `yaml:"migrationsTable" env:"MIGRATIONS_TABLE" env-default:"migrations" env-description:"Migrations table name" json:"migrations_table"` + // Database настройки подключения к базе данной, к которой применяются миграции. Database struct { - Type string `yaml:"type" env:"DB_TYPE" env-default:"pg" env-description:"Database type" json:"type"` - Host string `yaml:"host" env:"DB_HOST" env-description:"Database host" json:"host"` - Port string `yaml:"port" env:"DB_PORT" env-description:"Database port" json:"port"` - Name string `yaml:"name" env:"DB_NAME" env-description:"Database name" json:"name"` - User string `yaml:"user" env:"DB_USER" env-description:"Database user" json:"user"` + // Type тип базы данных: pg - PostgreSQL, sqlite - SQLite. + Type string `yaml:"type" env:"DB_TYPE" env-default:"pg" env-description:"Database type" json:"type"` + // Host хост базы. + Host string `yaml:"host" env:"DB_HOST" env-description:"Database host" json:"host"` + // Port порт базы. + Port string `yaml:"port" env:"DB_PORT" env-description:"Database port" json:"port"` + // Name название базы данных. + Name string `yaml:"name" env:"DB_NAME" env-description:"Database name" json:"name"` + // User пользователь. + User string `yaml:"user" env:"DB_USER" env-description:"Database user" json:"user"` + // Password пароль. Password string `yaml:"password" env:"DB_PASSWORD" env-description:"Database password" json:"password"` } `yaml:"database" json:"database"` } diff --git a/cmd/migrator/main.go b/cmd/migrator/main.go index 872a1f7..3b8cc3c 100644 --- a/cmd/migrator/main.go +++ b/cmd/migrator/main.go @@ -1,3 +1,9 @@ +// Отвечает за применение миграций к базе данных. +// Поддерживает PostgreSQL и SQLite. +// +// Конфигурация указывается через флаг -c, описывается в YAML файле: +// - пример для клиента ./config/migrator_client.yaml.dist +// - пример для сервера ./config/migrator_server.yaml.dist package main import ( @@ -22,6 +28,7 @@ import ( "github.com/bjlag/go-keeper/internal/infrastructure/logger" ) +// Константы содержат поддерживаемые базы данных. const ( typePG = "pg" typeSqlite = "sqlite" diff --git a/cmd/server/main.go b/cmd/server/main.go index 8b12026..a3caabe 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -1,3 +1,7 @@ +// Отвечает за запуск сервера. +// +// Конфигурация указывается через флаг -c, описывается в YAML файле: +// - пример ./config/server.yaml.dist package main import ( @@ -15,6 +19,7 @@ import ( ) const ( + // Конфигурация по умолчанию, если не передать флаг конфигурации при старте приложения. configPathDefault = "./config/server.yaml" ) diff --git a/config/client.yaml.dist b/config/client.yaml.dist index b88523e..9fec616 100644 --- a/config/client.yaml.dist +++ b/config/client.yaml.dist @@ -4,6 +4,11 @@ server: host: localhost port: 8080 +masterKey: + saltLength: 16 + iterCount: 100000 + length: 32 + database: host: localhost port: 5444 diff --git a/config/migrator_client.yaml.dist b/config/migrator_client.yaml.dist index cb40c47..4d94d49 100644 --- a/config/migrator_client.yaml.dist +++ b/config/migrator_client.yaml.dist @@ -1,10 +1,10 @@ env: dev -source_path: "./migrations/client" -migrations_table: "migrations" +sourcePath: "./migrations/client" +migrationsTable: "migrations" database: - type: pg + type: sqlite host: localhost port: 5444 name: master diff --git a/config/migrator_server.yaml.dist b/config/migrator_server.yaml.dist index 6cdcf4a..228ebb1 100644 --- a/config/migrator_server.yaml.dist +++ b/config/migrator_server.yaml.dist @@ -1,12 +1,12 @@ env: dev -source_path: "./migrations/server" -migrations_table: "migrations" +sourcePath: "./migrations/server" +migrationsTable: "migrations" database: - type: sqlite + type: pg host: localhost port: 5444 name: master user: postgres - password: secret \ No newline at end of file + password: secret diff --git a/internal/app/client/app.go b/internal/app/client/app.go index bc5aca4..71d42d6 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -1,3 +1,4 @@ +// Package client настраивает и запускает клиент. package client import ( @@ -35,12 +36,6 @@ import ( "github.com/bjlag/go-keeper/internal/usecase/client/sync" ) -const ( - saltLength = 16 - masterKeyIterCount = 100_000 - masterKeyLength = 256 / 8 -) - type App struct { cfg Config log *zap.Logger @@ -75,8 +70,8 @@ func (a *App) Run(ctx context.Context) error { return fmt.Errorf("%s: %w", op, err) } - salter := master_key.NewSaltGenerator(saltLength) - keymaker := master_key.NewKeyGenerator(masterKeyIterCount, masterKeyLength) + salter := master_key.NewSaltGenerator(a.cfg.MasterKey.SaltLength) + keymaker := master_key.NewKeyGenerator(a.cfg.MasterKey.IterCount, a.cfg.MasterKey.Length) cipher := new(crypt.Cipher) storeItem := sItem.NewStore(db) diff --git a/internal/app/client/config.go b/internal/app/client/config.go index 73a48d0..9e57d58 100644 --- a/internal/app/client/config.go +++ b/internal/app/client/config.go @@ -1,18 +1,39 @@ package client +// Config хранит конфигурацию клиента. type Config struct { + // Env окружение. Env string `yaml:"env" env:"ENV" env-default:"dev" env-description:"Environment" json:"env"` + // Server содержит настройки подключения к серверу. Server struct { + // Host хост сервера. Host string `yaml:"host" env:"SERVER_HOST" env-description:"Server host" json:"host"` - Port int `yaml:"port" env:"SERVER_PORT" env-description:"Server port" json:"port"` + // Port порт сервера. + Port int `yaml:"port" env:"SERVER_PORT" env-description:"Server port" json:"port"` } `yaml:"server" json:"server"` + // MasterKey настройки мастер ключа. + MasterKey struct { + // SaltLength длина соли. + SaltLength int `yaml:"saltLength" env:"SALT_LENGTH" env-description:"Salt length" json:"salt_length"` + // IterCount количество итерация при генерации мастер ключа. + IterCount int `yaml:"iterCount" env:"MASTER_KEY_ITER_COUNT" env-description:"Master key iteration" json:"iter_count"` + // Length длина мастер ключа. + Length int `yaml:"length" env:"MASTER_KEY_LENGTH" env-description:"Master key length" json:"length"` + } `yaml:"masterKey" json:"master_key"` + + // Database настройки подключения к базе данных клиента. Database struct { - Host string `yaml:"host" env:"DB_HOST" env-description:"Database host" json:"host"` - Port string `yaml:"port" env:"DB_PORT" env-description:"Database port" json:"port"` - Name string `yaml:"name" env:"DB_NAME" env-description:"Database name" json:"name"` - User string `yaml:"user" env:"DB_USER" env-description:"Database user" json:"user"` + // Host хост базы. + Host string `yaml:"host" env:"DB_HOST" env-description:"Database host" json:"host"` + // Port порт базы. + Port string `yaml:"port" env:"DB_PORT" env-description:"Database port" json:"port"` + // Name название базы данных. + Name string `yaml:"name" env:"DB_NAME" env-description:"Database name" json:"name"` + // User пользователь. + User string `yaml:"user" env:"DB_USER" env-description:"Database user" json:"user"` + // Password пароль. Password string `yaml:"password" env:"DB_PASSWORD" env-description:"Database password" json:"password"` } `yaml:"database" json:"database"` } diff --git a/internal/app/server/app.go b/internal/app/server/app.go index 828dcba..bcd74d7 100644 --- a/internal/app/server/app.go +++ b/internal/app/server/app.go @@ -1,3 +1,4 @@ +// Package server настраивает и запускает сервер. package server import ( diff --git a/internal/app/server/config.go b/internal/app/server/config.go index ab2bd83..d2fceaf 100644 --- a/internal/app/server/config.go +++ b/internal/app/server/config.go @@ -2,25 +2,40 @@ package server import "time" +// Config хранит конфигурацию сервера. type Config struct { + // Env окружение. Env string `yaml:"env" env:"ENV" env-default:"dev" env-description:"Environment" json:"env"` + // Address содержит адрес сервера. Address struct { + // Host хост сервера. Host string `yaml:"host" env:"ADDRESS_HOST" env-description:"Server host" json:"host"` - Port int `yaml:"port" env:"ADDRESS_PORT" env-description:"Server port" json:"port"` + // Port порт сервера. + Port int `yaml:"port" env:"ADDRESS_PORT" env-description:"Server port" json:"port"` } `yaml:"address" json:"address"` + // Auth настройки авторизации. Auth struct { - AccessTokenExp time.Duration `yaml:"accessTokenExp" env:"ACCESS_TOKEN_EXP" env-description:"Access token expiration" json:"access_token_exp"` + // AccessTokenExp время жизни access токена. + AccessTokenExp time.Duration `yaml:"accessTokenExp" env:"ACCESS_TOKEN_EXP" env-description:"Access token expiration" json:"access_token_exp"` + // RefreshTokenExp время жизни refresh токена. RefreshTokenExp time.Duration `yaml:"refreshTokenExp" env:"REFRESH_TOKEN_EXP" env-description:"Refresh token expiration" json:"refresh_token_exp"` - SecretKey string `yaml:"secretKey" env:"SECRET_KEY" env-description:"Secret key" json:"secret_key"` + // SecretKey секретный ключ. + SecretKey string `yaml:"secretKey" env:"SECRET_KEY" env-description:"Secret key" json:"secret_key"` } `yaml:"auth" json:"auth"` + // Database настройки подключения к базе данных клиента. Database struct { - Host string `yaml:"host" env:"DB_HOST" env-description:"Database host" json:"host"` - Port string `yaml:"port" env:"DB_PORT" env-description:"Database port" json:"port"` - Name string `yaml:"name" env:"DB_NAME" env-description:"Database name" json:"name"` - User string `yaml:"user" env:"DB_USER" env-description:"Database user" json:"user"` + // Host хост базы. + Host string `yaml:"host" env:"DB_HOST" env-description:"Database host" json:"host"` + // Port порт базы. + Port string `yaml:"port" env:"DB_PORT" env-description:"Database port" json:"port"` + // Name название базы данных. + Name string `yaml:"name" env:"DB_NAME" env-description:"Database name" json:"name"` + // User пользователь. + User string `yaml:"user" env:"DB_USER" env-description:"Database user" json:"user"` + // Password пароль. Password string `yaml:"password" env:"DB_PASSWORD" env-description:"Database password" json:"password"` } `yaml:"database" json:"database"` } diff --git a/internal/cli/element/button/button.go b/internal/cli/element/button/button.go index 8de0752..595bb3a 100644 --- a/internal/cli/element/button/button.go +++ b/internal/cli/element/button/button.go @@ -8,13 +8,19 @@ import ( "github.com/bjlag/go-keeper/internal/cli/style" ) +// Button описывает кнопку в UI. type Button struct { - text string - focus bool + // text содержит текст кнопки. + text string + // focus признак имеет ли кнопка фокус. + focus bool + // FocusedStyle стиль кнопки, когда она в состоянии фокуса FocusedStyle lipgloss.Style + // BlurredStyle стиль кнопки, когда она не в фокусе. BlurredStyle lipgloss.Style } +// NewButton создает экземпляр кнопки. func NewButton(text string) Button { return Button{ text: text, @@ -23,14 +29,18 @@ func NewButton(text string) Button { } } +// Option тип опции кнопки. type Option func(m *Button) +// WithFocused меняет состояние кнопки на фокус. func WithFocused() Option { return func(m *Button) { m.Focus() } } +// CreateDefaultButton создает экземпляр кнопки с заранее примененными стилями. +// Через аргумент opts можно передать дополнительные настройки. func CreateDefaultButton(text string, opts ...Option) Button { b := NewButton(text) b.FocusedStyle = style.FocusedStyle @@ -43,6 +53,7 @@ func CreateDefaultButton(text string, opts ...Option) Button { return b } +// String формирует строковое представление кнопки для вывода в UI. func (b *Button) String() string { if b.focus { return fmt.Sprintf("[ %s ]", b.FocusedStyle.Render(b.text)) @@ -50,14 +61,17 @@ func (b *Button) String() string { return fmt.Sprintf("[ %s ]", b.BlurredStyle.Render(b.text)) } +// Focus меняет состояние на фокус. func (b *Button) Focus() { b.focus = true } +// Blur снимает фокус с кнопки. func (b *Button) Blur() { b.focus = false } +// Focused возвращает состояние фокуса кнопки. func (b *Button) Focused() bool { return b.focus } diff --git a/internal/cli/element/list/list.go b/internal/cli/element/list/list.go index 7bf3aa0..c920717 100644 --- a/internal/cli/element/list/list.go +++ b/internal/cli/element/list/list.go @@ -12,6 +12,7 @@ import ( "github.com/bjlag/go-keeper/internal/domain/client" ) +// CreateDefaultList создает список [list.Model] с заранее определенными стилями и элементами списка. func CreateDefaultList(title string, with, height int, itemDelegate list.ItemDelegate, items ...list.Item) list.Model { l := list.New(items, itemDelegate, with, height) @@ -25,6 +26,7 @@ func CreateDefaultList(title string, with, height int, itemDelegate list.ItemDel return l } +// Category описывает элемент списка для категорий. type Category struct { Category client.Category Title string @@ -64,6 +66,7 @@ func (d CategoryDelegate) Render(w io.Writer, m list.Model, index int, listItem _, _ = fmt.Fprint(w, fn(str)) } +// Item описывает элемент списка для конкретной записи: пароль, текст и пр. type Item struct { Model client.Item } diff --git a/internal/cli/element/textarea/textarea.go b/internal/cli/element/textarea/textarea.go index d6999a9..d96aa9e 100644 --- a/internal/cli/element/textarea/textarea.go +++ b/internal/cli/element/textarea/textarea.go @@ -4,20 +4,24 @@ import ( "github.com/charmbracelet/bubbles/textarea" ) +// Option описывает тип опции элемента. type Option func(m *textarea.Model) +// WithFocused настроит элемент в фокусе. func WithFocused() Option { return func(m *textarea.Model) { m.Focus() } } +// WithValue настроит элемент с определенным значением. func WithValue(value string) Option { return func(m *textarea.Model) { m.SetValue(value) } } +// CreateDefaultTextArea создает [textarea.Model] с заранее определенными настройками. func CreateDefaultTextArea(placeholder string, opts ...Option) textarea.Model { m := textarea.New() m.Placeholder = placeholder diff --git a/internal/cli/element/textinput/textinput.go b/internal/cli/element/textinput/textinput.go index d489057..53b6dba 100644 --- a/internal/cli/element/textinput/textinput.go +++ b/internal/cli/element/textinput/textinput.go @@ -6,20 +6,24 @@ import ( "github.com/bjlag/go-keeper/internal/cli/style" ) +// Option описывает тип опции элемента. type Option func(m *textinput.Model) +// WithFocused настроит элемент в фокусе. func WithFocused() Option { return func(m *textinput.Model) { m.Focus() } } +// WithValue настроит элемент с определенным значением. func WithValue(value string) Option { return func(m *textinput.Model) { m.SetValue(value) } } +// CreateDefaultTextInput создает [textinput.Model] с заранее определенными настройками. func CreateDefaultTextInput(placeholder string, opts ...Option) textinput.Model { m := textinput.New() diff --git a/internal/cli/element/util.go b/internal/cli/element/util.go index 4c9d72f..1bd993c 100644 --- a/internal/cli/element/util.go +++ b/internal/cli/element/util.go @@ -5,6 +5,8 @@ import ( "github.com/charmbracelet/bubbles/textinput" ) +// GetValue вспомогательная функция, которая достает из переданного elements элемент +// в позиции pos и возвращает значение модели. func GetValue(elements []interface{}, pos int) string { switch e := elements[pos].(type) { case textinput.Model: diff --git a/internal/cli/message/message.go b/internal/cli/message/message.go index 4ada97d..4a8ef09 100644 --- a/internal/cli/message/message.go +++ b/internal/cli/message/message.go @@ -6,38 +6,46 @@ import ( "github.com/bjlag/go-keeper/internal/domain/client" ) +// OpenLoginMsg сообщение указывает, что надо открыть модель для аутентификации. type OpenLoginMsg struct{} +// OpenRegisterMsg сообщение указывает, что надо открыть модель для регистрации. type OpenRegisterMsg struct { + // LoginModel содержит ссылку на модель аутентификации, чтобы на нее можно было вернуться + // в случае отказа от регистрации. LoginModel tea.Model } +// SuccessMsg вспомогательное сообщение, что какое-то событие выполнилось успешно, например, аутентификация. type SuccessMsg struct{} // List +// OpenCategoriesMsg сообщение используется для открытия списка категорий. type OpenCategoriesMsg struct{} +// OpenItemsMsg сообщение используется для открытия списка элементов определенной категории. type OpenItemsMsg struct { + // Category содержит категорию элементов, которые надо вывести. Category client.Category } +// BackMsg используется для возврата в предыдущую модель, например, из элемента в список элементов. type BackMsg struct { + // State состояние модели, в которое надо вернуться. State int - Item *client.Item + // Item каким элементом надо заменить данные в модели, в которую возвращаемся. + Item *client.Item } // Item +// OpenItemMsg содержит информацию, какой элемент надо открыть. type OpenItemMsg struct { + // BackModel модель, в которую надо вернуться, в случае отмены. BackModel tea.Model + // BackState состояние, в которое надо вернуть модель, в случае отмены. BackState int - Item *client.Item -} - -type OpenItemMessage struct { - BackModel tea.Model - BackState int - Item *client.Item - Category client.Category + // Item каким элементом надо заменить данные в модели, в которую возвращаемся. + Item *client.Item } diff --git a/internal/cli/model/create/model.go b/internal/cli/model/create/model.go index e87f4f4..a0f602a 100644 --- a/internal/cli/model/create/model.go +++ b/internal/cli/model/create/model.go @@ -1,3 +1,4 @@ +// Package create описывает модель, которая выводит UI для создания элемента. package create import ( diff --git a/internal/cli/model/item/bank_card/model.go b/internal/cli/model/item/bank_card/model.go index d05926d..425a5a3 100644 --- a/internal/cli/model/item/bank_card/model.go +++ b/internal/cli/model/item/bank_card/model.go @@ -1,3 +1,4 @@ +// Package bank_card описывает модель для работы UI с банковскими картами. package bank_card import ( diff --git a/internal/cli/model/item/file/model.go b/internal/cli/model/item/file/model.go index fea4981..8899da4 100644 --- a/internal/cli/model/item/file/model.go +++ b/internal/cli/model/item/file/model.go @@ -1,3 +1,4 @@ +// Package file описывает модель для работы UI с файлами (бинарными данными). package file import ( diff --git a/internal/cli/model/item/password/model.go b/internal/cli/model/item/password/model.go index 94ecd00..43779c7 100644 --- a/internal/cli/model/item/password/model.go +++ b/internal/cli/model/item/password/model.go @@ -1,3 +1,4 @@ +// Package password описывает модель для работы UI с паролями. package password import ( diff --git a/internal/cli/model/item/sync/model.go b/internal/cli/model/item/sync/model.go index e9f5687..2f965b5 100644 --- a/internal/cli/model/item/sync/model.go +++ b/internal/cli/model/item/sync/model.go @@ -1,3 +1,4 @@ +// Package sync описывает модель для работы UI при синхронизации клиента с сервером. package sync import ( diff --git a/internal/cli/model/item/text/model.go b/internal/cli/model/item/text/model.go index e83368d..467cdcd 100644 --- a/internal/cli/model/item/text/model.go +++ b/internal/cli/model/item/text/model.go @@ -1,3 +1,4 @@ +// Package text описывает модель для работы UI с текстом. package text import ( diff --git a/internal/cli/model/list/model.go b/internal/cli/model/list/model.go index 1219528..059a9f0 100644 --- a/internal/cli/model/list/model.go +++ b/internal/cli/model/list/model.go @@ -1,3 +1,4 @@ +// Package list описывает модель для работы UI списка категорий и элементов. package list import ( diff --git a/internal/cli/model/login/model.go b/internal/cli/model/login/model.go index e18a3ae..861b41e 100644 --- a/internal/cli/model/login/model.go +++ b/internal/cli/model/login/model.go @@ -1,3 +1,4 @@ +// Package login описывает модель для работы UI аутентификации пользователя. package login import ( diff --git a/internal/cli/model/master/model.go b/internal/cli/model/master/model.go index a929881..83185c0 100644 --- a/internal/cli/model/master/model.go +++ b/internal/cli/model/master/model.go @@ -1,3 +1,4 @@ +// Package master описывает основную модель, которая стартует при запуске CLI приложения. package master import ( diff --git a/internal/cli/model/register/model.go b/internal/cli/model/register/model.go index 8c39413..de4117b 100644 --- a/internal/cli/model/register/model.go +++ b/internal/cli/model/register/model.go @@ -1,3 +1,4 @@ +// Package register описывает модель для работы UI регистрации пользователя. package register import ( diff --git a/internal/cli/style/style.go b/internal/cli/style/style.go index a2c5717..ade6bcd 100644 --- a/internal/cli/style/style.go +++ b/internal/cli/style/style.go @@ -1,3 +1,4 @@ +// Package style содержит стили CLI приложения, которые используются для вывода UI. package style import ( diff --git a/internal/domain/client/encrypted_data.go b/internal/domain/client/encrypted_data.go index 9954ad3..2eabfb6 100644 --- a/internal/domain/client/encrypted_data.go +++ b/internal/domain/client/encrypted_data.go @@ -5,11 +5,16 @@ import ( "fmt" ) +// EncryptedData описывает зашифрованные данные, которые приходят с сервера. type EncryptedData struct { - Title string `json:"title"` + // Title название элемента. + Title string `json:"title"` + // Category категория элемента: пароль, текст, файл и пр. Category Category `json:"category_id"` - Value *[]byte `json:"value,omitempty"` - Notes string `json:"notes"` + // Value у каждой категории свой набор данных, после расшифровки хранится в байтах, по сути это JSON. + Value *[]byte `json:"value,omitempty"` + // Notes какие-то заметки, которые можно указать у элемента. + Notes string `json:"notes"` } func (d *EncryptedData) UnmarshalJSON(data []byte) error { diff --git a/internal/domain/client/item.go b/internal/domain/client/item.go index 6170462..fa09335 100644 --- a/internal/domain/client/item.go +++ b/internal/domain/client/item.go @@ -6,15 +6,21 @@ import ( "github.com/google/uuid" ) +// Category тип под категорию. type Category int const ( + // CategoryPassword пароль. CategoryPassword Category = iota + // CategoryText текст. CategoryText + // CategoryFile файл (бинарные данные). CategoryFile + // CategoryBankCard банковская карта. CategoryBankCard ) +// String возвращает строковое представление категории. func (c Category) String() string { switch c { case CategoryPassword: @@ -29,6 +35,7 @@ func (c Category) String() string { return "" } +// RawItem описывает элемент как он приходит с сервера. type RawItem struct { GUID uuid.UUID Category Category @@ -39,6 +46,8 @@ type RawItem struct { UpdatedAt time.Time } +// Item содержит элемент после разбора. +// В Value будет лежать структура в зависимости от категории Category - Password, File, BankCard. type Item struct { GUID uuid.UUID Category Category @@ -49,6 +58,7 @@ type Item struct { UpdatedAt time.Time } +// NewItem создает новый элемент. func NewItem(category Category, title string, value interface{}, note string) Item { now := time.Now() return Item{ @@ -62,6 +72,7 @@ func NewItem(category Category, title string, value interface{}, note string) It } } +// NewPasswordItem создает новый элемент категории CategoryPassword. func NewPasswordItem(title, login, password, note string) Item { return NewItem( CategoryPassword, @@ -74,6 +85,7 @@ func NewPasswordItem(title, login, password, note string) Item { ) } +// NewTextItem создает новый элемент категории CategoryText. func NewTextItem(title, note string) Item { return NewItem( CategoryText, @@ -83,6 +95,7 @@ func NewTextItem(title, note string) Item { ) } +// NewBankCardItem создает новый элемент категории CategoryBankCard. func NewBankCardItem(title, number, cvv, expiry, note string) Item { return NewItem( CategoryBankCard, @@ -96,6 +109,7 @@ func NewBankCardItem(title, number, cvv, expiry, note string) Item { ) } +// NewFileItem создает новый элемент категории CategoryFile. func NewFileItem(title, name string, data []byte, note string) Item { return NewItem( CategoryFile, @@ -108,18 +122,28 @@ func NewFileItem(title, name string, data []byte, note string) Item { ) } +// Password описывает пароль. type Password struct { - Login string `json:"login"` + // Login логин. + Login string `json:"login"` + // Password пароль. Password string `json:"password"` } +// File файл. type File struct { + // Name название файла. Name string `json:"path"` + // Data контент файла. Data []byte `json:"data"` } +// BankCard банковская карта. type BankCard struct { + // Number номер карты. Number string `json:"number"` - CVV string `json:"cvv"` + // CVV код. + CVV string `json:"cvv"` + // Expiry действует до. Expiry string `json:"exp"` } diff --git a/internal/domain/client/option.go b/internal/domain/client/option.go index 29f2891..a2ac430 100644 --- a/internal/domain/client/option.go +++ b/internal/domain/client/option.go @@ -1,10 +1,14 @@ package client const ( + // OptSaltKey слаг под опцию, которая хранит соль для генерации мастер ключа. OptSaltKey = "salt" ) +// Option описывает опцию, которую храним в БД клиента. type Option struct { - Slug string + // Slug какое-то название опции. + Slug string + // Value значение опции. Value string } diff --git a/internal/domain/server/data/item.go b/internal/domain/server/data/item.go index 1b4029d..a6c0850 100644 --- a/internal/domain/server/data/item.go +++ b/internal/domain/server/data/item.go @@ -6,14 +6,21 @@ import ( "github.com/google/uuid" ) +// Item описывает секретные данные. type Item struct { - GUID uuid.UUID - UserGUID uuid.UUID + // GUID уникальный идентификатор. + GUID uuid.UUID + // UserGUID идентификатор пользователя, которому принадлежат данные. + UserGUID uuid.UUID + // EncryptedData сами секретные данные в зашифрованном виде. EncryptedData []byte - CreatedAt time.Time - UpdatedAt time.Time + // CreatedAt дата и время создания записи. + CreatedAt time.Time + // UpdatedAt дата и время обновления записи. + UpdatedAt time.Time } +// UpdatedItem описывает данные для обновления. type UpdatedItem struct { EncryptedData []byte UpdatedAt time.Time diff --git a/internal/domain/server/user/user.go b/internal/domain/server/user/user.go index fb392a7..78281c7 100644 --- a/internal/domain/server/user/user.go +++ b/internal/domain/server/user/user.go @@ -6,10 +6,16 @@ import ( "github.com/google/uuid" ) +// User описывает пользователя. type User struct { - GUID uuid.UUID - Email string + // GUID уникальный идентификатор пользователя. + GUID uuid.UUID + // Email пользователя. + Email string + // PasswordHash хеш пароля. PasswordHash string - CreatedAt time.Time - UpdatedAt time.Time + // CreatedAt дата и время создания пользователя. + CreatedAt time.Time + // UpdatedAt дата и время обновления пользователя. + UpdatedAt time.Time } diff --git a/internal/fetcher/item/fetcher.go b/internal/fetcher/item/fetcher.go index 16175d1..f060d45 100644 --- a/internal/fetcher/item/fetcher.go +++ b/internal/fetcher/item/fetcher.go @@ -1,3 +1,4 @@ +// Package item отвечает за получение данных по элементам. package item import ( @@ -11,6 +12,8 @@ import ( const prefixOp = "fetcher.item" +// ErrUnknownCategory ошибка на случай если получены данные, значение которых не известны. +// Неизвестно, в какую модель их раскладывать. var ErrUnknownCategory = errors.New("unknown category") type Fetcher struct { @@ -23,6 +26,7 @@ func NewFetcher(itemStore itemStore) *Fetcher { } } +// ItemsByCategory получает элементы из локальной базы клиента по указанной категории. func (u *Fetcher) ItemsByCategory(ctx context.Context, category model.Category) ([]model.Item, error) { const op = prefixOp + ".ItemsByCategory" diff --git a/internal/infrastructure/auth/jwt.go b/internal/infrastructure/auth/jwt.go index fea3d9a..be6d6fa 100644 --- a/internal/infrastructure/auth/jwt.go +++ b/internal/infrastructure/auth/jwt.go @@ -1,3 +1,4 @@ +// Package auth отвечает за выпуск JWT токенов и работу с ними. package auth import ( @@ -40,6 +41,7 @@ func NewJWT(secretKey string, accessTokenExp, refreshTokenExp time.Duration) *JW } } +// GetUserGUIDFromAccessToken получает GUID пользователя из переданного access токена. func (g *JWT) GetUserGUIDFromAccessToken(tokenString string) (uuid.UUID, error) { const op = prefixOp + "GetUserGUIDFromAccessToken" @@ -64,6 +66,7 @@ func (g *JWT) GetUserGUIDFromAccessToken(tokenString string) (uuid.UUID, error) return guid, nil } +// GetUserGUIDFromRefreshToken получает GUID пользователя из переданного refresh токена. func (g *JWT) GetUserGUIDFromRefreshToken(tokenString string) (uuid.UUID, error) { const op = prefixOp + "GetUserGUIDFromRefreshToken" @@ -88,6 +91,7 @@ func (g *JWT) GetUserGUIDFromRefreshToken(tokenString string) (uuid.UUID, error) return guid, nil } +// GenerateTokens генерирует и возвращает пару токенов: access и refresh. func (g *JWT) GenerateTokens(uuid string) (accessToken string, refreshToken string, err error) { const op = prefixOp + "GenerateTokens" @@ -107,6 +111,9 @@ func (g *JWT) GenerateTokens(uuid string) (accessToken string, refreshToken stri return } +// GenerateAccessToken генерирует и возвращает access токен. +// В claims токена кладет GUID переданного пользователя. +// Используется для аутентификации пользователя. func (g *JWT) GenerateAccessToken(uuid string) (string, *Claims, error) { const op = prefixOp + "GenerateAccessToken" @@ -129,6 +136,9 @@ func (g *JWT) GenerateAccessToken(uuid string) (string, *Claims, error) { return tokenString, claim, nil } +// GenerateRefreshToken генерирует и возвращает refresh токен. +// В claims токена кладет GUID переданного пользователя. +// Используется для обновления access и refresh токенов. func (g *JWT) GenerateRefreshToken(cl *Claims) (string, error) { const op = prefixOp + "GenerateRefreshToken" @@ -151,6 +161,7 @@ func (g *JWT) GenerateRefreshToken(cl *Claims) (string, error) { return tokenString, nil } +// ParseToken парсит токен из его строкового представления в tokenString. func (g *JWT) ParseToken(tokenString, subjectClaim string) (*jwt.Token, *Claims, error) { const op = prefixOp + "ParseToken" diff --git a/internal/infrastructure/crypt/cipher/cipher.go b/internal/infrastructure/crypt/cipher/cipher.go index 349be26..2bf6cd1 100644 --- a/internal/infrastructure/crypt/cipher/cipher.go +++ b/internal/infrastructure/crypt/cipher/cipher.go @@ -1,3 +1,4 @@ +// Package cipher отвечает за шифрование и дешифровку данных по переданному ключу. package cipher import ( @@ -13,6 +14,7 @@ type Cipher struct { gcmInstance cipher.AEAD } +// Encrypt шифрует переданные данные data используя переданный ключ key. func (c *Cipher) Encrypt(data, key []byte) ([]byte, error) { err := c.setup(key) if err != nil { @@ -28,6 +30,7 @@ func (c *Cipher) Encrypt(data, key []byte) ([]byte, error) { return c.gcmInstance.Seal(nonce, nonce, data, nil), nil } +// Decrypt дешифрует переданные зашифрованные данные в encryptedData используя переданный ключ key. func (c *Cipher) Decrypt(encryptedData, key []byte) ([]byte, error) { err := c.setup(key) if err != nil { diff --git a/internal/infrastructure/crypt/master_key/keygen.go b/internal/infrastructure/crypt/master_key/keygen.go index 36c034d..eef9723 100644 --- a/internal/infrastructure/crypt/master_key/keygen.go +++ b/internal/infrastructure/crypt/master_key/keygen.go @@ -6,6 +6,9 @@ import ( "golang.org/x/crypto/pbkdf2" ) +// KeyGenerator генератор мастер ключа на основе алгоритма PBKDF2. +// В iteCount количество итераций при генерации ключа. +// В keyLen длина ключа. type KeyGenerator struct { iteCount int keyLen int @@ -18,6 +21,7 @@ func NewKeyGenerator(iterCount, keyLen int) *KeyGenerator { } } +// GenerateMasterKey генерирует мастер ключ для переданного пароля password используя соль salt. func (g KeyGenerator) GenerateMasterKey(password, salt []byte) []byte { return pbkdf2.Key(password, salt, g.iteCount, g.keyLen, sha512.New) } diff --git a/internal/infrastructure/crypt/master_key/salt.go b/internal/infrastructure/crypt/master_key/salt.go index a2f8947..37953ee 100644 --- a/internal/infrastructure/crypt/master_key/salt.go +++ b/internal/infrastructure/crypt/master_key/salt.go @@ -5,8 +5,10 @@ import ( "encoding/base64" ) +// Salt соль для мастер ключа. type Salt []byte +// NewSalt создает соль указанной длины length. func NewSalt(length int) (*Salt, error) { salt := make([]byte, length) _, err := rand.Read(salt) @@ -19,6 +21,7 @@ func NewSalt(length int) (*Salt, error) { return &s, nil } +// ParseString создание структуры Salt из строкового представления соли. func ParseString(salt string) (*Salt, error) { b, err := base64.StdEncoding.DecodeString(salt) if err != nil { @@ -30,10 +33,12 @@ func ParseString(salt string) (*Salt, error) { return &s, nil } +// Value возвращает значение соли. func (s Salt) Value() []byte { return s } +// ToString encoding Salt в строку. func (s Salt) ToString() string { return base64.StdEncoding.EncodeToString(s) } diff --git a/internal/infrastructure/crypt/master_key/saltgen.go b/internal/infrastructure/crypt/master_key/saltgen.go index 5e1205b..a5693b3 100644 --- a/internal/infrastructure/crypt/master_key/saltgen.go +++ b/internal/infrastructure/crypt/master_key/saltgen.go @@ -1,15 +1,19 @@ package master_key +// SaltGenerator генератор соли Salt. +// В length длина соли. type SaltGenerator struct { length int } +// NewSaltGenerator создает генератор соли с указанной длиной в length. func NewSaltGenerator(length int) *SaltGenerator { return &SaltGenerator{ length: length, } } +// GenerateSalt генерирует соль. func (g SaltGenerator) GenerateSalt() (*Salt, error) { return NewSalt(g.length) } diff --git a/internal/infrastructure/db/pg/dsn.go b/internal/infrastructure/db/pg/dsn.go index 839bad9..def2b4b 100644 --- a/internal/infrastructure/db/pg/dsn.go +++ b/internal/infrastructure/db/pg/dsn.go @@ -2,6 +2,7 @@ package pg import "fmt" +// GetDSN вспомогательная функция для получения валидного DSN для подключения БД PostgreSQL. func GetDSN(host, port, name, user, pass string) string { return fmt.Sprintf( "postgresql://%s:%s@%s:%s/%s?sslmode=disable", diff --git a/internal/infrastructure/db/pg/pg.go b/internal/infrastructure/db/pg/pg.go index aeb8b6b..372ca21 100644 --- a/internal/infrastructure/db/pg/pg.go +++ b/internal/infrastructure/db/pg/pg.go @@ -1,3 +1,4 @@ +// Package pg отвечает за работу с базой данных PostgreSQL. package pg import ( @@ -26,6 +27,7 @@ func New(dsn string) *PG { } } +// Connect создает подключение к базе. func (p *PG) Connect() (*sqlx.DB, error) { const op = "pg.Connect" diff --git a/internal/infrastructure/db/sqlite/sqlite.go b/internal/infrastructure/db/sqlite/sqlite.go index 46a1965..07c2be7 100644 --- a/internal/infrastructure/db/sqlite/sqlite.go +++ b/internal/infrastructure/db/sqlite/sqlite.go @@ -1,3 +1,4 @@ +// Package sqlite отвечает за работу с базой данных SQLite. package sqlite import ( @@ -17,6 +18,7 @@ func New(dsn string) *SQLite { } } +// Connect создает подключение к базе. func (l SQLite) Connect() (*sqlx.DB, error) { const op = "sqlite.Connect" diff --git a/internal/infrastructure/logger/logger.go b/internal/infrastructure/logger/logger.go index 2f015ce..09b5757 100644 --- a/internal/infrastructure/logger/logger.go +++ b/internal/infrastructure/logger/logger.go @@ -1,3 +1,4 @@ +// Package logger отвечает за создание логгера и работу с ним. package logger import ( @@ -12,6 +13,7 @@ import ( type ctxKeyLogger int +// IDLoggerKey ID ключа для работы с логгером через контекст. const IDLoggerKey ctxKeyLogger = 0 var ( @@ -19,6 +21,8 @@ var ( logger *zap.Logger ) +// Get получить логгер. Используется паттерн Singletone. +// При первом получении создается экземпляр логгера и кладется в глобальную переменную пакета. func Get(env string) *zap.Logger { once.Do(func() { var config zap.Config @@ -42,6 +46,7 @@ func Get(env string) *zap.Logger { return logger } +// FromCtx получает логгер из контекста. func FromCtx(ctx context.Context) *zap.Logger { if l, ok := ctx.Value(IDLoggerKey).(*zap.Logger); ok { return l @@ -52,6 +57,7 @@ func FromCtx(ctx context.Context) *zap.Logger { return zap.NewNop() } +// WithCtx кладет логгер в контекст. func WithCtx(ctx context.Context, l *zap.Logger) context.Context { if lp, ok := ctx.Value(IDLoggerKey).(*zap.Logger); ok { if lp == l { diff --git a/internal/infrastructure/rpc/client/client.go b/internal/infrastructure/rpc/client/client.go index 7c107d9..75a1540 100644 --- a/internal/infrastructure/rpc/client/client.go +++ b/internal/infrastructure/rpc/client/client.go @@ -1,3 +1,4 @@ +// Package client содержит логику для работы с сервером через RPC протокол. package client import ( @@ -17,6 +18,9 @@ type RPCClient struct { client rpc.KeeperClient } +// NewRPCClient создает gRPC подключение к серверу расположенному на хосте serverHost и порте serverPort. +// tokensStore используется для интерцептора отвечающего за авторизацию клиента. +// log используется для интерцептора по логированию запросов. func NewRPCClient(serverHost string, serverPort int, tokensStore *token.Store, log *zap.Logger) (*RPCClient, error) { conn, err := grpc.NewClient( fmt.Sprintf("%s:%d", serverHost, serverPort), @@ -36,6 +40,7 @@ func NewRPCClient(serverHost string, serverPort int, tokensStore *token.Store, l }, nil } +// Close закрывает подключение. func (c RPCClient) Close() error { return c.conn.Close() } diff --git a/internal/infrastructure/rpc/client/create_item.go b/internal/infrastructure/rpc/client/create_item.go index 55bbd97..1949be6 100644 --- a/internal/infrastructure/rpc/client/create_item.go +++ b/internal/infrastructure/rpc/client/create_item.go @@ -11,12 +11,14 @@ import ( "github.com/bjlag/go-keeper/internal/generated/rpc" ) +// CreateItemIn данные создаваемого элемента. type CreateItemIn struct { - GUID uuid.UUID - EncryptedData []byte - CreatedAt time.Time + GUID uuid.UUID // GUID создаваемого элемента. + EncryptedData []byte // EncryptedData секретные данные элемента в зашифрованном виде. + CreatedAt time.Time // CreatedAt дата и время создания элемента. } +// CreateItem метод для создания элемента. func (c RPCClient) CreateItem(ctx context.Context, in *CreateItemIn) error { const op = "client.rpc.CreateItem" diff --git a/internal/infrastructure/rpc/client/delete_item.go b/internal/infrastructure/rpc/client/delete_item.go index 9bac1b5..b0eabb6 100644 --- a/internal/infrastructure/rpc/client/delete_item.go +++ b/internal/infrastructure/rpc/client/delete_item.go @@ -9,10 +9,12 @@ import ( "github.com/bjlag/go-keeper/internal/generated/rpc" ) +// DeleteItemIn данные удаляемого элемента. type DeleteItemIn struct { - GUID uuid.UUID + GUID uuid.UUID // GUID удаляемого элемента. } +// DeleteItem метод для удаления элемента. func (c RPCClient) DeleteItem(ctx context.Context, in *DeleteItemIn) error { const op = "client.rpc.DeleteItem" diff --git a/internal/infrastructure/rpc/client/get_all_items.go b/internal/infrastructure/rpc/client/get_all_items.go index 7248911..4558f4b 100644 --- a/internal/infrastructure/rpc/client/get_all_items.go +++ b/internal/infrastructure/rpc/client/get_all_items.go @@ -10,22 +10,26 @@ import ( "github.com/bjlag/go-keeper/internal/generated/rpc" ) +// GetAllItemsIn параметры запроса. type GetAllItemsIn struct { - Limit uint32 - Offset uint32 + Limit uint32 // Limit сколько получить данных максимум. + Offset uint32 // Offset с какой позиции получать данные. } +// GetAllItemsOut результат работы. type GetAllItemsOut struct { - Items []GetAllDataItem + Items []GetAllDataItem // Items список полученных элементов. } +// GetAllDataItem данные полученного элемента. type GetAllDataItem struct { - GUID uuid.UUID - EncryptedData []byte - CreatedAt time.Time - UpdatedAt time.Time + GUID uuid.UUID // GUID идентификатор. + EncryptedData []byte // EncryptedData зашифрованные данные. + CreatedAt time.Time // CreatedAt дата и время создания. + UpdatedAt time.Time // UpdatedAt дата и время обновления. } +// GetAllItems метод для получения всех данных авторизованного пользователя. func (c RPCClient) GetAllItems(ctx context.Context, in *GetAllItemsIn) (*GetAllItemsOut, error) { const op = "client.rpc.GetAllItems" diff --git a/internal/infrastructure/rpc/client/get_by_guid.go b/internal/infrastructure/rpc/client/get_by_guid.go index 1421fa2..197ab34 100644 --- a/internal/infrastructure/rpc/client/get_by_guid.go +++ b/internal/infrastructure/rpc/client/get_by_guid.go @@ -10,17 +10,20 @@ import ( "github.com/bjlag/go-keeper/internal/generated/rpc" ) +// GetByGUIDIn параметры запроса. type GetByGUIDIn struct { - GUID uuid.UUID + GUID uuid.UUID // GUID элемента, данные которого надо получить. } +// GetByGUIDOut результат работы. type GetByGUIDOut struct { - GUID uuid.UUID - EncryptedData []byte - CreatedAt time.Time - UpdatedAt time.Time + GUID uuid.UUID // GUID идентификатор элемента. + EncryptedData []byte // EncryptedData зашифрованные данные элемента. + CreatedAt time.Time // CreatedAt дата и время создания элемента. + UpdatedAt time.Time // UpdatedAt дата и время обновления элемента. } +// GetByGUID метод для получения элемента по его GUID. func (c RPCClient) GetByGUID(ctx context.Context, in *GetByGUIDIn) (*GetByGUIDOut, error) { const op = "client.rpc.GetByGUID" diff --git a/internal/infrastructure/rpc/client/login.go b/internal/infrastructure/rpc/client/login.go index 1baa650..b608fcd 100644 --- a/internal/infrastructure/rpc/client/login.go +++ b/internal/infrastructure/rpc/client/login.go @@ -7,16 +7,19 @@ import ( "github.com/bjlag/go-keeper/internal/generated/rpc" ) +// LoginIn параметры запроса. type LoginIn struct { - Email string - Password string + Email string // Email пользователя. + Password string // Password пароль пользователя. } +// LoginOut результат. type LoginOut struct { - AccessToken string - RefreshToken string + AccessToken string // AccessToken access токен. + RefreshToken string // RefreshToken refresh токен. } +// Login метод для аутентификации пользователя. func (c RPCClient) Login(ctx context.Context, in LoginIn) (*LoginOut, error) { const op = "client.rpc.Login" diff --git a/internal/infrastructure/rpc/client/refresh_tokens.go b/internal/infrastructure/rpc/client/refresh_tokens.go index 59e682b..ea0c02f 100644 --- a/internal/infrastructure/rpc/client/refresh_tokens.go +++ b/internal/infrastructure/rpc/client/refresh_tokens.go @@ -7,15 +7,18 @@ import ( "github.com/bjlag/go-keeper/internal/generated/rpc" ) +// RefreshTokensIn параметры запроса. type RefreshTokensIn struct { - RefreshToken string + RefreshToken string // RefreshToken refresh токен. } +// RefreshTokensOut результат. type RefreshTokensOut struct { - AccessToken string - RefreshToken string + AccessToken string // AccessToken access токен. + RefreshToken string // RefreshToken refresh токен. } +// RefreshTokens метод для обновления токенов используя refresh токен. func (c RPCClient) RefreshTokens(ctx context.Context, in RefreshTokensIn) (*RefreshTokensOut, error) { const op = "client.rpc.RefreshTokens" diff --git a/internal/infrastructure/rpc/client/register.go b/internal/infrastructure/rpc/client/register.go index fbf956a..44a4c11 100644 --- a/internal/infrastructure/rpc/client/register.go +++ b/internal/infrastructure/rpc/client/register.go @@ -7,16 +7,19 @@ import ( "github.com/bjlag/go-keeper/internal/generated/rpc" ) +// RegisterIn параметры запроса. type RegisterIn struct { - Email string - Password string + Email string // Email пользователя. + Password string // Password пароль пользователя. } +// RegisterOut результат. type RegisterOut struct { - AccessToken string - RefreshToken string + AccessToken string // AccessToken access токен. + RefreshToken string // RefreshToken refresh токен. } +// Register метод для регистрации пользователя. func (c RPCClient) Register(ctx context.Context, in RegisterIn) (*RegisterOut, error) { const op = "client.rpc.Register" diff --git a/internal/infrastructure/rpc/client/update_item.go b/internal/infrastructure/rpc/client/update_item.go index 9ec0622..cf72d38 100644 --- a/internal/infrastructure/rpc/client/update_item.go +++ b/internal/infrastructure/rpc/client/update_item.go @@ -9,12 +9,14 @@ import ( "github.com/bjlag/go-keeper/internal/generated/rpc" ) +// UpdateItemIn параметры запроса. type UpdateItemIn struct { - GUID uuid.UUID - EncryptedData []byte - Version int64 + GUID uuid.UUID // GUID идентификатор обновляемого элемента. + EncryptedData []byte // EncryptedData зашифрованные данные элемента + Version int64 // Version версия, с которой обновляем элемент (текущая версия). } +// UpdateItem метод для обновления элемента. func (c RPCClient) UpdateItem(ctx context.Context, in *UpdateItemIn) (int64, error) { const op = "client.rpc.UpdateItem" diff --git a/internal/infrastructure/rpc/interceptor/auth.go b/internal/infrastructure/rpc/interceptor/auth.go index 053d305..c7c7bc1 100644 --- a/internal/infrastructure/rpc/interceptor/auth.go +++ b/internal/infrastructure/rpc/interceptor/auth.go @@ -18,16 +18,21 @@ import ( ) const ( - AuthMeta = "authorization" + // authMeta мета заголовок, в котором лежит access токен. + authMeta = "authorization" + // bearerAuth метод аутентификации. bearerAuth = "Bearer" ) +// methodSkip содержит gRPC методы, у которых не надо проверять аутентификацию. var methodSkip = map[string]struct{}{ rpc.Keeper_Login_FullMethodName: {}, rpc.Keeper_Register_FullMethodName: {}, rpc.Keeper_RefreshTokens_FullMethodName: {}, } +// CheckAccessTokenServerInterceptor клиентский интерцептор, который перед запросом к серверу кладет мета заголовок +// в запрос с access токеном, чтобы сервер мог проверить аутентифицирован пользователь или нет. func CheckAccessTokenServerInterceptor(jwt *auth.JWT, log *zap.Logger) grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { if _, ok := methodSkip[info.FullMethod]; ok { @@ -39,7 +44,7 @@ func CheckAccessTokenServerInterceptor(jwt *auth.JWT, log *zap.Logger) grpc.Unar return nil, status.Errorf(codes.PermissionDenied, "permission denied") } - meta := md.Get(AuthMeta) + meta := md.Get(authMeta) if len(meta) == 0 { return nil, status.Errorf(codes.PermissionDenied, "permission denied") } @@ -62,13 +67,15 @@ func CheckAccessTokenServerInterceptor(jwt *auth.JWT, log *zap.Logger) grpc.Unar } } +// AuthClientInterceptor интерцептор, который на стороне сервера, перед исполнением запроса, проверяет +// по переданной информации с клиента, аутентифицирован пользователь или нет. func AuthClientInterceptor(tokens *token.Store) grpc.UnaryClientInterceptor { return func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { if _, ok := methodSkip[method]; ok { return invoker(ctx, method, req, reply, cc, opts...) } - ctx = metadata.AppendToOutgoingContext(ctx, AuthMeta, fmt.Sprintf("%s %s", bearerAuth, tokens.AccessToken())) + ctx = metadata.AppendToOutgoingContext(ctx, authMeta, fmt.Sprintf("%s %s", bearerAuth, tokens.AccessToken())) err := invoker(ctx, method, req, reply, cc, opts...) if status.Code(err) == codes.PermissionDenied { in := &rpc.RefreshTokensIn{ @@ -90,7 +97,7 @@ func AuthClientInterceptor(tokens *token.Store) grpc.UnaryClientInterceptor { md = metadata.New(nil) } - md.Set(AuthMeta, fmt.Sprintf("%s %s", bearerAuth, tokens.AccessToken())) + md.Set(authMeta, fmt.Sprintf("%s %s", bearerAuth, tokens.AccessToken())) err = invoker(metadata.NewOutgoingContext(ctx, md), method, req, reply, cc, opts...) } diff --git a/internal/infrastructure/rpc/interceptor/logger.go b/internal/infrastructure/rpc/interceptor/logger.go index 4165651..2406834 100644 --- a/internal/infrastructure/rpc/interceptor/logger.go +++ b/internal/infrastructure/rpc/interceptor/logger.go @@ -10,6 +10,7 @@ import ( "github.com/bjlag/go-keeper/internal/infrastructure/logger" ) +// LoggerServerInterceptor интерцептор логирующий запросы на стороне сервера. func LoggerServerInterceptor(log *zap.Logger) grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { hLog := log @@ -28,6 +29,7 @@ func LoggerServerInterceptor(log *zap.Logger) grpc.UnaryServerInterceptor { } } +// LoggerClientInterceptor интерцептор логирующий запросы на стороне клиента. func LoggerClientInterceptor(log *zap.Logger) grpc.UnaryClientInterceptor { return func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { err := invoker(ctx, method, req, reply, cc, opts...) diff --git a/internal/infrastructure/rpc/server/method.go b/internal/infrastructure/rpc/server/method.go index 472ad43..a695431 100644 --- a/internal/infrastructure/rpc/server/method.go +++ b/internal/infrastructure/rpc/server/method.go @@ -10,6 +10,7 @@ import ( pb "github.com/bjlag/go-keeper/internal/generated/rpc" ) +// Зарегистрированные методы. const ( RegisterMethod = "Register" LoginMethod = "Login" @@ -21,6 +22,7 @@ const ( DeleteItemMethod = "DeleteItem" ) +// Register регистрация пользователя. func (s RPCServer) Register(ctx context.Context, in *pb.RegisterIn) (*pb.RegisterOut, error) { handler, err := s.getHandler(RegisterMethod) if err != nil { @@ -35,6 +37,7 @@ func (s RPCServer) Register(ctx context.Context, in *pb.RegisterIn) (*pb.Registe return h(ctx, in) } +// Login аутентификация пользователя. func (s RPCServer) Login(ctx context.Context, in *pb.LoginIn) (*pb.LoginOut, error) { handler, err := s.getHandler(LoginMethod) if err != nil { @@ -49,6 +52,7 @@ func (s RPCServer) Login(ctx context.Context, in *pb.LoginIn) (*pb.LoginOut, err return h(ctx, in) } +// RefreshTokens обновление токенов. func (s RPCServer) RefreshTokens(ctx context.Context, in *pb.RefreshTokensIn) (*pb.RefreshTokensOut, error) { handler, err := s.getHandler(RefreshTokensMethod) if err != nil { @@ -63,6 +67,7 @@ func (s RPCServer) RefreshTokens(ctx context.Context, in *pb.RefreshTokensIn) (* return h(ctx, in) } +// GetByGuid получение элемента по его GUID. func (s RPCServer) GetByGuid(ctx context.Context, in *pb.GetByGuidIn) (*pb.GetByGuidOut, error) { //nolint:revive handler, err := s.getHandler(GetByGUIDMethod) if err != nil { @@ -77,6 +82,7 @@ func (s RPCServer) GetByGuid(ctx context.Context, in *pb.GetByGuidIn) (*pb.GetBy return h(ctx, in) } +// GetAllItems получение всех элементов. func (s RPCServer) GetAllItems(ctx context.Context, in *pb.GetAllItemsIn) (*pb.GetAllItemsOut, error) { handler, err := s.getHandler(GetAllItemsMethod) if err != nil { @@ -91,6 +97,7 @@ func (s RPCServer) GetAllItems(ctx context.Context, in *pb.GetAllItemsIn) (*pb.G return h(ctx, in) } +// CreateItem создание элемента. func (s RPCServer) CreateItem(ctx context.Context, in *pb.CreateItemIn) (*emptypb.Empty, error) { handler, err := s.getHandler(CreateItemMethod) if err != nil { @@ -105,6 +112,7 @@ func (s RPCServer) CreateItem(ctx context.Context, in *pb.CreateItemIn) (*emptyp return h(ctx, in) } +// UpdateItem обновление элемента. func (s RPCServer) UpdateItem(ctx context.Context, in *pb.UpdateItemIn) (*pb.UpdateItemOut, error) { handler, err := s.getHandler(UpdateItemMethod) if err != nil { @@ -119,6 +127,7 @@ func (s RPCServer) UpdateItem(ctx context.Context, in *pb.UpdateItemIn) (*pb.Upd return h(ctx, in) } +// DeleteItem удаление элемента. func (s RPCServer) DeleteItem(ctx context.Context, in *pb.DeleteItemIn) (*emptypb.Empty, error) { handler, err := s.getHandler(DeleteItemMethod) if err != nil { diff --git a/internal/infrastructure/rpc/server/option.go b/internal/infrastructure/rpc/server/option.go index 998cf7d..dbf9db8 100644 --- a/internal/infrastructure/rpc/server/option.go +++ b/internal/infrastructure/rpc/server/option.go @@ -6,8 +6,10 @@ import ( "github.com/bjlag/go-keeper/internal/infrastructure/auth" ) +// Option тип параметра сервера. type Option func(*RPCServer) +// WithAddress передача адреса сервера. func WithAddress(host string, port int) Option { return func(s *RPCServer) { s.host = host @@ -15,18 +17,21 @@ func WithAddress(host string, port int) Option { } } +// WithLogger передача логгера. func WithLogger(logger *zap.Logger) Option { return func(s *RPCServer) { s.log = logger } } +// WithJWT передача сервиса для работы с JWT токенами. func WithJWT(jwt *auth.JWT) Option { return func(s *RPCServer) { s.jwt = jwt } } +// WithHandler регистрация обработчика запроса по указанному имени. func WithHandler(method string, handler any) Option { return func(s *RPCServer) { s.handlers[method] = handler diff --git a/internal/infrastructure/rpc/server/server.go b/internal/infrastructure/rpc/server/server.go index 40f3b64..ca0b1ba 100644 --- a/internal/infrastructure/rpc/server/server.go +++ b/internal/infrastructure/rpc/server/server.go @@ -1,3 +1,4 @@ +// Package server отвечает за работу gRPC сервера. package server import ( diff --git a/internal/infrastructure/store/client/item/store.go b/internal/infrastructure/store/client/item/store.go index 57fb915..879f3bb 100644 --- a/internal/infrastructure/store/client/item/store.go +++ b/internal/infrastructure/store/client/item/store.go @@ -1,3 +1,4 @@ +// Package item отвечает за работу с элементами в базе данных на стороне клиента. package item import ( @@ -25,6 +26,7 @@ func NewStore(db *sqlx.DB) *Store { } } +// SaveItem сохраняет переданный элемент в базе. func (s *Store) SaveItem(ctx context.Context, item model.Item) error { const op = prefixOp + "SaveItem" @@ -50,6 +52,7 @@ func (s *Store) SaveItem(ctx context.Context, item model.Item) error { return nil } +// CreateItem создает переданную модель элемента в базе. func (s *Store) CreateItem(ctx context.Context, item model.Item) error { const op = prefixOp + "CreateItem" @@ -86,6 +89,7 @@ func (s *Store) CreateItem(ctx context.Context, item model.Item) error { return nil } +// DeleteItem удаляет элемент с переданным GUID из базы. func (s *Store) DeleteItem(ctx context.Context, guid uuid.UUID) error { const op = prefixOp + "DeleteItem" @@ -101,6 +105,7 @@ func (s *Store) DeleteItem(ctx context.Context, guid uuid.UUID) error { return nil } +// SaveItems сохраняет несколько элементов. func (s *Store) SaveItems(ctx context.Context, items []model.RawItem) error { // todo пределать на модель model.Item const op = prefixOp + "SaveItems" @@ -138,6 +143,7 @@ func (s *Store) SaveItems(ctx context.Context, items []model.RawItem) error { return nil } +// ItemsByCategory получает элементы указанной категории. func (s *Store) ItemsByCategory(ctx context.Context, category model.Category) ([]model.RawItem, error) { const op = prefixOp + "Passwords" diff --git a/internal/infrastructure/store/client/option/store.go b/internal/infrastructure/store/client/option/store.go index 5919747..adcd9bf 100644 --- a/internal/infrastructure/store/client/option/store.go +++ b/internal/infrastructure/store/client/option/store.go @@ -1,3 +1,4 @@ +// Package option отвечает за работу с опциями в базе данных на стороне клиента. package option import ( @@ -23,6 +24,8 @@ func NewStore(db *sqlx.DB) *Store { } } +// SaveOption сохраняет переданную модель опции в базе данных. +// Если элемента в базе нет, то создает. func (s *Store) SaveOption(ctx context.Context, option model.Option) error { const op = prefixOp + "SaveOption" @@ -42,6 +45,7 @@ func (s *Store) SaveOption(ctx context.Context, option model.Option) error { return nil } +// OptionBySlug получает опцию по ее слагу. func (s *Store) OptionBySlug(ctx context.Context, slug string) (*model.Option, error) { const op = prefixOp + "OptionBySlug" diff --git a/internal/infrastructure/store/client/token/store.go b/internal/infrastructure/store/client/token/store.go index 3f318e3..7b436f4 100644 --- a/internal/infrastructure/store/client/token/store.go +++ b/internal/infrastructure/store/client/token/store.go @@ -1,3 +1,4 @@ +// Package token in-memory хранилище токенов и мастер ключа на стороне клиента. package token type tokens struct { @@ -14,23 +15,28 @@ func NewStore() *Store { return &Store{} } +// SaveTokens запомнить переданные токены. func (s *Store) SaveTokens(accessToken, refreshToken string) { s.tokens.accessToken = accessToken s.tokens.refreshToken = refreshToken } +// SaveMasterKey запомнить мастер ключ. func (s *Store) SaveMasterKey(key []byte) { s.tokens.masterKey = key } +// AccessToken получить access токен. func (s *Store) AccessToken() string { return s.tokens.accessToken } +// RefreshToken получить refresh токен. func (s *Store) RefreshToken() string { return s.tokens.refreshToken } +// MasterKey получить мастер ключ. func (s *Store) MasterKey() []byte { return s.tokens.masterKey } diff --git a/internal/infrastructure/store/server/item/store.go b/internal/infrastructure/store/server/item/store.go index 12d6cb4..b405367 100644 --- a/internal/infrastructure/store/server/item/store.go +++ b/internal/infrastructure/store/server/item/store.go @@ -1,3 +1,4 @@ +// Package item отвечает за работу с элементами в базе данных на стороне сервера. package item import ( @@ -13,8 +14,10 @@ import ( ) var ( + // ErrNotAffectedRows ошибка в случае, если при обновлении не было задето ни одной записи. ErrNotAffectedRows = errors.New("not affected") - ErrNotFound = errors.New("not found") + // ErrNotFound ошибка в случае, если не было найдено ни одной записи. + ErrNotFound = errors.New("not found") ) type Store struct { @@ -27,6 +30,7 @@ func NewStore(db *sqlx.DB) *Store { } } +// GetAllByUser получить все элементы пользователя. func (s *Store) GetAllByUser(ctx context.Context, userGUID uuid.UUID, limit, offset uint32) ([]model.Item, error) { const op = "store.item.GetAllByUser" @@ -47,6 +51,7 @@ func (s *Store) GetAllByUser(ctx context.Context, userGUID uuid.UUID, limit, off return convertToModels(rows), nil } +// ItemByGUID получить элемент по его GUID. func (s *Store) ItemByGUID(ctx context.Context, guid uuid.UUID) (*model.Item, error) { const op = "store.item.ItemByGUID" @@ -69,6 +74,7 @@ func (s *Store) ItemByGUID(ctx context.Context, guid uuid.UUID) (*model.Item, er return &result, nil } +// UserItemByGUID получить элемент пользователя по его GUID. func (s *Store) UserItemByGUID(ctx context.Context, userGUID, itemGUID uuid.UUID) (*model.Item, error) { const op = "store.item.UserItemByGUID" @@ -91,6 +97,7 @@ func (s *Store) UserItemByGUID(ctx context.Context, userGUID, itemGUID uuid.UUID return &result, nil } +// Create создать элемент по переданной модели. func (s *Store) Create(ctx context.Context, item model.Item) error { const op = "store.item.Create" @@ -115,6 +122,7 @@ func (s *Store) Create(ctx context.Context, item model.Item) error { return nil } +// Update обновить элемент пользователя. func (s *Store) Update(ctx context.Context, guid uuid.UUID, userGUID uuid.UUID, data model.UpdatedItem) error { const op = "store.item.Update" @@ -147,6 +155,7 @@ func (s *Store) Update(ctx context.Context, guid uuid.UUID, userGUID uuid.UUID, return nil } +// Delete удалить элемент пользователя. func (s *Store) Delete(ctx context.Context, guid uuid.UUID, userGUID uuid.UUID) error { const op = "store.item.Delete" diff --git a/internal/infrastructure/store/server/user/model.go b/internal/infrastructure/store/server/user/model.go index 60a5891..1007255 100644 --- a/internal/infrastructure/store/server/user/model.go +++ b/internal/infrastructure/store/server/user/model.go @@ -16,7 +16,7 @@ type row struct { UpdatedAt time.Time `db:"updated_at"` } -func convertToRow(m *model.User) row { +func convertToRow(m model.User) row { return row{ GUID: m.GUID, Email: m.Email, diff --git a/internal/infrastructure/store/server/user/store.go b/internal/infrastructure/store/server/user/store.go index 1dc822e..98e7a48 100644 --- a/internal/infrastructure/store/server/user/store.go +++ b/internal/infrastructure/store/server/user/store.go @@ -1,3 +1,4 @@ +// Package user отвечает за работу с пользователями на стороне сервера. package user import ( @@ -12,6 +13,7 @@ import ( model "github.com/bjlag/go-keeper/internal/domain/server/user" ) +// ErrNotFound ошибка в случае, если пользователь не найден. var ErrNotFound = errors.New("user not found") type Store struct { @@ -24,6 +26,7 @@ func NewStore(db *sqlx.DB) *Store { } } +// GetByGUID получить пользователя по его GUID. func (s Store) GetByGUID(ctx context.Context, guid uuid.UUID) (*model.User, error) { const op = "store.user.GetByGUID" @@ -44,6 +47,7 @@ func (s Store) GetByGUID(ctx context.Context, guid uuid.UUID) (*model.User, erro return user.convertToModel(), nil } +// GetByEmail получить пользователя по его емейлу. func (s Store) GetByEmail(ctx context.Context, email string) (*model.User, error) { const op = "store.user.GetByEmail" @@ -64,7 +68,8 @@ func (s Store) GetByEmail(ctx context.Context, email string) (*model.User, error return user.convertToModel(), nil } -func (s Store) Add(ctx context.Context, user *model.User) error { +// Add добавить пользователя. +func (s Store) Add(ctx context.Context, user model.User) error { const op = "store.user.Add" query := `INSERT INTO users (guid, email, password_hash) VALUES (:guid, :email, :password_hash)` diff --git a/internal/infrastructure/validator/email.go b/internal/infrastructure/validator/email.go index e645f67..b34a16f 100644 --- a/internal/infrastructure/validator/email.go +++ b/internal/infrastructure/validator/email.go @@ -10,6 +10,7 @@ var ( regexEmailPattern *regexp.Regexp ) +// ValidateEmail валидация емейла. func ValidateEmail(email string) bool { once.Do(func() { regexEmailPattern = regexp.MustCompile("^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$") diff --git a/internal/infrastructure/validator/password.go b/internal/infrastructure/validator/password.go index bc30a08..4cfefa3 100644 --- a/internal/infrastructure/validator/password.go +++ b/internal/infrastructure/validator/password.go @@ -2,6 +2,7 @@ package validator const minPasswordLength = 8 +// ValidatePassword валидация пароля. func ValidatePassword(password string) bool { return !(len(password) < minPasswordLength) } diff --git a/internal/rpc/create_item/handler.go b/internal/rpc/create_item/handler.go index a244e2e..718344b 100644 --- a/internal/rpc/create_item/handler.go +++ b/internal/rpc/create_item/handler.go @@ -25,6 +25,7 @@ func New(usecase usecase) *Handler { } } +// Handle метод создания элемента для аутентифицированного пользователя. func (h *Handler) Handle(ctx context.Context, in *pb.CreateItemIn) (*emptypb.Empty, error) { log := logger.FromCtx(ctx) diff --git a/internal/rpc/delete_item/handler.go b/internal/rpc/delete_item/handler.go index a684ec7..a32722f 100644 --- a/internal/rpc/delete_item/handler.go +++ b/internal/rpc/delete_item/handler.go @@ -26,6 +26,7 @@ func New(usecase usecase) *Handler { } } +// Handle метод удаления элемента у аутентифицированного пользователя. func (h *Handler) Handle(ctx context.Context, in *pb.DeleteItemIn) (*emptypb.Empty, error) { log := logger.FromCtx(ctx) diff --git a/internal/rpc/get_all_items/handler.go b/internal/rpc/get_all_items/handler.go index 2455efd..515957a 100644 --- a/internal/rpc/get_all_items/handler.go +++ b/internal/rpc/get_all_items/handler.go @@ -31,6 +31,7 @@ func New(usecase usecase) *Handler { } } +// Handle метод получения всех элементов аутентифицированного пользователя. func (h *Handler) Handle(ctx context.Context, in *pb.GetAllItemsIn) (*pb.GetAllItemsOut, error) { log := logger.FromCtx(ctx) diff --git a/internal/rpc/get_by_guid/handler.go b/internal/rpc/get_by_guid/handler.go index 53df500..8c23846 100644 --- a/internal/rpc/get_by_guid/handler.go +++ b/internal/rpc/get_by_guid/handler.go @@ -26,6 +26,7 @@ func New(usecase usecase) *Handler { } } +// Handle метод получение элемента по его GUID аутентифицированного пользователя. func (h *Handler) Handle(ctx context.Context, in *pb.GetByGuidIn) (*pb.GetByGuidOut, error) { log := logger.FromCtx(ctx) diff --git a/internal/rpc/login/handler.go b/internal/rpc/login/handler.go index dec416d..f5f6122 100644 --- a/internal/rpc/login/handler.go +++ b/internal/rpc/login/handler.go @@ -24,6 +24,7 @@ func New(usecase usecase) *Handler { } } +// Handle метод для аутентификации пользователя. func (h *Handler) Handle(ctx context.Context, in *pb.LoginIn) (*pb.LoginOut, error) { log := logger.FromCtx(ctx) diff --git a/internal/rpc/refresh_tokens/handler.go b/internal/rpc/refresh_tokens/handler.go index 8c199e3..38615d6 100644 --- a/internal/rpc/refresh_tokens/handler.go +++ b/internal/rpc/refresh_tokens/handler.go @@ -25,6 +25,7 @@ func New(usecase usecase) *Handler { } } +// Handle метод для обновления токенов по refresh токену. func (h *Handler) Handle(ctx context.Context, in *pb.RefreshTokensIn) (*pb.RefreshTokensOut, error) { log := logger.FromCtx(ctx) diff --git a/internal/rpc/register/handler.go b/internal/rpc/register/handler.go index 9292fab..990dc59 100644 --- a/internal/rpc/register/handler.go +++ b/internal/rpc/register/handler.go @@ -24,6 +24,7 @@ func New(usecase usecase) *Handler { } } +// Handle метод для регистрации пользователя. func (h *Handler) Handle(ctx context.Context, in *pb.RegisterIn) (*pb.RegisterOut, error) { log := logger.FromCtx(ctx) diff --git a/internal/rpc/update_item/handler.go b/internal/rpc/update_item/handler.go index e8709cc..093d67a 100644 --- a/internal/rpc/update_item/handler.go +++ b/internal/rpc/update_item/handler.go @@ -25,6 +25,7 @@ func New(usecase usecase) *Handler { } } +// Handle метод для обновления элемента аутентифицированного пользователя. func (h *Handler) Handle(ctx context.Context, in *pb.UpdateItemIn) (*pb.UpdateItemOut, error) { log := logger.FromCtx(ctx) diff --git a/internal/usecase/client/item/create/usecase.go b/internal/usecase/client/item/create/usecase.go index c22f32f..b83f807 100644 --- a/internal/usecase/client/item/create/usecase.go +++ b/internal/usecase/client/item/create/usecase.go @@ -1,3 +1,4 @@ +// Package create отвечает за сценарий создания элемента на стороне клиента. package create import ( diff --git a/internal/usecase/client/item/edit/usecase.go b/internal/usecase/client/item/edit/usecase.go index bdaf6af..de99c04 100644 --- a/internal/usecase/client/item/edit/usecase.go +++ b/internal/usecase/client/item/edit/usecase.go @@ -1,3 +1,4 @@ +// Package edit отвечает за сценарий изменения элемента на стороне клиента. package edit import ( diff --git a/internal/usecase/client/item/remove/usecase.go b/internal/usecase/client/item/remove/usecase.go index 8e96ac0..279e726 100644 --- a/internal/usecase/client/item/remove/usecase.go +++ b/internal/usecase/client/item/remove/usecase.go @@ -1,3 +1,4 @@ +// Package remove отвечает за сценарий удаления элемента на стороне клиента. package remove import ( diff --git a/internal/usecase/client/item/sync/usecase.go b/internal/usecase/client/item/sync/usecase.go index af3ad8d..65012c8 100644 --- a/internal/usecase/client/item/sync/usecase.go +++ b/internal/usecase/client/item/sync/usecase.go @@ -1,3 +1,4 @@ +// Package sync отвечает за сценарий синхронизации элемента с сервером на стороне клиента. package sync import ( diff --git a/internal/usecase/client/login/usecase.go b/internal/usecase/client/login/usecase.go index 02d1e36..946aad9 100644 --- a/internal/usecase/client/login/usecase.go +++ b/internal/usecase/client/login/usecase.go @@ -1,3 +1,4 @@ +// Package login отвечает за сценарий аутентификации пользователя на стороне клиента. package login import ( diff --git a/internal/usecase/client/master_key/usecase.go b/internal/usecase/client/master_key/usecase.go index 2e3c86e..3404d07 100644 --- a/internal/usecase/client/master_key/usecase.go +++ b/internal/usecase/client/master_key/usecase.go @@ -1,3 +1,4 @@ +// Package master_key отвечает за сценарий генерации и сохранения мастер ключа на стороне клиента. package master_key import ( diff --git a/internal/usecase/client/register/usecase.go b/internal/usecase/client/register/usecase.go index ff3de05..c2547cc 100644 --- a/internal/usecase/client/register/usecase.go +++ b/internal/usecase/client/register/usecase.go @@ -1,3 +1,4 @@ +// Package register отвечает за сценарий регистрации пользователя на стороне клиента. package register import ( diff --git a/internal/usecase/client/sync/usecase.go b/internal/usecase/client/sync/usecase.go index d4349f4..7b16b8b 100644 --- a/internal/usecase/client/sync/usecase.go +++ b/internal/usecase/client/sync/usecase.go @@ -1,3 +1,4 @@ +// Package sync отвечает за сценарий синхронизации клиента с сервером. package sync import ( diff --git a/internal/usecase/server/item/create/usecase.go b/internal/usecase/server/item/create/usecase.go index 15cc30a..f5efdf1 100644 --- a/internal/usecase/server/item/create/usecase.go +++ b/internal/usecase/server/item/create/usecase.go @@ -1,3 +1,4 @@ +// Package create отвечает за сценарий создания элемента на стороне сервера. package create import ( diff --git a/internal/usecase/server/item/get_all/usecase.go b/internal/usecase/server/item/get_all/usecase.go index fe48491..d719cd4 100644 --- a/internal/usecase/server/item/get_all/usecase.go +++ b/internal/usecase/server/item/get_all/usecase.go @@ -1,3 +1,4 @@ +// Package get_all отвечает за получение всех элементов на стороне сервера. package get_all import ( diff --git a/internal/usecase/server/item/get_by_guid/usecase.go b/internal/usecase/server/item/get_by_guid/usecase.go index a2417d6..f1e575c 100644 --- a/internal/usecase/server/item/get_by_guid/usecase.go +++ b/internal/usecase/server/item/get_by_guid/usecase.go @@ -1,3 +1,4 @@ +// Package get_by_guid отвечает за получение элемента на стороне сервера. package get_by_guid import ( diff --git a/internal/usecase/server/item/remove/usecase.go b/internal/usecase/server/item/remove/usecase.go index 859e413..06e5fd1 100644 --- a/internal/usecase/server/item/remove/usecase.go +++ b/internal/usecase/server/item/remove/usecase.go @@ -1,3 +1,4 @@ +// Package remove отвечает за удаление элемента на стороне сервера. package remove import ( diff --git a/internal/usecase/server/item/update/usecase.go b/internal/usecase/server/item/update/usecase.go index f296546..4c250ee 100644 --- a/internal/usecase/server/item/update/usecase.go +++ b/internal/usecase/server/item/update/usecase.go @@ -1,3 +1,4 @@ +// Package update отвечает за обновление элемента на стороне сервера. package update import ( diff --git a/internal/usecase/server/user/login/usecase.go b/internal/usecase/server/user/login/usecase.go index 0979145..5d3e63e 100644 --- a/internal/usecase/server/user/login/usecase.go +++ b/internal/usecase/server/user/login/usecase.go @@ -1,3 +1,4 @@ +// Package login отвечает за аутентификацию пользователя на стороне сервера. package login import ( diff --git a/internal/usecase/server/user/refresh_tokens/usecase.go b/internal/usecase/server/user/refresh_tokens/usecase.go index 8849ef8..1146c82 100644 --- a/internal/usecase/server/user/refresh_tokens/usecase.go +++ b/internal/usecase/server/user/refresh_tokens/usecase.go @@ -1,3 +1,4 @@ +// Package refresh_tokens отвечает за обновление токенов на стороне сервера. package refresh_tokens import ( diff --git a/internal/usecase/server/user/register/contract.go b/internal/usecase/server/user/register/contract.go index 168915e..ac20c54 100644 --- a/internal/usecase/server/user/register/contract.go +++ b/internal/usecase/server/user/register/contract.go @@ -8,7 +8,7 @@ import ( type userStore interface { GetByEmail(ctx context.Context, email string) (*model.User, error) - Add(ctx context.Context, user *model.User) error + Add(ctx context.Context, user model.User) error } type tokenGenerator interface { diff --git a/internal/usecase/server/user/register/usecase.go b/internal/usecase/server/user/register/usecase.go index ea76293..5870feb 100644 --- a/internal/usecase/server/user/register/usecase.go +++ b/internal/usecase/server/user/register/usecase.go @@ -1,3 +1,4 @@ +// Package register отвечает за регистрацию пользователя на стороне сервера. package register import ( @@ -43,7 +44,7 @@ func (u Usecase) Do(ctx context.Context, data Data) (*Result, error) { return nil, fmt.Errorf("%s: %w", op, err) } - user := &model.User{ + user := model.User{ GUID: uuid.New(), Email: data.Email, PasswordHash: string(passwordHash), From 5def57e308b8b139accc98e81f386ab9af50d255 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Sun, 16 Mar 2025 17:18:23 +0300 Subject: [PATCH 67/94] client: view build version and data --- .gitignore | 2 +- Makefile | 8 +++++--- cmd/client/main.go | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 7dba713..cd4e229 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,7 @@ # Binaries cmd/client/main -cmd/client/client +cmd/client/client-osx cmd/server/main cmd/server/server cmd/migrator/main diff --git a/Makefile b/Makefile index 5878462..a61ceaa 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,5 @@ -BUILD_VERSION = "v1.0.0" -BUILD_DATE = $(shell date +'%Y/%m/%d %H:%M:%S') -BUILD_COMMIT = $(shell git rev-parse --short HEAD) +BUILD_VERSION ?= "v1.0.0" +BUILD_DATE ?= $(shell date +'%Y/%m/%d %H:%M:%S') NAME = $(shell basename "$(PWD)") DIR = $(shell pwd) @@ -86,6 +85,9 @@ proto: doc: godoc -http=:8888 -play +build-client-osx: + go build -ldflags "-X main.buildVersion=$(BUILD_VERSION) -X 'main.buildDate=$(BUILD_DATE)'" -o ./cmd/client/client-osx ./cmd/client/. + .PHONY: help help: Makefile @echo diff --git a/cmd/client/main.go b/cmd/client/main.go index d3e1e22..b8dd390 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -2,12 +2,16 @@ // // Конфигурация указывается через флаг -c, описывается в YAML файле: // - пример ./config/client.yaml.dist +// +// Флаг -version выведет текущую версию и дату сборки. package main import ( "context" "flag" + "fmt" logNative "log" + "os" "os/signal" "syscall" @@ -23,6 +27,12 @@ const ( configPathDefault = "./config/client.yaml" ) +var ( + viewVersion bool + buildVersion string + buildDate string +) + func main() { defer func() { if r := recover(); r != nil { @@ -33,8 +43,14 @@ func main() { var configPath string flag.StringVar(&configPath, "c", configPathDefault, "Path to config file") + flag.BoolVar(&viewVersion, "version", false, "View build version and data") flag.Parse() + if viewVersion { + fmt.Printf("Version: %s\nBuild: %s\n", buildVersion, buildDate) + os.Exit(0) + } + var cfg client.Config if err := cleanenv.ReadConfig(configPath, &cfg); err != nil { panic(err) From 593ddded9e8bdefa921f12dc352d0fa5ecd2178d Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Sun, 16 Mar 2025 17:53:32 +0300 Subject: [PATCH 68/94] client: building --- .gitignore | 4 +++- Makefile | 11 +++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index cd4e229..054effb 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,9 @@ # Binaries cmd/client/main -cmd/client/client-osx +cmd/client/client_darwin_amd64 +cmd/client/client_linux_amd64 +cmd/client/client_windows_amd64 cmd/server/main cmd/server/server cmd/migrator/main diff --git a/Makefile b/Makefile index a61ceaa..4dacf78 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,8 @@ NAME = $(shell basename "$(PWD)") DIR = $(shell pwd) DOCKER_FILE = "docker-compose.local.yaml" +.DEFAULT_GOAL := up + .PHONY: all all: help @@ -85,8 +87,13 @@ proto: doc: godoc -http=:8888 -play -build-client-osx: - go build -ldflags "-X main.buildVersion=$(BUILD_VERSION) -X 'main.buildDate=$(BUILD_DATE)'" -o ./cmd/client/client-osx ./cmd/client/. +build-client: + @echo " > Building client for OSX" + @GOOS=darwin GOARCH=amd64 go build -ldflags "-X main.buildVersion=$(BUILD_VERSION) -X 'main.buildDate=$(BUILD_DATE)'" -o ./cmd/client/client_darwin_amd64 ./cmd/client/. + @echo " > Building client for Linux" + @GOOS=linux GOARCH=amd64 go build -ldflags "-X main.buildVersion=$(BUILD_VERSION) -X 'main.buildDate=$(BUILD_DATE)'" -o ./cmd/client/client_linux_amd64 ./cmd/client/. + @echo " > Building client for Windows" + @GOOS=windows GOARCH=amd64 go build -ldflags "-X main.buildVersion=$(BUILD_VERSION) -X 'main.buildDate=$(BUILD_DATE)'" -o ./cmd/client/client_windows_amd64 ./cmd/client/. .PHONY: help help: Makefile From bffce1c3837c17d1fc9389b9a98c5c4807c4a1a1 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Sun, 16 Mar 2025 23:33:04 +0300 Subject: [PATCH 69/94] client: refactoring --- internal/cli/message/message.go | 84 ++++++++++++++++----------------- 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/internal/cli/message/message.go b/internal/cli/message/message.go index 4a8ef09..48fcafe 100644 --- a/internal/cli/message/message.go +++ b/internal/cli/message/message.go @@ -6,46 +6,44 @@ import ( "github.com/bjlag/go-keeper/internal/domain/client" ) -// OpenLoginMsg сообщение указывает, что надо открыть модель для аутентификации. -type OpenLoginMsg struct{} - -// OpenRegisterMsg сообщение указывает, что надо открыть модель для регистрации. -type OpenRegisterMsg struct { - // LoginModel содержит ссылку на модель аутентификации, чтобы на нее можно было вернуться - // в случае отказа от регистрации. - LoginModel tea.Model -} - -// SuccessMsg вспомогательное сообщение, что какое-то событие выполнилось успешно, например, аутентификация. -type SuccessMsg struct{} - -// List - -// OpenCategoriesMsg сообщение используется для открытия списка категорий. -type OpenCategoriesMsg struct{} - -// OpenItemsMsg сообщение используется для открытия списка элементов определенной категории. -type OpenItemsMsg struct { - // Category содержит категорию элементов, которые надо вывести. - Category client.Category -} - -// BackMsg используется для возврата в предыдущую модель, например, из элемента в список элементов. -type BackMsg struct { - // State состояние модели, в которое надо вернуться. - State int - // Item каким элементом надо заменить данные в модели, в которую возвращаемся. - Item *client.Item -} - -// Item - -// OpenItemMsg содержит информацию, какой элемент надо открыть. -type OpenItemMsg struct { - // BackModel модель, в которую надо вернуться, в случае отмены. - BackModel tea.Model - // BackState состояние, в которое надо вернуть модель, в случае отмены. - BackState int - // Item каким элементом надо заменить данные в модели, в которую возвращаемся. - Item *client.Item -} +type ( + // OpenLoginMsg сообщение указывает, что надо открыть модель для аутентификации. + OpenLoginMsg struct{} + + // OpenRegisterMsg сообщение указывает, что надо открыть модель для регистрации. + OpenRegisterMsg struct { + // LoginModel содержит ссылку на модель аутентификации, чтобы на нее можно было вернуться + // в случае отказа от регистрации. + LoginModel tea.Model + } + + // SuccessMsg вспомогательное сообщение, что какое-то событие выполнилось успешно, например, аутентификация. + SuccessMsg struct{} + + // OpenCategoriesMsg сообщение используется для открытия списка категорий. + OpenCategoriesMsg struct{} + + // OpenItemsMsg сообщение используется для открытия списка элементов определенной категории. + OpenItemsMsg struct { + // Category содержит категорию элементов, которые надо вывести. + Category client.Category + } + + // BackMsg используется для возврата в предыдущую модель, например, из элемента в список элементов. + BackMsg struct { + // State состояние модели, в которое надо вернуться. + State int + // Item каким элементом надо заменить данные в модели, в которую возвращаемся. + Item *client.Item + } + + // OpenItemMsg содержит информацию, какой элемент надо открыть. + OpenItemMsg struct { + // BackModel модель, в которую надо вернуться, в случае отмены. + BackModel tea.Model + // BackState состояние, в которое надо вернуть модель, в случае отмены. + BackState int + // Item каким элементом надо заменить данные в модели, в которую возвращаемся. + Item *client.Item + } +) From 5e8e1033c9daf79e29fe33e5843a72279eab2176 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Mon, 17 Mar 2025 01:18:16 +0300 Subject: [PATCH 70/94] client: tests --- internal/app/server/app.go | 8 +- internal/infrastructure/rpc/server/option.go | 8 ++ internal/infrastructure/rpc/server/server.go | 8 +- internal/rpc/login/handler_test.go | 81 ++++++++++++++++++++ 4 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 internal/rpc/login/handler_test.go diff --git a/internal/app/server/app.go b/internal/app/server/app.go index bcd74d7..eb48cca 100644 --- a/internal/app/server/app.go +++ b/internal/app/server/app.go @@ -4,6 +4,7 @@ package server import ( "context" "fmt" + "net" "go.uber.org/zap" @@ -70,8 +71,13 @@ func (a *App) Run(ctx context.Context) error { ucGetByGUID := get_by_guid.NewUsecase(dataStore) ucGetAllData := get_all.NewUsecase(dataStore) + listen, err := net.Listen("tcp", fmt.Sprintf("%s:%d", a.cfg.Address.Host, a.cfg.Address.Port)) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + s := server.NewRPCServer( - server.WithAddress(a.cfg.Address.Host, a.cfg.Address.Port), + server.WithListener(listen), server.WithJWT(jwt), server.WithLogger(a.log), diff --git a/internal/infrastructure/rpc/server/option.go b/internal/infrastructure/rpc/server/option.go index dbf9db8..b0fad51 100644 --- a/internal/infrastructure/rpc/server/option.go +++ b/internal/infrastructure/rpc/server/option.go @@ -2,6 +2,7 @@ package server import ( "go.uber.org/zap" + "net" "github.com/bjlag/go-keeper/internal/infrastructure/auth" ) @@ -17,6 +18,13 @@ func WithAddress(host string, port int) Option { } } +// WithListener передача сетевого прослушивателя сервера. +func WithListener(listener net.Listener) Option { + return func(s *RPCServer) { + s.listener = listener + } +} + // WithLogger передача логгера. func WithLogger(logger *zap.Logger) Option { return func(s *RPCServer) { diff --git a/internal/infrastructure/rpc/server/server.go b/internal/infrastructure/rpc/server/server.go index ca0b1ba..88ef714 100644 --- a/internal/infrastructure/rpc/server/server.go +++ b/internal/infrastructure/rpc/server/server.go @@ -20,6 +20,7 @@ type RPCServer struct { host string port int + listener net.Listener handlers map[string]any jwt *auth.JWT log *zap.Logger @@ -45,11 +46,6 @@ func (s RPCServer) Start(ctx context.Context) error { zap.Int("port", s.port), ) - listen, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.host, s.port)) - if err != nil { - return fmt.Errorf("%s: %w", op, err) - } - grpcServer := grpc.NewServer( grpc.ChainUnaryInterceptor( interceptor.LoggerServerInterceptor(s.log), @@ -61,7 +57,7 @@ func (s RPCServer) Start(ctx context.Context) error { g, gCtx := errgroup.WithContext(ctx) g.Go(func() error { - return grpcServer.Serve(listen) + return grpcServer.Serve(s.listener) }) g.Go(func() error { <-gCtx.Done() diff --git a/internal/rpc/login/handler_test.go b/internal/rpc/login/handler_test.go new file mode 100644 index 0000000..18c1706 --- /dev/null +++ b/internal/rpc/login/handler_test.go @@ -0,0 +1,81 @@ +package login_test + +import ( + "context" + "github.com/bjlag/go-keeper/internal/generated/rpc" + "github.com/bjlag/go-keeper/internal/infrastructure/auth" + "github.com/bjlag/go-keeper/internal/infrastructure/db/pg" + "github.com/bjlag/go-keeper/internal/infrastructure/logger" + server2 "github.com/bjlag/go-keeper/internal/infrastructure/rpc/server" + "github.com/bjlag/go-keeper/internal/infrastructure/store/server/user" + rpcLogin "github.com/bjlag/go-keeper/internal/rpc/login" + "github.com/bjlag/go-keeper/internal/usecase/server/user/login" + "github.com/jmoiron/sqlx" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/test/bufconn" + "log" + "net" + "testing" + "time" +) + +func dialer(ctx context.Context, db *sqlx.DB) func(context.Context, string) (net.Conn, error) { + listener := bufconn.Listen(1204 * 1024) + + jwt := auth.NewJWT("secret", 15*time.Minute, 15*time.Minute) + + userStore := user.NewStore(db) + ucLogin := login.NewUsecase(userStore, jwt) + + server := server2.NewRPCServer( + server2.WithListener(listener), + server2.WithLogger(logger.Get("test")), + + server2.WithHandler(server2.LoginMethod, rpcLogin.New(ucLogin).Handle), + ) + + go func() { + if err := server.Start(ctx); err != nil { + log.Fatal(err) + } + }() + + return func(context.Context, string) (net.Conn, error) { + return listener.Dial() + } +} + +func TestHandler_Handle(t *testing.T) { + ctx := context.Background() + + db, err := pg.New(pg.GetDSN("localhost", "5444", "master", "postgres", "secret")).Connect() + if err != nil { + log.Fatal(err) + } + defer func() { + _ = db.Close() + }() + + conn, err := grpc.NewClient("passthrough://bufnet", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithContextDialer(dialer(ctx, db))) + if err != nil { + log.Fatal(err) + } + defer conn.Close() + + client := rpc.NewKeeperClient(conn) + + // + + t.Run("success", func(t *testing.T) { + out, err := client.Login(ctx, &rpc.LoginIn{ + Email: "test@test.com", + Password: "test", + }) + + if err != nil { + } + + out.GetAccessToken() + }) +} From f67f2dfefdec93b535cb4e8580338b43122168db Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Mon, 17 Mar 2025 17:25:53 +0300 Subject: [PATCH 71/94] migrator: refactoring --- cmd/migrator/main.go | 56 ++----------------- .../infrastructure}/migrator/config.go | 4 +- internal/infrastructure/migrator/migrator.go | 55 ++++++++++++++++++ 3 files changed, 63 insertions(+), 52 deletions(-) rename {cmd => internal/infrastructure}/migrator/config.go (95%) create mode 100644 internal/infrastructure/migrator/migrator.go diff --git a/cmd/migrator/main.go b/cmd/migrator/main.go index 3b8cc3c..decf585 100644 --- a/cmd/migrator/main.go +++ b/cmd/migrator/main.go @@ -9,14 +9,10 @@ package main import ( "errors" "flag" - "fmt" "io/fs" logNative "log" "github.com/golang-migrate/migrate/v4" - "github.com/golang-migrate/migrate/v4/database" - "github.com/golang-migrate/migrate/v4/database/pgx/v5" - "github.com/golang-migrate/migrate/v4/database/sqlite3" _ "github.com/golang-migrate/migrate/v4/source/file" "github.com/ilyakaznacheev/cleanenv" "github.com/jmoiron/sqlx" @@ -26,12 +22,7 @@ import ( "github.com/bjlag/go-keeper/internal/infrastructure/db/pg" "github.com/bjlag/go-keeper/internal/infrastructure/db/sqlite" "github.com/bjlag/go-keeper/internal/infrastructure/logger" -) - -// Константы содержат поддерживаемые базы данных. -const ( - typePG = "pg" - typeSqlite = "sqlite" + "github.com/bjlag/go-keeper/internal/infrastructure/migrator" ) func main() { @@ -46,7 +37,7 @@ func main() { flag.StringVar(&configPath, "c", "", "Path to config file") flag.Parse() - var cfg Config + var cfg migrator.Config if err := cleanenv.ReadConfig(configPath, &cfg); err != nil { panic(err) } @@ -57,7 +48,7 @@ func main() { }() log.Debug("Config loaded", zap.Any("config", cfg)) - log = log.With(zap.String("db_type", cfg.Database.Type)) + log = log.With(zap.Any("db_type", cfg.Database.Type)) var ( err error @@ -65,10 +56,10 @@ func main() { ) switch cfg.Database.Type { - case typePG: + case migrator.TypePG: dbConf := cfg.Database db, err = pg.New(pg.GetDSN(dbConf.Host, dbConf.Port, dbConf.Name, dbConf.User, dbConf.Password)).Connect() - case typeSqlite: + case migrator.TypeSqlite: db, err = sqlite.New("./client.db").Connect() } defer func() { @@ -80,7 +71,7 @@ func main() { panic(err) } - m, err := initMigrator(db, cfg) + m, err := migrator.Get(db, cfg.Database.Type, cfg.Database.Name, cfg.SourcePath, cfg.MigrationsTable) if err != nil { log.Error("Failed to initialize migrator", zap.Error(err)) panic(err) @@ -104,38 +95,3 @@ func main() { log.Info("Migrations applied") } - -func initMigrator(db *sqlx.DB, cfg Config) (*migrate.Migrate, error) { - const op = "initMigrator" - - driver, err := getDBDriver(db, cfg) - if err != nil { - return nil, fmt.Errorf("%s get driver: %w", op, err) - } - - m, err := migrate.NewWithDatabaseInstance( - fmt.Sprintf("file://%s", cfg.SourcePath), - cfg.Database.Name, - driver, - ) - if err != nil { - return nil, fmt.Errorf("%s create migrator instance: %w", op, err) - } - - return m, nil -} - -func getDBDriver(db *sqlx.DB, cfg Config) (database.Driver, error) { - switch cfg.Database.Type { - case typePG: - return pgx.WithInstance(db.DB, &pgx.Config{ - MigrationsTable: cfg.MigrationsTable, - }) - case typeSqlite: - return sqlite3.WithInstance(db.DB, &sqlite3.Config{ - MigrationsTable: cfg.MigrationsTable, - }) - default: - return nil, fmt.Errorf("uknown database type: %s", cfg.Database.Type) //nolint:err113 - } -} diff --git a/cmd/migrator/config.go b/internal/infrastructure/migrator/config.go similarity index 95% rename from cmd/migrator/config.go rename to internal/infrastructure/migrator/config.go index a941244..0c53904 100644 --- a/cmd/migrator/config.go +++ b/internal/infrastructure/migrator/config.go @@ -1,4 +1,4 @@ -package main +package migrator // Config хранит конфигурацию мигратора. type Config struct { @@ -12,7 +12,7 @@ type Config struct { // Database настройки подключения к базе данной, к которой применяются миграции. Database struct { // Type тип базы данных: pg - PostgreSQL, sqlite - SQLite. - Type string `yaml:"type" env:"DB_TYPE" env-default:"pg" env-description:"Database type" json:"type"` + Type DBType `yaml:"type" env:"DB_TYPE" env-default:"pg" env-description:"Database type" json:"type"` // Host хост базы. Host string `yaml:"host" env:"DB_HOST" env-description:"Database host" json:"host"` // Port порт базы. diff --git a/internal/infrastructure/migrator/migrator.go b/internal/infrastructure/migrator/migrator.go new file mode 100644 index 0000000..481df70 --- /dev/null +++ b/internal/infrastructure/migrator/migrator.go @@ -0,0 +1,55 @@ +package migrator + +import ( + "fmt" + + "github.com/golang-migrate/migrate/v4" + "github.com/golang-migrate/migrate/v4/database" + "github.com/golang-migrate/migrate/v4/database/pgx/v5" + "github.com/golang-migrate/migrate/v4/database/sqlite3" + "github.com/jmoiron/sqlx" +) + +type DBType string + +// Константы содержат поддерживаемые базы данных. +const ( + TypePG DBType = "pg" + TypeSqlite = "sqlite" +) + +// Get возвращает настроенный экземпляр мигратора. +func Get(db *sqlx.DB, dbType DBType, dbName, sourcePath, migrationsTable string) (*migrate.Migrate, error) { + const op = "migrator.Init" + + driver, err := getDBDriver(db, dbType, migrationsTable) + if err != nil { + return nil, fmt.Errorf("%s get driver: %w", op, err) + } + + m, err := migrate.NewWithDatabaseInstance( + fmt.Sprintf("file://%s", sourcePath), + dbName, + driver, + ) + if err != nil { + return nil, fmt.Errorf("%s create migrator instance: %w", op, err) + } + + return m, nil +} + +func getDBDriver(db *sqlx.DB, dbType DBType, migrationsTable string) (database.Driver, error) { + switch dbType { + case TypePG: + return pgx.WithInstance(db.DB, &pgx.Config{ + MigrationsTable: migrationsTable, + }) + case TypeSqlite: + return sqlite3.WithInstance(db.DB, &sqlite3.Config{ + MigrationsTable: migrationsTable, + }) + default: + return nil, fmt.Errorf("uknown database type: %s", dbType) //nolint:err113 + } +} From 16d676bff9dc3154f2c86fd4d0738cdd33cd1598 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Mon, 17 Mar 2025 18:19:29 +0300 Subject: [PATCH 72/94] tests: init db --- cmd/migrator/main.go | 2 - docker/docker-compose.local.yaml | 3 + go.mod | 55 ++++++- go.sum | 162 ++++++++++++++++--- internal/infrastructure/migrator/migrator.go | 4 +- internal/rpc/login/handler_test.go | 80 ++++++++- 6 files changed, 275 insertions(+), 31 deletions(-) diff --git a/cmd/migrator/main.go b/cmd/migrator/main.go index decf585..c2c3614 100644 --- a/cmd/migrator/main.go +++ b/cmd/migrator/main.go @@ -13,10 +13,8 @@ import ( logNative "log" "github.com/golang-migrate/migrate/v4" - _ "github.com/golang-migrate/migrate/v4/source/file" "github.com/ilyakaznacheev/cleanenv" "github.com/jmoiron/sqlx" - _ "github.com/mattn/go-sqlite3" "go.uber.org/zap" "github.com/bjlag/go-keeper/internal/infrastructure/db/pg" diff --git a/docker/docker-compose.local.yaml b/docker/docker-compose.local.yaml index 1060762..0feeeda 100644 --- a/docker/docker-compose.local.yaml +++ b/docker/docker-compose.local.yaml @@ -11,6 +11,9 @@ services: - "5444:5432" volumes: - pg:/var/lib/postgresql/data + healthcheck: + test: [ "CMD-SHELL", "pg_isready" ] + interval: 2s volumes: pg: diff --git a/go.mod b/go.mod index 9da4a2b..dac96c8 100644 --- a/go.mod +++ b/go.mod @@ -14,23 +14,41 @@ require ( github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.10.9 github.com/mattn/go-sqlite3 v1.14.24 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 + github.com/testcontainers/testcontainers-go v0.35.0 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.36.0 golang.org/x/sync v0.12.0 - google.golang.org/grpc v1.64.1 - google.golang.org/protobuf v1.34.2 + google.golang.org/grpc v1.71.0 + google.golang.org/protobuf v1.36.5 ) require ( + dario.cat/mergo v1.0.0 // indirect + github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/BurntSushi/toml v1.4.0 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/charmbracelet/x/ansi v0.8.0 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v27.2.0+incompatible // indirect + github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/gogo/protobuf v1.3.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 // indirect @@ -38,22 +56,49 @@ require ( github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/joho/godotenv v1.5.1 // indirect + github.com/klauspost/compress v1.17.4 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/sequential v0.5.0 // indirect + github.com/moby/sys/user v0.1.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/morikuni/aec v1.0.0 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/termenv v0.16.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/sahilm/fuzzy v0.1.1 // indirect + github.com/shirou/gopsutil/v3 v3.23.12 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/yusufpapurcu/wmi v1.2.3 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect + go.opentelemetry.io/otel v1.35.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect + go.opentelemetry.io/otel/metric v1.35.0 // indirect + go.opentelemetry.io/otel/sdk v1.35.0 // indirect + go.opentelemetry.io/otel/trace v1.35.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.33.0 // indirect + golang.org/x/net v0.35.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.23.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect gopkg.in/yaml.v3 v3.0.1 // indirect olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect ) diff --git a/go.sum b/go.sum index 91007af..d49b137 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,9 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= @@ -13,6 +17,8 @@ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= github.com/charmbracelet/bubbletea v1.3.3 h1:WpU6fCY0J2vDWM3zfS3vIDi/ULq3SYphZhkAGGvmEUY= @@ -23,6 +29,14 @@ github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2ll github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= +github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 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= @@ -42,10 +56,13 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -54,10 +71,17 @@ github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQg github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-migrate/migrate/v4 v4.18.2 h1:2VSCMz7x7mjyTXx3m2zPokOY82LTRgxK1yQYKo6wWQ8= github.com/golang-migrate/migrate/v4 v4.18.2/go.mod h1:2CM6tJvn2kqPXwnXO/d3rAQYiyoIm180VsO8PRX6Rpk= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M= 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= @@ -79,8 +103,12 @@ github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= @@ -89,6 +117,10 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= @@ -100,6 +132,14 @@ github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBW github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= +github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= @@ -118,26 +158,65 @@ 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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= +github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 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/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +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/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/testcontainers/testcontainers-go v0.35.0 h1:uADsZpTKFAtp8SLK+hMwSaa+X+JiERHtd4sQAFmXeMo= +github.com/testcontainers/testcontainers-go v0.35.0/go.mod h1:oEVBj5zrfJTrgjwONs1SsRbnBtH9OKl+IGl3UMcr2B4= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= +github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -146,29 +225,70 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +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-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +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.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 h1:mxSlqyb8ZAHsYDCfiXN1EDdNTdvjUJSLY+OnAUtYNYA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM= -google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= -google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= +google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0= +google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= +google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= +google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 h1:slmdOY3vp8a7KQbHkL+FLbvbkgMqmXojpFUO/jENuqQ= olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw= diff --git a/internal/infrastructure/migrator/migrator.go b/internal/infrastructure/migrator/migrator.go index 481df70..4a128f6 100644 --- a/internal/infrastructure/migrator/migrator.go +++ b/internal/infrastructure/migrator/migrator.go @@ -7,7 +7,9 @@ import ( "github.com/golang-migrate/migrate/v4/database" "github.com/golang-migrate/migrate/v4/database/pgx/v5" "github.com/golang-migrate/migrate/v4/database/sqlite3" + _ "github.com/golang-migrate/migrate/v4/source/file" "github.com/jmoiron/sqlx" + _ "github.com/mattn/go-sqlite3" ) type DBType string @@ -15,7 +17,7 @@ type DBType string // Константы содержат поддерживаемые базы данных. const ( TypePG DBType = "pg" - TypeSqlite = "sqlite" + TypeSqlite DBType = "sqlite" ) // Get возвращает настроенный экземпляр мигратора. diff --git a/internal/rpc/login/handler_test.go b/internal/rpc/login/handler_test.go index 18c1706..3599249 100644 --- a/internal/rpc/login/handler_test.go +++ b/internal/rpc/login/handler_test.go @@ -6,16 +6,22 @@ import ( "github.com/bjlag/go-keeper/internal/infrastructure/auth" "github.com/bjlag/go-keeper/internal/infrastructure/db/pg" "github.com/bjlag/go-keeper/internal/infrastructure/logger" + "github.com/bjlag/go-keeper/internal/infrastructure/migrator" server2 "github.com/bjlag/go-keeper/internal/infrastructure/rpc/server" "github.com/bjlag/go-keeper/internal/infrastructure/store/server/user" rpcLogin "github.com/bjlag/go-keeper/internal/rpc/login" "github.com/bjlag/go-keeper/internal/usecase/server/user/login" "github.com/jmoiron/sqlx" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/test/bufconn" "log" "net" + "os" + "path" + "runtime" "testing" "time" ) @@ -49,7 +55,42 @@ func dialer(ctx context.Context, db *sqlx.DB) func(context.Context, string) (net func TestHandler_Handle(t *testing.T) { ctx := context.Background() - db, err := pg.New(pg.GetDSN("localhost", "5444", "master", "postgres", "secret")).Connect() + // todo из конфига берем данные для базы + + req := testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + Env: map[string]string{ + "POSTGRES_USER": "postgres", + "POSTGRES_PASSWORD": "secret", + "POSTGRES_DB": "master", + }, + ExposedPorts: []string{"5432/tcp"}, + Image: "postgres:16.4-alpine3.20", + WaitingFor: wait.ForExec([]string{"pg_isready"}). + WithPollInterval(2 * time.Second). + WithExitCodeMatcher(func(exitCode int) bool { + return exitCode == 0 + }), + }, + Started: true, + } + + container, err := testcontainers.GenericContainer(ctx, req) + if err != nil { + log.Fatal(err) + } + + host, err := container.Host(ctx) + if err != nil { + log.Fatal(err) + } + + mappedPort, err := container.MappedPort(ctx, "5432") + if err != nil { + log.Fatal(err) + } + + db, err := pg.New(pg.GetDSN(host, mappedPort.Port(), "master", "postgres", "secret")).Connect() if err != nil { log.Fatal(err) } @@ -57,6 +98,36 @@ func TestHandler_Handle(t *testing.T) { _ = db.Close() }() + var cfg migrator.Config + cfg.Database.Type = "pg" + cfg.MigrationsTable = "migrations" + cfg.SourcePath = "./migrations/server" + cfg.Database.Name = "master" + + _, filename, _, _ := runtime.Caller(0) + baseDir := path.Join(path.Dir(filename), "..", "..", "..") + + err = os.Chdir(baseDir) + if err != nil { + log.Fatal(err) + } + + dir, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + + _ = dir + + m, err := migrator.Get(db, "pg", "master", "./migrations/server", "migrations") + if err != nil { + log.Fatal(err) + } + + if err := m.Up(); err != nil { + log.Fatal(err) + } + conn, err := grpc.NewClient("passthrough://bufnet", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithContextDialer(dialer(ctx, db))) if err != nil { log.Fatal(err) @@ -65,7 +136,12 @@ func TestHandler_Handle(t *testing.T) { client := rpc.NewKeeperClient(conn) - // + // todo выключение логгера + // todo конфиги для тестов + // todo тестовое приложение + // todo своя база + // todo фикстуры базы + // todo test suit, tear down t.Run("success", func(t *testing.T) { out, err := client.Login(ctx, &rpc.LoginIn{ From 2caee6c3ee2193a13c495e42da6339a8f1db5aee Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Mon, 17 Mar 2025 18:33:46 +0300 Subject: [PATCH 73/94] tests: fixtures --- go.mod | 39 +- go.sum | 1541 ++++++++++++++++++++++++++++ internal/rpc/login/handler_test.go | 34 +- test/fixture/users.yaml | 5 + 4 files changed, 1597 insertions(+), 22 deletions(-) create mode 100644 test/fixture/users.yaml diff --git a/go.mod b/go.mod index dac96c8..479be37 100644 --- a/go.mod +++ b/go.mod @@ -24,16 +24,29 @@ require ( ) require ( + cel.dev/expr v0.22.0 // indirect + cloud.google.com/go v0.119.0 // indirect + cloud.google.com/go/auth v0.15.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect + cloud.google.com/go/compute/metadata v0.6.0 // indirect + cloud.google.com/go/iam v1.4.2 // indirect + cloud.google.com/go/longrunning v0.6.6 // indirect + cloud.google.com/go/monitoring v1.24.1 // indirect + cloud.google.com/go/spanner v1.77.0 // indirect dario.cat/mergo v1.0.0 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/BurntSushi/toml v1.4.0 // indirect + github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charmbracelet/x/ansi v0.8.0 // indirect github.com/charmbracelet/x/term v0.2.1 // indirect + github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/dockercfg v0.3.2 // indirect @@ -43,12 +56,20 @@ require ( github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect + github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-testfixtures/testfixtures/v3 v3.14.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/s2a-go v0.1.9 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect + github.com/googleapis/gax-go/v2 v2.14.1 // indirect + github.com/googleapis/go-sql-spanner v1.11.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 // indirect @@ -56,7 +77,7 @@ require ( github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/joho/godotenv v1.5.1 // indirect - github.com/klauspost/compress v1.17.4 // indirect + github.com/klauspost/compress v1.17.7 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/magiconair/properties v1.8.7 // indirect @@ -76,6 +97,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/rivo/uniseg v0.4.7 // indirect @@ -86,19 +108,28 @@ require ( github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect + go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.35.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect go.opentelemetry.io/otel v1.35.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect go.opentelemetry.io/otel/metric v1.35.0 // indirect go.opentelemetry.io/otel/sdk v1.35.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect go.opentelemetry.io/otel/trace v1.35.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.35.0 // indirect + golang.org/x/net v0.37.0 // indirect + golang.org/x/oauth2 v0.28.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.23.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect + golang.org/x/time v0.11.0 // indirect + google.golang.org/api v0.226.0 // indirect + google.golang.org/genproto v0.0.0-20250313205543-e70fdf4c4cb4 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250313205543-e70fdf4c4cb4 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect ) diff --git a/go.sum b/go.sum index d49b137..90f3f3c 100644 --- a/go.sum +++ b/go.sum @@ -1,24 +1,667 @@ +cel.dev/expr v0.22.0 h1:+hFFhLPmquBImfs1BiN2PZmkr5ASse2ZOuaxIs9e4R8= +cel.dev/expr v0.22.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= +cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= +cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= +cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= +cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= +cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= +cloud.google.com/go v0.119.0 h1:tw7OjErMzJKbbjaEHkrt60KQrK5Wus/boCZ7tm5/RNE= +cloud.google.com/go v0.119.0/go.mod h1:fwB8QLzTcNevxqi8dcpR+hoMIs3jBherGS9VUBDAW08= +cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= +cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= +cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= +cloud.google.com/go/accesscontextmanager v1.3.0/go.mod h1:TgCBehyr5gNMz7ZaH9xubp+CE8dkrszb4oK9CWyvD4o= +cloud.google.com/go/accesscontextmanager v1.4.0/go.mod h1:/Kjh7BBu/Gh83sv+K60vN9QE5NJcd80sU33vIe2IFPE= +cloud.google.com/go/accesscontextmanager v1.6.0/go.mod h1:8XCvZWfYw3K/ji0iVnp+6pu7huxoQTLmxAbVjbloTtM= +cloud.google.com/go/accesscontextmanager v1.7.0/go.mod h1:CEGLewx8dwa33aDAZQujl7Dx+uYhS0eay198wB/VumQ= +cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= +cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= +cloud.google.com/go/aiplatform v1.27.0/go.mod h1:Bvxqtl40l0WImSb04d0hXFU7gDOiq9jQmorivIiWcKg= +cloud.google.com/go/aiplatform v1.35.0/go.mod h1:7MFT/vCaOyZT/4IIFfxH4ErVg/4ku6lKv3w0+tFTgXQ= +cloud.google.com/go/aiplatform v1.36.1/go.mod h1:WTm12vJRPARNvJ+v6P52RDHCNe4AhvjcIZ/9/RRHy/k= +cloud.google.com/go/aiplatform v1.37.0/go.mod h1:IU2Cv29Lv9oCn/9LkFiiuKfwrRTq+QQMbW+hPCxJGZw= +cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= +cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= +cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M= +cloud.google.com/go/analytics v0.18.0/go.mod h1:ZkeHGQlcIPkw0R/GW+boWHhCOR43xz9RN/jn7WcqfIE= +cloud.google.com/go/analytics v0.19.0/go.mod h1:k8liqf5/HCnOUkbawNtrWWc+UAzyDlW89doe8TtoDsE= +cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= +cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= +cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= +cloud.google.com/go/apigeeconnect v1.3.0/go.mod h1:G/AwXFAKo0gIXkPTVfZDd2qA1TxBXJ3MgMRBQkIi9jc= +cloud.google.com/go/apigeeconnect v1.4.0/go.mod h1:kV4NwOKqjvt2JYR0AoIWo2QGfoRtn/pkS3QlHp0Ni04= +cloud.google.com/go/apigeeconnect v1.5.0/go.mod h1:KFaCqvBRU6idyhSNyn3vlHXc8VMDJdRmwDF6JyFRqZ8= +cloud.google.com/go/apigeeregistry v0.4.0/go.mod h1:EUG4PGcsZvxOXAdyEghIdXwAEi/4MEaoqLMLDMIwKXY= +cloud.google.com/go/apigeeregistry v0.5.0/go.mod h1:YR5+s0BVNZfVOUkMa5pAR2xGd0A473vA5M7j247o1wM= +cloud.google.com/go/apigeeregistry v0.6.0/go.mod h1:BFNzW7yQVLZ3yj0TKcwzb8n25CFBri51GVGOEUcgQsc= +cloud.google.com/go/apikeys v0.4.0/go.mod h1:XATS/yqZbaBK0HOssf+ALHp8jAlNHUgyfprvNcBIszU= +cloud.google.com/go/apikeys v0.5.0/go.mod h1:5aQfwY4D+ewMMWScd3hm2en3hCj+BROlyrt3ytS7KLI= +cloud.google.com/go/apikeys v0.6.0/go.mod h1:kbpXu5upyiAlGkKrJgQl8A0rKNNJ7dQ377pdroRSSi8= +cloud.google.com/go/appengine v1.4.0/go.mod h1:CS2NhuBuDXM9f+qscZ6V86m1MIIqPj3WC/UoEuR1Sno= +cloud.google.com/go/appengine v1.5.0/go.mod h1:TfasSozdkFI0zeoxW3PTBLiNqRmzraodCWatWI9Dmak= +cloud.google.com/go/appengine v1.6.0/go.mod h1:hg6i0J/BD2cKmDJbaFSYHFyZkgBEfQrDg/X0V5fJn84= +cloud.google.com/go/appengine v1.7.0/go.mod h1:eZqpbHFCqRGa2aCdope7eC0SWLV1j0neb/QnMJVWx6A= +cloud.google.com/go/appengine v1.7.1/go.mod h1:IHLToyb/3fKutRysUlFO0BPt5j7RiQ45nrzEJmKTo6E= +cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= +cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= +cloud.google.com/go/area120 v0.7.0/go.mod h1:a3+8EUD1SX5RUcCs3MY5YasiO1z6yLiNLRiFrykbynY= +cloud.google.com/go/area120 v0.7.1/go.mod h1:j84i4E1RboTWjKtZVWXPqvK5VHQFJRF2c1Nm69pWm9k= +cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= +cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= +cloud.google.com/go/artifactregistry v1.8.0/go.mod h1:w3GQXkJX8hiKN0v+at4b0qotwijQbYUqF2GWkZzAhC0= +cloud.google.com/go/artifactregistry v1.9.0/go.mod h1:2K2RqvA2CYvAeARHRkLDhMDJ3OXy26h3XW+3/Jh2uYc= +cloud.google.com/go/artifactregistry v1.11.1/go.mod h1:lLYghw+Itq9SONbCa1YWBoWs1nOucMH0pwXN1rOBZFI= +cloud.google.com/go/artifactregistry v1.11.2/go.mod h1:nLZns771ZGAwVLzTX/7Al6R9ehma4WUEhZGWV6CeQNQ= +cloud.google.com/go/artifactregistry v1.12.0/go.mod h1:o6P3MIvtzTOnmvGagO9v/rOjjA0HmhJ+/6KAXrmYDCI= +cloud.google.com/go/artifactregistry v1.13.0/go.mod h1:uy/LNfoOIivepGhooAUpL1i30Hgee3Cu0l4VTWHUC08= +cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= +cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= +cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= +cloud.google.com/go/asset v1.9.0/go.mod h1:83MOE6jEJBMqFKadM9NLRcs80Gdw76qGuHn8m3h8oHQ= +cloud.google.com/go/asset v1.10.0/go.mod h1:pLz7uokL80qKhzKr4xXGvBQXnzHn5evJAEAtZiIb0wY= +cloud.google.com/go/asset v1.11.1/go.mod h1:fSwLhbRvC9p9CXQHJ3BgFeQNM4c9x10lqlrdEUYXlJo= +cloud.google.com/go/asset v1.12.0/go.mod h1:h9/sFOa4eDIyKmH6QMpm4eUK3pDojWnUhTgJlk762Hg= +cloud.google.com/go/asset v1.13.0/go.mod h1:WQAMyYek/b7NBpYq/K4KJWcRqzoalEsxz/t/dTk4THw= +cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= +cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= +cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= +cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= +cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= +cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= +cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps= +cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8= +cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M= +cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= +cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= +cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= +cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= +cloud.google.com/go/automl v1.8.0/go.mod h1:xWx7G/aPEe/NP+qzYXktoBSDfjO+vnKMGgsApGJJquM= +cloud.google.com/go/automl v1.12.0/go.mod h1:tWDcHDp86aMIuHmyvjuKeeHEGq76lD7ZqfGLN6B0NuU= +cloud.google.com/go/baremetalsolution v0.3.0/go.mod h1:XOrocE+pvK1xFfleEnShBlNAXf+j5blPPxrhjKgnIFc= +cloud.google.com/go/baremetalsolution v0.4.0/go.mod h1:BymplhAadOO/eBa7KewQ0Ppg4A4Wplbn+PsFKRLo0uI= +cloud.google.com/go/baremetalsolution v0.5.0/go.mod h1:dXGxEkmR9BMwxhzBhV0AioD0ULBmuLZI8CdwalUxuss= +cloud.google.com/go/batch v0.3.0/go.mod h1:TR18ZoAekj1GuirsUsR1ZTKN3FC/4UDnScjT8NXImFE= +cloud.google.com/go/batch v0.4.0/go.mod h1:WZkHnP43R/QCGQsZ+0JyG4i79ranE2u8xvjq/9+STPE= +cloud.google.com/go/batch v0.7.0/go.mod h1:vLZN95s6teRUqRQ4s3RLDsH8PvboqBK+rn1oevL159g= +cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= +cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= +cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= +cloud.google.com/go/beyondcorp v0.5.0/go.mod h1:uFqj9X+dSfrheVp7ssLTaRHd2EHqSL4QZmH4e8WXGGU= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= +cloud.google.com/go/bigquery v1.43.0/go.mod h1:ZMQcXHsl+xmU1z36G2jNGZmKp9zNY5BUua5wDgmNCfw= +cloud.google.com/go/bigquery v1.44.0/go.mod h1:0Y33VqXTEsbamHJvJHdFmtqHvMIY28aK1+dFsvaChGc= +cloud.google.com/go/bigquery v1.47.0/go.mod h1:sA9XOgy0A8vQK9+MWhEQTY6Tix87M/ZurWFIxmF9I/E= +cloud.google.com/go/bigquery v1.48.0/go.mod h1:QAwSz+ipNgfL5jxiaK7weyOhzdoAy1zFm0Nf1fysJac= +cloud.google.com/go/bigquery v1.49.0/go.mod h1:Sv8hMmTFFYBlt/ftw2uN6dFdQPzBlREY9yBh7Oy7/4Q= +cloud.google.com/go/bigquery v1.50.0/go.mod h1:YrleYEh2pSEbgTBZYMJ5SuSr0ML3ypjRB1zgf7pvQLU= +cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= +cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= +cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= +cloud.google.com/go/billing v1.7.0/go.mod h1:q457N3Hbj9lYwwRbnlD7vUpyjq6u5U1RAOArInEiD5Y= +cloud.google.com/go/billing v1.12.0/go.mod h1:yKrZio/eu+okO/2McZEbch17O5CB5NpZhhXG6Z766ss= +cloud.google.com/go/billing v1.13.0/go.mod h1:7kB2W9Xf98hP9Sr12KfECgfGclsH3CQR0R08tnRlRbc= +cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= +cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= +cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= +cloud.google.com/go/binaryauthorization v1.4.0/go.mod h1:tsSPQrBd77VLplV70GUhBf/Zm3FsKmgSqgm4UmiDItk= +cloud.google.com/go/binaryauthorization v1.5.0/go.mod h1:OSe4OU1nN/VswXKRBmciKpo9LulY41gch5c68htf3/Q= +cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= +cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= +cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= +cloud.google.com/go/channel v1.8.0/go.mod h1:W5SwCXDJsq/rg3tn3oG0LOxpAo6IMxNa09ngphpSlnk= +cloud.google.com/go/channel v1.9.0/go.mod h1:jcu05W0my9Vx4mt3/rEHpfxc9eKi9XwsdDL8yBMbKUk= +cloud.google.com/go/channel v1.11.0/go.mod h1:IdtI0uWGqhEeatSB62VOoJ8FSUhJ9/+iGkJVqp74CGE= +cloud.google.com/go/channel v1.12.0/go.mod h1:VkxCGKASi4Cq7TbXxlaBezonAYpp1GCnKMY6tnMQnLU= +cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= +cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= +cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M= +cloud.google.com/go/cloudbuild v1.7.0/go.mod h1:zb5tWh2XI6lR9zQmsm1VRA+7OCuve5d8S+zJUul8KTg= +cloud.google.com/go/cloudbuild v1.9.0/go.mod h1:qK1d7s4QlO0VwfYn5YuClDGg2hfmLZEb4wQGAbIgL1s= +cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= +cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= +cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= +cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= +cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= +cloud.google.com/go/cloudtasks v1.7.0/go.mod h1:ImsfdYWwlWNJbdgPIIGJWC+gemEGTBK/SunNQQNCAb4= +cloud.google.com/go/cloudtasks v1.8.0/go.mod h1:gQXUIwCSOI4yPVK7DgTVFiiP0ZW/eQkydWzwVMdHxrI= +cloud.google.com/go/cloudtasks v1.9.0/go.mod h1:w+EyLsVkLWHcOaqNEyvcKAsWp9p29dL6uL9Nst1cI7Y= +cloud.google.com/go/cloudtasks v1.10.0/go.mod h1:NDSoTLkZ3+vExFEWu2UJV1arUyzVDAiZtdWcsUyNwBs= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= +cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= +cloud.google.com/go/compute v1.12.0/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= +cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= +cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= +cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= +cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= +cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= +cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= +cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= +cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= +cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= +cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= +cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= +cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= +cloud.google.com/go/container v1.14.0/go.mod h1:3AoJMPhHfLDxLvrlVWaK57IXzaPnLaZq63WX59aQBfM= +cloud.google.com/go/container v1.15.0/go.mod h1:ft+9S0WGjAyjDggg5S06DXj+fHJICWg8L7isCQe9pQA= +cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= +cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= +cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= +cloud.google.com/go/containeranalysis v0.9.0/go.mod h1:orbOANbwk5Ejoom+s+DUCTTJ7IBdBQJDcSylAx/on9s= +cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= +cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= +cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= +cloud.google.com/go/datacatalog v1.7.0/go.mod h1:9mEl4AuDYWw81UGc41HonIHH7/sn52H0/tc8f8ZbZIE= +cloud.google.com/go/datacatalog v1.8.0/go.mod h1:KYuoVOv9BM8EYz/4eMFxrr4DUKhGIOXxZoKYF5wdISM= +cloud.google.com/go/datacatalog v1.8.1/go.mod h1:RJ58z4rMp3gvETA465Vg+ag8BGgBdnRPEMMSTr5Uv+M= +cloud.google.com/go/datacatalog v1.12.0/go.mod h1:CWae8rFkfp6LzLumKOnmVh4+Zle4A3NXLzVJ1d1mRm0= +cloud.google.com/go/datacatalog v1.13.0/go.mod h1:E4Rj9a5ZtAxcQJlEBTLgMTphfP11/lNaAshpoBgemX8= +cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= +cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= +cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= +cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= +cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= +cloud.google.com/go/dataform v0.5.0/go.mod h1:GFUYRe8IBa2hcomWplodVmUx/iTL0FrsauObOM3Ipr0= +cloud.google.com/go/dataform v0.6.0/go.mod h1:QPflImQy33e29VuapFdf19oPbE4aYTJxr31OAPV+ulA= +cloud.google.com/go/dataform v0.7.0/go.mod h1:7NulqnVozfHvWUBpMDfKMUESr+85aJsC/2O0o3jWPDE= +cloud.google.com/go/datafusion v1.4.0/go.mod h1:1Zb6VN+W6ALo85cXnM1IKiPw+yQMKMhB9TsTSRDo/38= +cloud.google.com/go/datafusion v1.5.0/go.mod h1:Kz+l1FGHB0J+4XF2fud96WMmRiq/wj8N9u007vyXZ2w= +cloud.google.com/go/datafusion v1.6.0/go.mod h1:WBsMF8F1RhSXvVM8rCV3AeyWVxcC2xY6vith3iw3S+8= +cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= +cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= +cloud.google.com/go/datalabeling v0.7.0/go.mod h1:WPQb1y08RJbmpM3ww0CSUAGweL0SxByuW2E+FU+wXcM= +cloud.google.com/go/dataplex v1.3.0/go.mod h1:hQuRtDg+fCiFgC8j0zV222HvzFQdRd+SVX8gdmFcZzA= +cloud.google.com/go/dataplex v1.4.0/go.mod h1:X51GfLXEMVJ6UN47ESVqvlsRplbLhcsAt0kZCCKsU0A= +cloud.google.com/go/dataplex v1.5.2/go.mod h1:cVMgQHsmfRoI5KFYq4JtIBEUbYwc3c7tXmIDhRmNNVQ= +cloud.google.com/go/dataplex v1.6.0/go.mod h1:bMsomC/aEJOSpHXdFKFGQ1b0TDPIeL28nJObeO1ppRs= +cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= +cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= +cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= +cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= +cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= +cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/datastore v1.10.0/go.mod h1:PC5UzAmDEkAmkfaknstTYbNpgE49HAgW2J1gcgUfmdM= +cloud.google.com/go/datastore v1.11.0/go.mod h1:TvGxBIHCS50u8jzG+AW/ppf87v1of8nwzFNgEZU1D3c= +cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= +cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= +cloud.google.com/go/datastream v1.4.0/go.mod h1:h9dpzScPhDTs5noEMQVWP8Wx8AFBRyS0s8KWPx/9r0g= +cloud.google.com/go/datastream v1.5.0/go.mod h1:6TZMMNPwjUqZHBKPQ1wwXpb0d5VDVPl2/XoS5yi88q4= +cloud.google.com/go/datastream v1.6.0/go.mod h1:6LQSuswqLa7S4rPAOZFVjHIG3wJIjZcZrw8JDEDJuIs= +cloud.google.com/go/datastream v1.7.0/go.mod h1:uxVRMm2elUSPuh65IbZpzJNMbuzkcvu5CjMqVIUHrww= +cloud.google.com/go/deploy v1.4.0/go.mod h1:5Xghikd4VrmMLNaF6FiRFDlHb59VM59YoDQnOUdsH/c= +cloud.google.com/go/deploy v1.5.0/go.mod h1:ffgdD0B89tToyW/U/D2eL0jN2+IEV/3EMuXHA0l4r+s= +cloud.google.com/go/deploy v1.6.0/go.mod h1:f9PTHehG/DjCom3QH0cntOVRm93uGBDt2vKzAPwpXQI= +cloud.google.com/go/deploy v1.8.0/go.mod h1:z3myEJnA/2wnB4sgjqdMfgxCA0EqC3RBTNcVPs93mtQ= +cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= +cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= +cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= +cloud.google.com/go/dialogflow v1.18.0/go.mod h1:trO7Zu5YdyEuR+BhSNOqJezyFQ3aUzz0njv7sMx/iek= +cloud.google.com/go/dialogflow v1.19.0/go.mod h1:JVmlG1TwykZDtxtTXujec4tQ+D8SBFMoosgy+6Gn0s0= +cloud.google.com/go/dialogflow v1.29.0/go.mod h1:b+2bzMe+k1s9V+F2jbJwpHPzrnIyHihAdRFMtn2WXuM= +cloud.google.com/go/dialogflow v1.31.0/go.mod h1:cuoUccuL1Z+HADhyIA7dci3N5zUssgpBJmCzI6fNRB4= +cloud.google.com/go/dialogflow v1.32.0/go.mod h1:jG9TRJl8CKrDhMEcvfcfFkkpp8ZhgPz3sBGmAUYJ2qE= +cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= +cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= +cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= +cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= +cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= +cloud.google.com/go/documentai v1.9.0/go.mod h1:FS5485S8R00U10GhgBC0aNGrJxBP8ZVpEeJ7PQDZd6k= +cloud.google.com/go/documentai v1.10.0/go.mod h1:vod47hKQIPeCfN2QS/jULIvQTugbmdc0ZvxxfQY1bg4= +cloud.google.com/go/documentai v1.16.0/go.mod h1:o0o0DLTEZ+YnJZ+J4wNfTxmDVyrkzFvttBXXtYRMHkM= +cloud.google.com/go/documentai v1.18.0/go.mod h1:F6CK6iUH8J81FehpskRmhLq/3VlwQvb7TvwOceQ2tbs= +cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= +cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= +cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= +cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= +cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= +cloud.google.com/go/edgecontainer v0.3.0/go.mod h1:FLDpP4nykgwwIfcLt6zInhprzw0lEi2P1fjO6Ie0qbc= +cloud.google.com/go/edgecontainer v1.0.0/go.mod h1:cttArqZpBB2q58W/upSG++ooo6EsblxDIolxa3jSjbY= +cloud.google.com/go/errorreporting v0.3.0/go.mod h1:xsP2yaAp+OAW4OIm60An2bbLpqIhKXdWR/tawvl7QzU= +cloud.google.com/go/essentialcontacts v1.3.0/go.mod h1:r+OnHa5jfj90qIfZDO/VztSFqbQan7HV75p8sA+mdGI= +cloud.google.com/go/essentialcontacts v1.4.0/go.mod h1:8tRldvHYsmnBCHdFpvU+GL75oWiBKl80BiqlFh9tp+8= +cloud.google.com/go/essentialcontacts v1.5.0/go.mod h1:ay29Z4zODTuwliK7SnX8E86aUF2CTzdNtvv42niCX0M= +cloud.google.com/go/eventarc v1.7.0/go.mod h1:6ctpF3zTnaQCxUjHUdcfgcA1A2T309+omHZth7gDfmc= +cloud.google.com/go/eventarc v1.8.0/go.mod h1:imbzxkyAU4ubfsaKYdQg04WS1NvncblHEup4kvF+4gw= +cloud.google.com/go/eventarc v1.10.0/go.mod h1:u3R35tmZ9HvswGRBnF48IlYgYeBcPUCjkr4BTdem2Kw= +cloud.google.com/go/eventarc v1.11.0/go.mod h1:PyUjsUKPWoRBCHeOxZd/lbOOjahV41icXyUY5kSTvVY= +cloud.google.com/go/filestore v1.3.0/go.mod h1:+qbvHGvXU1HaKX2nD0WEPo92TP/8AQuCVEBXNY9z0+w= +cloud.google.com/go/filestore v1.4.0/go.mod h1:PaG5oDfo9r224f8OYXURtAsY+Fbyq/bLYoINEK8XQAI= +cloud.google.com/go/filestore v1.5.0/go.mod h1:FqBXDWBp4YLHqRnVGveOkHDf8svj9r5+mUDLupOWEDs= +cloud.google.com/go/filestore v1.6.0/go.mod h1:di5unNuss/qfZTw2U9nhFqo8/ZDSc466dre85Kydllg= +cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= +cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= +cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= +cloud.google.com/go/functions v1.8.0/go.mod h1:RTZ4/HsQjIqIYP9a9YPbU+QFoQsAlYgrwOXJWHn1POY= +cloud.google.com/go/functions v1.9.0/go.mod h1:Y+Dz8yGguzO3PpIjhLTbnqV1CWmgQ5UwtlpzoyquQ08= +cloud.google.com/go/functions v1.10.0/go.mod h1:0D3hEOe3DbEvCXtYOZHQZmD+SzYsi1YbI7dGvHfldXw= +cloud.google.com/go/functions v1.12.0/go.mod h1:AXWGrF3e2C/5ehvwYo/GH6O5s09tOPksiKhz+hH8WkA= +cloud.google.com/go/functions v1.13.0/go.mod h1:EU4O007sQm6Ef/PwRsI8N2umygGqPBS/IZQKBQBcJ3c= +cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= +cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= +cloud.google.com/go/gaming v1.7.0/go.mod h1:LrB8U7MHdGgFG851iHAfqUdLcKBdQ55hzXy9xBJz0+w= +cloud.google.com/go/gaming v1.8.0/go.mod h1:xAqjS8b7jAVW0KFYeRUxngo9My3f33kFmua++Pi+ggM= +cloud.google.com/go/gaming v1.9.0/go.mod h1:Fc7kEmCObylSWLO334NcO+O9QMDyz+TKC4v1D7X+Bc0= +cloud.google.com/go/gkebackup v0.2.0/go.mod h1:XKvv/4LfG829/B8B7xRkk8zRrOEbKtEam6yNfuQNH60= +cloud.google.com/go/gkebackup v0.3.0/go.mod h1:n/E671i1aOQvUxT541aTkCwExO/bTer2HDlj4TsBRAo= +cloud.google.com/go/gkebackup v0.4.0/go.mod h1:byAyBGUwYGEEww7xsbnUTBHIYcOPy/PgUWUtOeRm9Vg= +cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= +cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= +cloud.google.com/go/gkeconnect v0.7.0/go.mod h1:SNfmVqPkaEi3bF/B3CNZOAYPYdg7sU+obZ+QTky2Myw= +cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= +cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= +cloud.google.com/go/gkehub v0.11.0/go.mod h1:JOWHlmN+GHyIbuWQPl47/C2RFhnFKH38jH9Ascu3n0E= +cloud.google.com/go/gkehub v0.12.0/go.mod h1:djiIwwzTTBrF5NaXCGv3mf7klpEMcST17VBTVVDcuaw= +cloud.google.com/go/gkemulticloud v0.3.0/go.mod h1:7orzy7O0S+5kq95e4Hpn7RysVA7dPs8W/GgfUtsPbrA= +cloud.google.com/go/gkemulticloud v0.4.0/go.mod h1:E9gxVBnseLWCk24ch+P9+B2CoDFJZTyIgLKSalC7tuI= +cloud.google.com/go/gkemulticloud v0.5.0/go.mod h1:W0JDkiyi3Tqh0TJr//y19wyb1yf8llHVto2Htf2Ja3Y= +cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= +cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= +cloud.google.com/go/gsuiteaddons v1.4.0/go.mod h1:rZK5I8hht7u7HxFQcFei0+AtfS9uSushomRlg+3ua1o= +cloud.google.com/go/gsuiteaddons v1.5.0/go.mod h1:TFCClYLd64Eaa12sFVmUyG62tk4mdIsI7pAnSXRkcFo= +cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= +cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= +cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= +cloud.google.com/go/iam v0.7.0/go.mod h1:H5Br8wRaDGNc8XP3keLc4unfUUZeyH3Sfl9XpQEYOeg= +cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGESjkE= +cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY= +cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= +cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= +cloud.google.com/go/iam v1.4.2 h1:4AckGYAYsowXeHzsn/LCKWIwSWLkdb0eGjH8wWkd27Q= +cloud.google.com/go/iam v1.4.2/go.mod h1:REGlrt8vSlh4dfCJfSEcNjLGq75wW75c5aU3FLOYq34= +cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= +cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= +cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= +cloud.google.com/go/iap v1.7.0/go.mod h1:beqQx56T9O1G1yNPph+spKpNibDlYIiIixiqsQXxLIo= +cloud.google.com/go/iap v1.7.1/go.mod h1:WapEwPc7ZxGt2jFGB/C/bm+hP0Y6NXzOYGjpPnmMS74= +cloud.google.com/go/ids v1.1.0/go.mod h1:WIuwCaYVOzHIj2OhN9HAwvW+DBdmUAdcWlFxRl+KubM= +cloud.google.com/go/ids v1.2.0/go.mod h1:5WXvp4n25S0rA/mQWAg1YEEBBq6/s+7ml1RDCW1IrcY= +cloud.google.com/go/ids v1.3.0/go.mod h1:JBdTYwANikFKaDP6LtW5JAi4gubs57SVNQjemdt6xV4= +cloud.google.com/go/iot v1.3.0/go.mod h1:r7RGh2B61+B8oz0AGE+J72AhA0G7tdXItODWsaA2oLs= +cloud.google.com/go/iot v1.4.0/go.mod h1:dIDxPOn0UvNDUMD8Ger7FIaTuvMkj+aGk94RPP0iV+g= +cloud.google.com/go/iot v1.5.0/go.mod h1:mpz5259PDl3XJthEmh9+ap0affn/MqNSP4My77Qql9o= +cloud.google.com/go/iot v1.6.0/go.mod h1:IqdAsmE2cTYYNO1Fvjfzo9po179rAtJeVGUvkLN3rLE= +cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= +cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= +cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= +cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4jMAg= +cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= +cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24= +cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= +cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= +cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= +cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= +cloud.google.com/go/language v1.8.0/go.mod h1:qYPVHf7SPoNNiCL2Dr0FfEFNil1qi3pQEyygwpgVKB8= +cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrbeJBE+ptUY= +cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= +cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= +cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= +cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= +cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= +cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= +cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= +cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= +cloud.google.com/go/longrunning v0.6.6 h1:XJNDo5MUfMM05xK3ewpbSdmt7R2Zw+aQEMbdQR65Rbw= +cloud.google.com/go/longrunning v0.6.6/go.mod h1:hyeGJUrPHcx0u2Uu1UFSoYZLn4lkMrccJig0t4FI7yw= +cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= +cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= +cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= +cloud.google.com/go/maps v0.1.0/go.mod h1:BQM97WGyfw9FWEmQMpZ5T6cpovXXSd1cGmFma94eubI= +cloud.google.com/go/maps v0.6.0/go.mod h1:o6DAMMfb+aINHz/p/jbcY+mYeXBoZoxTfdSQ8VAJaCw= +cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= +cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= +cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= +cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= +cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= +cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= +cloud.google.com/go/memcache v1.6.0/go.mod h1:XS5xB0eQZdHtTuTF9Hf8eJkKtR3pVRCcvJwtm68T3rA= +cloud.google.com/go/memcache v1.7.0/go.mod h1:ywMKfjWhNtkQTxrWxCkCFkoPjLHPW6A7WOTVI8xy3LY= +cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= +cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= +cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= +cloud.google.com/go/metastore v1.7.0/go.mod h1:s45D0B4IlsINu87/AsWiEVYbLaIMeUSoxlKKDqBGFS8= +cloud.google.com/go/metastore v1.8.0/go.mod h1:zHiMc4ZUpBiM7twCIFQmJ9JMEkDSyZS9U12uf7wHqSI= +cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= +cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= +cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= +cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= +cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= +cloud.google.com/go/monitoring v1.24.1 h1:vKiypZVFD/5a3BbQMvI4gZdl8445ITzXFh257XBgrS0= +cloud.google.com/go/monitoring v1.24.1/go.mod h1:Z05d1/vn9NaujqY2voG6pVQXoJGbp+r3laV+LySt9K0= +cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= +cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= +cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= +cloud.google.com/go/networkconnectivity v1.7.0/go.mod h1:RMuSbkdbPwNMQjB5HBWD5MpTBnNm39iAVpC3TmsExt8= +cloud.google.com/go/networkconnectivity v1.10.0/go.mod h1:UP4O4sWXJG13AqrTdQCD9TnLGEbtNRqjuaaA7bNjF5E= +cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= +cloud.google.com/go/networkmanagement v1.4.0/go.mod h1:Q9mdLLRn60AsOrPc8rs8iNV6OHXaGcDdsIQe1ohekq8= +cloud.google.com/go/networkmanagement v1.5.0/go.mod h1:ZnOeZ/evzUdUsnvRt792H0uYEnHQEMaz+REhhzJRcf4= +cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= +cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= +cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= +cloud.google.com/go/networksecurity v0.7.0/go.mod h1:mAnzoxx/8TBSyXEeESMy9OOYwo1v+gZ5eMRnsT5bC8k= +cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= +cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= +cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= +cloud.google.com/go/notebooks v1.4.0/go.mod h1:4QPMngcwmgb6uw7Po99B2xv5ufVoIQ7nOGDyL4P8AgA= +cloud.google.com/go/notebooks v1.5.0/go.mod h1:q8mwhnP9aR8Hpfnrc5iN5IBhrXUy8S2vuYs+kBJ/gu0= +cloud.google.com/go/notebooks v1.7.0/go.mod h1:PVlaDGfJgj1fl1S3dUwhFMXFgfYGhYQt2164xOMONmE= +cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= +cloud.google.com/go/optimization v1.1.0/go.mod h1:5po+wfvX5AQlPznyVEZjGJTMr4+CAkJf2XSTQOOl9l4= +cloud.google.com/go/optimization v1.2.0/go.mod h1:Lr7SOHdRDENsh+WXVmQhQTrzdu9ybg0NecjHidBq6xs= +cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= +cloud.google.com/go/orchestration v1.3.0/go.mod h1:Sj5tq/JpWiB//X/q3Ngwdl5K7B7Y0KZ7bfv0wL6fqVA= +cloud.google.com/go/orchestration v1.4.0/go.mod h1:6W5NLFWs2TlniBphAViZEVhrXRSMgUGDfW7vrWKvsBk= +cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= +cloud.google.com/go/orgpolicy v1.4.0/go.mod h1:xrSLIV4RePWmP9P3tBl8S93lTmlAxjm06NSm2UTmKvE= +cloud.google.com/go/orgpolicy v1.5.0/go.mod h1:hZEc5q3wzwXJaKrsx5+Ewg0u1LxJ51nNFlext7Tanwc= +cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= +cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= +cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= +cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= +cloud.google.com/go/osconfig v1.10.0/go.mod h1:uMhCzqC5I8zfD9zDEAfvgVhDS8oIjySWh+l4WK6GnWw= +cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= +cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= +cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= +cloud.google.com/go/oslogin v1.6.0/go.mod h1:zOJ1O3+dTU8WPlGEkFSh7qeHPPSoxrcMbbK1Nm2iX70= +cloud.google.com/go/oslogin v1.7.0/go.mod h1:e04SN0xO1UNJ1M5GP0vzVBFicIe4O53FOfcixIqTyXo= +cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= +cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= +cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= +cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= +cloud.google.com/go/policytroubleshooter v1.3.0/go.mod h1:qy0+VwANja+kKrjlQuOzmlvscn4RNsAc0e15GGqfMxg= +cloud.google.com/go/policytroubleshooter v1.4.0/go.mod h1:DZT4BcRw3QoO8ota9xw/LKtPa8lKeCByYeKTIf/vxdE= +cloud.google.com/go/policytroubleshooter v1.5.0/go.mod h1:Rz1WfV+1oIpPdN2VvvuboLVRsB1Hclg3CKQ53j9l8vw= +cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= +cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= +cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= +cloud.google.com/go/privatecatalog v0.7.0/go.mod h1:2s5ssIFO69F5csTXcwBP7NPFTZvps26xGzvQ2PQaBYg= +cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= +cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= +cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8= +cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= +cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= +cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= +cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= +cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= +cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= +cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= +cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= +cloud.google.com/go/recaptchaenterprise/v2 v2.4.0/go.mod h1:Am3LHfOuBstrLrNCBrlI5sbwx9LBg3te2N6hGvHn2mE= +cloud.google.com/go/recaptchaenterprise/v2 v2.5.0/go.mod h1:O8LzcHXN3rz0j+LBC91jrwI3R+1ZSZEWrfL7XHgNo9U= +cloud.google.com/go/recaptchaenterprise/v2 v2.6.0/go.mod h1:RPauz9jeLtB3JVzg6nCbe12qNoaa8pXc4d/YukAmcnA= +cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c= +cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= +cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= +cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= +cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= +cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= +cloud.google.com/go/recommender v1.7.0/go.mod h1:XLHs/W+T8olwlGOgfQenXBTbIseGclClff6lhFVe9Bs= +cloud.google.com/go/recommender v1.8.0/go.mod h1:PkjXrTT05BFKwxaUxQmtIlrtj0kph108r02ZZQ5FE70= +cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= +cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= +cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= +cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= +cloud.google.com/go/redis v1.10.0/go.mod h1:ThJf3mMBQtW18JzGgh41/Wld6vnDDc/F/F35UolRZPM= +cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= +cloud.google.com/go/resourcemanager v1.3.0/go.mod h1:bAtrTjZQFJkiWTPDb1WBjzvc6/kifjj4QBYuKCCoqKA= +cloud.google.com/go/resourcemanager v1.4.0/go.mod h1:MwxuzkumyTX7/a3n37gmsT3py7LIXwrShilPh3P1tR0= +cloud.google.com/go/resourcemanager v1.5.0/go.mod h1:eQoXNAiAvCf5PXxWxXjhKQoTMaUSNrEfg+6qdf/wots= +cloud.google.com/go/resourcemanager v1.6.0/go.mod h1:YcpXGRs8fDzcUl1Xw8uOVmI8JEadvhRIkoXXUNVYcVo= +cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= +cloud.google.com/go/resourcesettings v1.3.0/go.mod h1:lzew8VfESA5DQ8gdlHwMrqZs1S9V87v3oCnKCWoOuQU= +cloud.google.com/go/resourcesettings v1.4.0/go.mod h1:ldiH9IJpcrlC3VSuCGvjR5of/ezRrOxFtpJoJo5SmXg= +cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= +cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= +cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= +cloud.google.com/go/retail v1.10.0/go.mod h1:2gDk9HsL4HMS4oZwz6daui2/jmKvqShXKQuB2RZ+cCc= +cloud.google.com/go/retail v1.11.0/go.mod h1:MBLk1NaWPmh6iVFSz9MeKG/Psyd7TAgm6y/9L2B4x9Y= +cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= +cloud.google.com/go/run v0.2.0/go.mod h1:CNtKsTA1sDcnqqIFR3Pb5Tq0usWxJJvsWOCPldRU3Do= +cloud.google.com/go/run v0.3.0/go.mod h1:TuyY1+taHxTjrD0ZFk2iAR+xyOXEA0ztb7U3UNA0zBo= +cloud.google.com/go/run v0.8.0/go.mod h1:VniEnuBwqjigv0A7ONfQUaEItaiCRVujlMqerPPiktM= +cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= +cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= +cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= +cloud.google.com/go/scheduler v1.6.0/go.mod h1:SgeKVM7MIwPn3BqtcBntpLyrIJftQISRrYB5ZtT+KOk= +cloud.google.com/go/scheduler v1.7.0/go.mod h1:jyCiBqWW956uBjjPMMuX09n3x37mtyPJegEWKxRsn44= +cloud.google.com/go/scheduler v1.8.0/go.mod h1:TCET+Y5Gp1YgHT8py4nlg2Sew8nUHMqcpousDgXJVQc= +cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= +cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= +cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= +cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= +cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= +cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= +cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= +cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= +cloud.google.com/go/security v1.9.0/go.mod h1:6Ta1bO8LXI89nZnmnsZGp9lVoVWXqsVbIq/t9dzI+2Q= +cloud.google.com/go/security v1.10.0/go.mod h1:QtOMZByJVlibUT2h9afNDWRZ1G96gVywH8T5GUSb9IA= +cloud.google.com/go/security v1.12.0/go.mod h1:rV6EhrpbNHrrxqlvW0BWAIawFWq3X90SduMJdFwtLB8= +cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= +cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= +cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= +cloud.google.com/go/securitycenter v1.15.0/go.mod h1:PeKJ0t8MoFmmXLXWm41JidyzI3PJjd8sXWaVqg43WWk= +cloud.google.com/go/securitycenter v1.16.0/go.mod h1:Q9GMaLQFUD+5ZTabrbujNWLtSLZIZF7SAR0wWECrjdk= +cloud.google.com/go/securitycenter v1.18.1/go.mod h1:0/25gAzCM/9OL9vVx4ChPeM/+DlfGQJDwBy/UC8AKK0= +cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= +cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= +cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= +cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA= +cloud.google.com/go/servicecontrol v1.11.0/go.mod h1:kFmTzYzTUIuZs0ycVqRHNaNhgR+UMUpw9n02l/pY+mc= +cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk= +cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= +cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= +cloud.google.com/go/servicedirectory v1.6.0/go.mod h1:pUlbnWsLH9c13yGkxCmfumWEPjsRs1RlmJ4pqiNjVL4= +cloud.google.com/go/servicedirectory v1.7.0/go.mod h1:5p/U5oyvgYGYejufvxhgwjL8UVXjkuw7q5XcG10wx1U= +cloud.google.com/go/servicedirectory v1.8.0/go.mod h1:srXodfhY1GFIPvltunswqXpVxFPpZjf8nkKQT7XcXaY= +cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s= +cloud.google.com/go/servicemanagement v1.4.0/go.mod h1:d8t8MDbezI7Z2R1O/wu8oTggo3BI2GKYbdG4y/SJTco= +cloud.google.com/go/servicemanagement v1.5.0/go.mod h1:XGaCRe57kfqu4+lRxaFEAuqmjzF0r+gWHjWqKqBvKFo= +cloud.google.com/go/servicemanagement v1.6.0/go.mod h1:aWns7EeeCOtGEX4OvZUWCCJONRZeFKiptqKf1D0l/Jc= +cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4= +cloud.google.com/go/serviceusage v1.3.0/go.mod h1:Hya1cozXM4SeSKTAgGXgj97GlqUvF5JaoXacR1JTP/E= +cloud.google.com/go/serviceusage v1.4.0/go.mod h1:SB4yxXSaYVuUBYUml6qklyONXNLt83U0Rb+CXyhjEeU= +cloud.google.com/go/serviceusage v1.5.0/go.mod h1:w8U1JvqUqwJNPEOTQjrMHkw3IaIFLoLsPLvsE3xueec= +cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA= +cloud.google.com/go/shell v1.3.0/go.mod h1:VZ9HmRjZBsjLGXusm7K5Q5lzzByZmJHf1d0IWHEN5X4= +cloud.google.com/go/shell v1.4.0/go.mod h1:HDxPzZf3GkDdhExzD/gs8Grqk+dmYcEjGShZgYa9URw= +cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= +cloud.google.com/go/spanner v1.41.0/go.mod h1:MLYDBJR/dY4Wt7ZaMIQ7rXOTLjYrmxLE/5ve9vFfWos= +cloud.google.com/go/spanner v1.44.0/go.mod h1:G8XIgYdOK+Fbcpbs7p2fiprDw4CaZX63whnSMLVBxjk= +cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= +cloud.google.com/go/spanner v1.77.0 h1:SR2ceRiwFUysdjo09CLM4IYgoTZtCzH1jGmptfvqgVw= +cloud.google.com/go/spanner v1.77.0/go.mod h1:SyWlGAC2Ssp1wCTl/bT3nedt/8/I8sIqPzkmigOfvbI= +cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= +cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= +cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= +cloud.google.com/go/speech v1.9.0/go.mod h1:xQ0jTcmnRFFM2RfX/U+rk6FQNUF6DQlydUSyoooSpco= +cloud.google.com/go/speech v1.14.1/go.mod h1:gEosVRPJ9waG7zqqnsHpYTOoAS4KouMRLDFMekpJ0J0= +cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= +cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= +cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= +cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= +cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= +cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= +cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= +cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= +cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= +cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= +cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= +cloud.google.com/go/talent v1.3.0/go.mod h1:CmcxwJ/PKfRgd1pBjQgU6W3YBwiewmUzQYH5HHmSCmM= +cloud.google.com/go/talent v1.4.0/go.mod h1:ezFtAgVuRf8jRsvyE6EwmbTK5LKciD4KVnHuDEFmOOA= +cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= +cloud.google.com/go/texttospeech v1.4.0/go.mod h1:FX8HQHA6sEpJ7rCMSfXuzBcysDAuWusNNNvN9FELDd8= +cloud.google.com/go/texttospeech v1.5.0/go.mod h1:oKPLhR4n4ZdQqWKURdwxMy0uiTS1xU161C8W57Wkea4= +cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= +cloud.google.com/go/tpu v1.3.0/go.mod h1:aJIManG0o20tfDQlRIej44FcwGGl/cD0oiRyMKG19IQ= +cloud.google.com/go/tpu v1.4.0/go.mod h1:mjZaX8p0VBgllCzF6wcU2ovUXN9TONFLd7iz227X2Xg= +cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= +cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= +cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= +cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= +cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= +cloud.google.com/go/translate v1.3.0/go.mod h1:gzMUwRjvOqj5i69y/LYLd8RrNQk+hOmIXTi9+nb3Djs= +cloud.google.com/go/translate v1.4.0/go.mod h1:06Dn/ppvLD6WvA5Rhdp029IX2Mi3Mn7fpMRLPvXT5Wg= +cloud.google.com/go/translate v1.5.0/go.mod h1:29YDSYveqqpA1CQFD7NQuP49xymq17RXNaUDdc0mNu0= +cloud.google.com/go/translate v1.6.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= +cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= +cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= +cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg= +cloud.google.com/go/video v1.13.0/go.mod h1:ulzkYlYgCp15N2AokzKjy7MQ9ejuynOJdf1tR5lGthk= +cloud.google.com/go/video v1.14.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= +cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= +cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= +cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= +cloud.google.com/go/videointelligence v1.8.0/go.mod h1:dIcCn4gVDdS7yte/w+koiXn5dWVplOZkE+xwG9FgK+M= +cloud.google.com/go/videointelligence v1.9.0/go.mod h1:29lVRMPDYHikk3v8EdPSaL8Ku+eMzDljjuvRs105XoU= +cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= +cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= +cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= +cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= +cloud.google.com/go/vision/v2 v2.4.0/go.mod h1:VtI579ll9RpVTrdKdkMzckdnwMyX2JILb+MhPqRbPsY= +cloud.google.com/go/vision/v2 v2.5.0/go.mod h1:MmaezXOOE+IWa+cS7OhRRLK2cNv1ZL98zhqFFZaaH2E= +cloud.google.com/go/vision/v2 v2.6.0/go.mod h1:158Hes0MvOS9Z/bDMSFpjwsUrZ5fPrdwuyyvKSGAGMY= +cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= +cloud.google.com/go/vmmigration v1.2.0/go.mod h1:IRf0o7myyWFSmVR1ItrBSFLFD/rJkfDCUTO4vLlJvsE= +cloud.google.com/go/vmmigration v1.3.0/go.mod h1:oGJ6ZgGPQOFdjHuocGcLqX4lc98YQ7Ygq8YQwHh9A7g= +cloud.google.com/go/vmmigration v1.5.0/go.mod h1:E4YQ8q7/4W9gobHjQg4JJSgXXSgY21nA5r8swQV+Xxc= +cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= +cloud.google.com/go/vmwareengine v0.1.0/go.mod h1:RsdNEf/8UDvKllXhMz5J40XxDrNJNN4sagiox+OI208= +cloud.google.com/go/vmwareengine v0.2.2/go.mod h1:sKdctNJxb3KLZkE/6Oui94iw/xs9PRNC2wnNLXsHvH8= +cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= +cloud.google.com/go/vpcaccess v1.4.0/go.mod h1:aQHVbTWDYUR1EbTApSVvMq1EnT57ppDmQzZ3imqIk4w= +cloud.google.com/go/vpcaccess v1.5.0/go.mod h1:drmg4HLk9NkZpGfCmZ3Tz0Bwnm2+DKqViEpeEpOq0m8= +cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= +cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= +cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= +cloud.google.com/go/webrisk v1.6.0/go.mod h1:65sW9V9rOosnc9ZY7A7jsy1zoHS5W9IAXv6dGqhMQMc= +cloud.google.com/go/webrisk v1.7.0/go.mod h1:mVMHgEYH0r337nmt1JyLthzMr6YxwN1aAIEc2fTcq7A= +cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= +cloud.google.com/go/websecurityscanner v1.3.0/go.mod h1:uImdKm2wyeXQevQJXeh8Uun/Ym1VqworNDlBXQevGMo= +cloud.google.com/go/websecurityscanner v1.4.0/go.mod h1:ebit/Fp0a+FWu5j4JOmJEV8S8CzdTkAS77oDsiSqYWQ= +cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= +cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= +cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= +cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= +cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= +cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= +git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2 h1:DBjmt6/otSdULyJdVg2BlG0qGZO5tKL4VzOs0jpvw5Q= +github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2/go.mod h1:dppbR7CwXD4pgtV9t3wD1812RaLDcBjtblcDF5f1vI0= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY= +github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= +github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= +github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= +github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= github.com/charmbracelet/bubbletea v1.3.3 h1:WpU6fCY0J2vDWM3zfS3vIDi/ULq3SYphZhkAGGvmEUY= @@ -29,12 +672,32 @@ github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2ll github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk= +github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -50,12 +713,47 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= +github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= +github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= +github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= +github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= +github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= +github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= +github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= +github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= +github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/liberation v0.2.0/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= +github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -63,23 +761,134 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-testfixtures/testfixtures/v3 v3.14.0 h1:aRt5qyH2XjzFgCC5NizNs6QrzjO7rC4pQZ1oJpPIdo8= +github.com/go-testfixtures/testfixtures/v3 v3.14.0/go.mod h1:HHb6Yd8spzm6aFZU6jwBj9qFvVUNNkx5nGbjG4UHeOE= +github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-migrate/migrate/v4 v4.18.2 h1:2VSCMz7x7mjyTXx3m2zPokOY82LTRgxK1yQYKo6wWQ8= github.com/golang-migrate/migrate/v4 v4.18.2/go.mod h1:2CM6tJvn2kqPXwnXO/d3rAQYiyoIm180VsO8PRX6Rpk= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= +github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= +github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= +github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= +github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= +github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= +github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= +github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= +github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= +github.com/googleapis/go-sql-spanner v1.11.2 h1:G5mvQXSuuoISyElJuz6Ce1ax6GO/2yDoks+wA/PBKVM= +github.com/googleapis/go-sql-spanner v1.11.2/go.mod h1:PMXTYVjFrP4zX1HB+M6hBkiEF6CUKUZP4DeJG1SvWQ0= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -87,6 +896,11 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY 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/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ilyakaznacheev/cleanenv v1.5.0 h1:0VNZXggJE2OYdXE87bfSSwGxeiGt9moSR2lOrsHHvr4= github.com/ilyakaznacheev/cleanenv v1.5.0/go.mod h1:a5aDzaJrLCQZsazHol1w8InnDcOX0OColm64SlIi6gk= github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0= @@ -103,12 +917,28 @@ github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= +github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= @@ -119,17 +949,25 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69 github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= +github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= +github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= @@ -154,17 +992,37 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= +github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= 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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= +github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= @@ -175,15 +1033,25 @@ github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= 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/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= @@ -193,14 +1061,36 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/detectors/gcp v1.35.0 h1:bGvFt68+KTiAKFlacHW6AhA56GF2rS0bdD3aJYEnmzA= +go.opentelemetry.io/contrib/detectors/gcp v1.35.0/go.mod h1:qGWP8/+ILwMRIUf9uIVLloR1uo5ZYAslM4O6OqUi1DA= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= @@ -213,8 +1103,13 @@ go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0Qe go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= +go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= @@ -226,69 +1121,715 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8 go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20220827204233-334a2380cb91/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20220302094943-723b81ca9867/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= +golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= +golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= +golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/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.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/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-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/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-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/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-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= +gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= +gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= +google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= +google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= +google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= +google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= +google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= +google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= +google.golang.org/api v0.99.0/go.mod h1:1YOf74vkVndF7pG6hIHuINsM7eWwpVTAfNMNiL91A08= +google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= +google.golang.org/api v0.102.0/go.mod h1:3VFl6/fzoA+qNuS1N1/VfXY4LjoXN/wzeIp7TweWwGo= +google.golang.org/api v0.103.0/go.mod h1:hGtW6nK1AC+d9si/UBhw8Xli+QMOf6xyNAyJw4qU9w0= +google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.107.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= +google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= +google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= +google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= +google.golang.org/api v0.226.0 h1:9A29y1XUD+YRXfnHkO66KggxHBZWg9LsTGqm7TkUvtQ= +google.golang.org/api v0.226.0/go.mod h1:WP/0Xm4LVvMOCldfvOISnWquSRWbG2kArDZcg+W2DbY= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= +google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= +google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= +google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= +google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= +google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= +google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= +google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= +google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= +google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20221024153911-1573dae28c9c/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221024183307-1bc688fe9f3e/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= +google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= +google.golang.org/genproto v0.0.0-20221109142239-94d6d90a7d66/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221117204609-8f9c96812029/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201164419-0e50fba7f41c/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221201204527-e3fa12d562f3/go.mod h1:rZS5c/ZVYMaOGBfO68GWtjOw/eLaZM1X6iVtgjZ+EWg= +google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE= +google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230112194545-e10362b5ecf9/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230113154510-dbe35b8444a5/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230123190316-2c411cf9d197/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230124163310-31e0e69b6fc2/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230127162408-596548ed4efa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44/go.mod h1:8B0gmkoRebU8ukX6HP+4wrVQUY1+6PkQ44BSyIlflHA= +google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= +google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= +google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= +google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= +google.golang.org/genproto v0.0.0-20250313205543-e70fdf4c4cb4 h1:kCjWYliqPA8g5z87mbjnf/cdgQqMzBfp9xYre5qKu2A= +google.golang.org/genproto v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:SqIx1NV9hcvqdLHo7uNZDS5lrUJybQ3evo3+z/WBfA0= google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0= google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU= +google.golang.org/genproto/googleapis/api v0.0.0-20250313205543-e70fdf4c4cb4 h1:IFnXJq3UPB3oBREOodn1v1aGQeZYQclEmvWRMN0PSsY= +google.golang.org/genproto/googleapis/api v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:c8q6Z6OCqnfVIqUFJkCzKcrj8eCvUrz+K4KRzSTuANg= google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= +google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +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.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/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= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.36.2/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= +modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= +modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= +modernc.org/ccgo/v3 v3.16.4/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ= +modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aws= +modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= +modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A= +modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU= +modernc.org/libc v1.16.17/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU= +modernc.org/libc v1.16.19/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA= +modernc.org/libc v1.17.0/go.mod h1:XsgLldpP4aWlPlsjqKRdHPqCxCjISdHfM/yeWC5GyW0= +modernc.org/libc v1.17.1/go.mod h1:FZ23b+8LjxZs7XtFMbSzL/EhPxNbfZbErxEHc7cbD9s= +modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/memory v1.2.0/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw= +modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= +modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw= +modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 h1:slmdOY3vp8a7KQbHkL+FLbvbkgMqmXojpFUO/jENuqQ= olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internal/rpc/login/handler_test.go b/internal/rpc/login/handler_test.go index 3599249..9b99d1c 100644 --- a/internal/rpc/login/handler_test.go +++ b/internal/rpc/login/handler_test.go @@ -11,7 +11,9 @@ import ( "github.com/bjlag/go-keeper/internal/infrastructure/store/server/user" rpcLogin "github.com/bjlag/go-keeper/internal/rpc/login" "github.com/bjlag/go-keeper/internal/usecase/server/user/login" + "github.com/go-testfixtures/testfixtures/v3" "github.com/jmoiron/sqlx" + "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" "google.golang.org/grpc" @@ -62,7 +64,7 @@ func TestHandler_Handle(t *testing.T) { Env: map[string]string{ "POSTGRES_USER": "postgres", "POSTGRES_PASSWORD": "secret", - "POSTGRES_DB": "master", + "POSTGRES_DB": "master_test", }, ExposedPorts: []string{"5432/tcp"}, Image: "postgres:16.4-alpine3.20", @@ -90,7 +92,7 @@ func TestHandler_Handle(t *testing.T) { log.Fatal(err) } - db, err := pg.New(pg.GetDSN(host, mappedPort.Port(), "master", "postgres", "secret")).Connect() + db, err := pg.New(pg.GetDSN(host, mappedPort.Port(), "master_test", "postgres", "secret")).Connect() if err != nil { log.Fatal(err) } @@ -98,12 +100,6 @@ func TestHandler_Handle(t *testing.T) { _ = db.Close() }() - var cfg migrator.Config - cfg.Database.Type = "pg" - cfg.MigrationsTable = "migrations" - cfg.SourcePath = "./migrations/server" - cfg.Database.Name = "master" - _, filename, _, _ := runtime.Caller(0) baseDir := path.Join(path.Dir(filename), "..", "..", "..") @@ -112,14 +108,7 @@ func TestHandler_Handle(t *testing.T) { log.Fatal(err) } - dir, err := os.Getwd() - if err != nil { - log.Fatal(err) - } - - _ = dir - - m, err := migrator.Get(db, "pg", "master", "./migrations/server", "migrations") + m, err := migrator.Get(db, "pg", "master_test", "./migrations/server", "migrations") if err != nil { log.Fatal(err) } @@ -128,6 +117,15 @@ func TestHandler_Handle(t *testing.T) { log.Fatal(err) } + fixtures, err := testfixtures.New( + testfixtures.Database(db.DB), + testfixtures.Dialect("postgres"), + testfixtures.Directory("test/fixture"), + ) + + require.NoError(t, err) + require.NoError(t, fixtures.Load()) + conn, err := grpc.NewClient("passthrough://bufnet", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithContextDialer(dialer(ctx, db))) if err != nil { log.Fatal(err) @@ -145,8 +143,8 @@ func TestHandler_Handle(t *testing.T) { t.Run("success", func(t *testing.T) { out, err := client.Login(ctx, &rpc.LoginIn{ - Email: "test@test.com", - Password: "test", + Email: "test@test.ru", + Password: "12345678", }) if err != nil { diff --git a/test/fixture/users.yaml b/test/fixture/users.yaml new file mode 100644 index 0000000..59f3a4e --- /dev/null +++ b/test/fixture/users.yaml @@ -0,0 +1,5 @@ +- guid: bf4e6232-f1ae-41da-8535-73048891b1e3 + email: test@test.ru + password_hash: $2a$04$OxrD4jeA4SrmsnQOdYH4iu1dfsydv36kbBsmQSXZBDtDTca6Vef2K + created_at: 2025-03-15 13:00:00 + updated_at: 2025-03-15 13:00:00 \ No newline at end of file From 6d6bcc0c8090f799191a54bc4a6c255501d7e772 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Mon, 17 Mar 2025 18:44:19 +0300 Subject: [PATCH 74/94] tests: init --- .golangci.yml | 1 + .../functional/login/login_test.go | 15 +++------------ test/util/init/init.go | 16 ++++++++++++++++ 3 files changed, 20 insertions(+), 12 deletions(-) rename internal/rpc/login/handler_test.go => test/functional/login/login_test.go (95%) create mode 100644 test/util/init/init.go diff --git a/.golangci.yml b/.golangci.yml index 48ecc83..d2dd5af 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -26,6 +26,7 @@ linters: - mnd - godox - gocyclo + - gochecknoinits linters-settings: revive: diff --git a/internal/rpc/login/handler_test.go b/test/functional/login/login_test.go similarity index 95% rename from internal/rpc/login/handler_test.go rename to test/functional/login/login_test.go index 9b99d1c..40a76bd 100644 --- a/internal/rpc/login/handler_test.go +++ b/test/functional/login/login_test.go @@ -7,7 +7,6 @@ import ( "github.com/bjlag/go-keeper/internal/infrastructure/db/pg" "github.com/bjlag/go-keeper/internal/infrastructure/logger" "github.com/bjlag/go-keeper/internal/infrastructure/migrator" - server2 "github.com/bjlag/go-keeper/internal/infrastructure/rpc/server" "github.com/bjlag/go-keeper/internal/infrastructure/store/server/user" rpcLogin "github.com/bjlag/go-keeper/internal/rpc/login" "github.com/bjlag/go-keeper/internal/usecase/server/user/login" @@ -21,11 +20,11 @@ import ( "google.golang.org/grpc/test/bufconn" "log" "net" - "os" - "path" - "runtime" "testing" "time" + + server2 "github.com/bjlag/go-keeper/internal/infrastructure/rpc/server" + _ "github.com/bjlag/go-keeper/test/util/init" ) func dialer(ctx context.Context, db *sqlx.DB) func(context.Context, string) (net.Conn, error) { @@ -100,14 +99,6 @@ func TestHandler_Handle(t *testing.T) { _ = db.Close() }() - _, filename, _, _ := runtime.Caller(0) - baseDir := path.Join(path.Dir(filename), "..", "..", "..") - - err = os.Chdir(baseDir) - if err != nil { - log.Fatal(err) - } - m, err := migrator.Get(db, "pg", "master_test", "./migrations/server", "migrations") if err != nil { log.Fatal(err) diff --git a/test/util/init/init.go b/test/util/init/init.go new file mode 100644 index 0000000..11e96d7 --- /dev/null +++ b/test/util/init/init.go @@ -0,0 +1,16 @@ +package init + +import ( + "os" + "path" + "runtime" +) + +func init() { + _, filename, _, _ := runtime.Caller(0) //nolint:dogsled + dir := path.Join(path.Dir(filename), "..", "..", "..") + err := os.Chdir(dir) + if err != nil { + panic(err) + } +} From 1b5b3744ee2fb53739c8fff2704d05b55fca3f8b Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Mon, 17 Mar 2025 19:10:18 +0300 Subject: [PATCH 75/94] tests: pg container refactoring --- test/functional/login/login_test.go | 45 +++++----------- test/util/container/container.go | 83 +++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 32 deletions(-) create mode 100644 test/util/container/container.go diff --git a/test/functional/login/login_test.go b/test/functional/login/login_test.go index 40a76bd..6393f9a 100644 --- a/test/functional/login/login_test.go +++ b/test/functional/login/login_test.go @@ -10,11 +10,10 @@ import ( "github.com/bjlag/go-keeper/internal/infrastructure/store/server/user" rpcLogin "github.com/bjlag/go-keeper/internal/rpc/login" "github.com/bjlag/go-keeper/internal/usecase/server/user/login" + container2 "github.com/bjlag/go-keeper/test/util/container" "github.com/go-testfixtures/testfixtures/v3" "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" - "github.com/testcontainers/testcontainers-go" - "github.com/testcontainers/testcontainers-go/wait" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/test/bufconn" @@ -58,40 +57,22 @@ func TestHandler_Handle(t *testing.T) { // todo из конфига берем данные для базы - req := testcontainers.GenericContainerRequest{ - ContainerRequest: testcontainers.ContainerRequest{ - Env: map[string]string{ - "POSTGRES_USER": "postgres", - "POSTGRES_PASSWORD": "secret", - "POSTGRES_DB": "master_test", - }, - ExposedPorts: []string{"5432/tcp"}, - Image: "postgres:16.4-alpine3.20", - WaitingFor: wait.ForExec([]string{"pg_isready"}). - WithPollInterval(2 * time.Second). - WithExitCodeMatcher(func(exitCode int) bool { - return exitCode == 0 - }), - }, - Started: true, - } - - container, err := testcontainers.GenericContainer(ctx, req) - if err != nil { - log.Fatal(err) - } - - host, err := container.Host(ctx) - if err != nil { - log.Fatal(err) - } + const ( + migrationsSourcePath = "./migrations/server" + migrationsTable = "migrations" + ) - mappedPort, err := container.MappedPort(ctx, "5432") + pgContainer, err := container2.NewPostgreSQLContainer(ctx, container2.PostgreSQLConfig{ + Database: "master_test", + Username: "postgres", + Password: "secret", + ImageTag: "16.4-alpine3.20", + }) if err != nil { log.Fatal(err) } - db, err := pg.New(pg.GetDSN(host, mappedPort.Port(), "master_test", "postgres", "secret")).Connect() + db, err := pg.New(pg.GetDSN(pgContainer.Host, pgContainer.Port, pgContainer.Database, pgContainer.Username, pgContainer.Password)).Connect() if err != nil { log.Fatal(err) } @@ -99,7 +80,7 @@ func TestHandler_Handle(t *testing.T) { _ = db.Close() }() - m, err := migrator.Get(db, "pg", "master_test", "./migrations/server", "migrations") + m, err := migrator.Get(db, migrator.TypePG, pgContainer.Database, migrationsSourcePath, migrationsTable) if err != nil { log.Fatal(err) } diff --git a/test/util/container/container.go b/test/util/container/container.go new file mode 100644 index 0000000..00fe6b9 --- /dev/null +++ b/test/util/container/container.go @@ -0,0 +1,83 @@ +package container + +import ( + "context" + "fmt" + "time" + + "github.com/docker/go-connections/nat" + + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +type ( + PostgreSQLContainer struct { + testcontainers.Container + Port string + Host string + Database string + Username string + Password string + } + + PostgreSQLConfig struct { + Database string + Username string + Password string + ImageTag string + } +) + +func NewPostgreSQLContainer(ctx context.Context, cfg PostgreSQLConfig) (*PostgreSQLContainer, error) { + const ( + image = "postgres" + port = "5432" + ) + + containerPort := fmt.Sprintf("%s/tcp", port) + + req := testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + Env: map[string]string{ + "POSTGRES_USER": cfg.Username, + "POSTGRES_PASSWORD": cfg.Password, + "POSTGRES_DB": cfg.Database, + }, + ExposedPorts: []string{ + containerPort, + }, + Image: fmt.Sprintf("%s:%s", image, cfg.ImageTag), + WaitingFor: wait.ForExec([]string{"pg_isready"}). + WithPollInterval(2 * time.Second). + WithExitCodeMatcher(func(exitCode int) bool { + return exitCode == 0 + }), + }, + Started: true, + } + + container, err := testcontainers.GenericContainer(ctx, req) + if err != nil { + return nil, err + } + + host, err := container.Host(ctx) + if err != nil { + return nil, err + } + + mappedPort, err := container.MappedPort(ctx, nat.Port(containerPort)) + if err != nil { + return nil, err + } + + return &PostgreSQLContainer{ + Container: container, + Host: host, + Port: mappedPort.Port(), + Database: cfg.Database, + Username: cfg.Username, + Password: cfg.Password, + }, nil +} From 58eda0a38f9f85914150654298c4f2591fac9ca7 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Mon, 17 Mar 2025 19:24:30 +0300 Subject: [PATCH 76/94] tests: fixture refactoring --- config/server_test.yaml | 17 +++++++++++++++++ test/functional/login/login_test.go | 27 +++++++-------------------- test/util/container/container.go | 21 ++++++++++++--------- test/util/fixture/fixture.go | 24 ++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 29 deletions(-) create mode 100644 config/server_test.yaml create mode 100644 test/util/fixture/fixture.go diff --git a/config/server_test.yaml b/config/server_test.yaml new file mode 100644 index 0000000..5147d97 --- /dev/null +++ b/config/server_test.yaml @@ -0,0 +1,17 @@ +address: + host: localhost + port: 8080 + +auth: + accessTokenExp: 10s + refreshTokenExp: 2h # 30 days + secretKey: secret + +container: + pg: + + host: localhost + port: 5444 + name: master + user: postgres + password: secret \ No newline at end of file diff --git a/test/functional/login/login_test.go b/test/functional/login/login_test.go index 6393f9a..04311b9 100644 --- a/test/functional/login/login_test.go +++ b/test/functional/login/login_test.go @@ -11,7 +11,7 @@ import ( rpcLogin "github.com/bjlag/go-keeper/internal/rpc/login" "github.com/bjlag/go-keeper/internal/usecase/server/user/login" container2 "github.com/bjlag/go-keeper/test/util/container" - "github.com/go-testfixtures/testfixtures/v3" + "github.com/bjlag/go-keeper/test/util/fixture" "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" "google.golang.org/grpc" @@ -73,30 +73,17 @@ func TestHandler_Handle(t *testing.T) { } db, err := pg.New(pg.GetDSN(pgContainer.Host, pgContainer.Port, pgContainer.Database, pgContainer.Username, pgContainer.Password)).Connect() - if err != nil { - log.Fatal(err) - } - defer func() { - _ = db.Close() - }() + require.NoError(t, err) + defer db.Close() m, err := migrator.Get(db, migrator.TypePG, pgContainer.Database, migrationsSourcePath, migrationsTable) - if err != nil { - log.Fatal(err) - } - - if err := m.Up(); err != nil { - log.Fatal(err) - } + require.NoError(t, err) - fixtures, err := testfixtures.New( - testfixtures.Database(db.DB), - testfixtures.Dialect("postgres"), - testfixtures.Directory("test/fixture"), - ) + err = m.Up() + require.NoError(t, err) + err = fixture.Load(db, "test/fixture") require.NoError(t, err) - require.NoError(t, fixtures.Load()) conn, err := grpc.NewClient("passthrough://bufnet", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithContextDialer(dialer(ctx, db))) if err != nil { diff --git a/test/util/container/container.go b/test/util/container/container.go index 00fe6b9..333fe51 100644 --- a/test/util/container/container.go +++ b/test/util/container/container.go @@ -12,23 +12,26 @@ import ( ) type ( + // PostgreSQLContainer хранит информацию о готовом контейнере. PostgreSQLContainer struct { testcontainers.Container - Port string - Host string - Database string - Username string - Password string + Port string // Port порт по которому можно подключиться к БД. + Host string // Host хост на котором будет доступна БД. + Database string // Database название БД. + Username string // Username пользователь БД. + Password string // Password пароль пользователя БД. } + // PostgreSQLConfig хранит конфигурацию контейнера. PostgreSQLConfig struct { - Database string - Username string - Password string - ImageTag string + Database string // Database название базы данных для переменной окружения контейнера POSTGRES_DB. + Username string // Username пользователь для переменной окружения контейнера POSTGRES_USER. + Password string // Password пароль для переменной окружения контейнера POSTGRES_PASSWORD. + ImageTag string // ImageTag тег докер образа. } ) +// NewPostgreSQLContainer создает контейнера для базы данных PostgreSQL по переданной конфигурации. func NewPostgreSQLContainer(ctx context.Context, cfg PostgreSQLConfig) (*PostgreSQLContainer, error) { const ( image = "postgres" diff --git a/test/util/fixture/fixture.go b/test/util/fixture/fixture.go new file mode 100644 index 0000000..a936af5 --- /dev/null +++ b/test/util/fixture/fixture.go @@ -0,0 +1,24 @@ +package fixture + +import ( + "github.com/go-testfixtures/testfixtures/v3" + "github.com/jmoiron/sqlx" +) + +// Load загружает в БД по переданному подключению db фикстуры из директории dir. +func Load(db *sqlx.DB, dir string) error { + fixtures, err := testfixtures.New( + testfixtures.Database(db.DB), + testfixtures.Dialect("postgres"), + testfixtures.Directory(dir), + ) + if err != nil { + return err + } + + if err := fixtures.Load(); err != nil { + return err + } + + return nil +} From 32b22ed5fe03ef9849b17a5cd6115140c1ffb6fc Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Mon, 17 Mar 2025 19:57:37 +0300 Subject: [PATCH 77/94] tests: config --- .gitignore | 2 ++ config/server_test.yaml | 17 ----------- config/server_test.yaml.dist | 10 +++++++ test/config/config.go | 17 +++++++++++ test/functional/login/login_test.go | 30 ++++++++++--------- .../container/container.go | 0 .../fixture/fixture.go | 0 test/{util => infrastructure}/init/init.go | 0 8 files changed, 45 insertions(+), 31 deletions(-) delete mode 100644 config/server_test.yaml create mode 100644 config/server_test.yaml.dist create mode 100644 test/config/config.go rename test/{util => infrastructure}/container/container.go (100%) rename test/{util => infrastructure}/fixture/fixture.go (100%) rename test/{util => infrastructure}/init/init.go (100%) diff --git a/.gitignore b/.gitignore index 054effb..fdc7541 100644 --- a/.gitignore +++ b/.gitignore @@ -33,7 +33,9 @@ vendor/ config/migrator_client.yaml config/migrator_server.yaml config/server.yaml +config/server_test.yaml config/client.yaml +config/client_test.yaml # log *.log diff --git a/config/server_test.yaml b/config/server_test.yaml deleted file mode 100644 index 5147d97..0000000 --- a/config/server_test.yaml +++ /dev/null @@ -1,17 +0,0 @@ -address: - host: localhost - port: 8080 - -auth: - accessTokenExp: 10s - refreshTokenExp: 2h # 30 days - secretKey: secret - -container: - pg: - - host: localhost - port: 5444 - name: master - user: postgres - password: secret \ No newline at end of file diff --git a/config/server_test.yaml.dist b/config/server_test.yaml.dist new file mode 100644 index 0000000..b454913 --- /dev/null +++ b/config/server_test.yaml.dist @@ -0,0 +1,10 @@ +migration: + sourcePath: "./migrations/server" + table: "migrations" + +container: + pg: + tag: 16.4-alpine3.20 + dbName: master_test + dbUser: postgres + dbPassword: secret \ No newline at end of file diff --git a/test/config/config.go b/test/config/config.go new file mode 100644 index 0000000..9fc8f7c --- /dev/null +++ b/test/config/config.go @@ -0,0 +1,17 @@ +package config + +type Config struct { + Migration struct { + SourcePath string `yaml:"sourcePath" env:"MIGRATION_SOURCE_PATH" env-description:"Path to migration source" json:"source_path"` + Table string `yaml:"table" env:"MIGRATION_TABLE" env-description:"Migration table" json:"table"` + } `yaml:"migration" json:"migration"` + + Container struct { + PG struct { + Tag string `yaml:"tag" env:"CONTAINER_PG_TAG" env-description:"Container image tag" json:"tag"` + DBName string `yaml:"dbName" env:"CONTAINER_PG_DB_NAME" env-description:"DB name" json:"db_name"` + DBUser string `yaml:"dbUser" env:"CONTAINER_PG_DB_USER" env-description:"DB user" json:"db_user"` + DBPassword string `yaml:"dbPassword" env:"CONTAINER_PG_DB_PASSWORD" env-description:"DB password" json:"db_password"` + } `yaml:"pg" json:"pg"` + } `yaml:"container" json:"container"` +} diff --git a/test/functional/login/login_test.go b/test/functional/login/login_test.go index 04311b9..1eed68a 100644 --- a/test/functional/login/login_test.go +++ b/test/functional/login/login_test.go @@ -10,8 +10,10 @@ import ( "github.com/bjlag/go-keeper/internal/infrastructure/store/server/user" rpcLogin "github.com/bjlag/go-keeper/internal/rpc/login" "github.com/bjlag/go-keeper/internal/usecase/server/user/login" - container2 "github.com/bjlag/go-keeper/test/util/container" - "github.com/bjlag/go-keeper/test/util/fixture" + "github.com/bjlag/go-keeper/test/config" + container2 "github.com/bjlag/go-keeper/test/infrastructure/container" + "github.com/bjlag/go-keeper/test/infrastructure/fixture" + "github.com/ilyakaznacheev/cleanenv" "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" "google.golang.org/grpc" @@ -23,7 +25,7 @@ import ( "time" server2 "github.com/bjlag/go-keeper/internal/infrastructure/rpc/server" - _ "github.com/bjlag/go-keeper/test/util/init" + _ "github.com/bjlag/go-keeper/test/infrastructure/init" ) func dialer(ctx context.Context, db *sqlx.DB) func(context.Context, string) (net.Conn, error) { @@ -53,20 +55,20 @@ func dialer(ctx context.Context, db *sqlx.DB) func(context.Context, string) (net } func TestHandler_Handle(t *testing.T) { - ctx := context.Background() + const pathToConfig = "./config/server_test.yaml" - // todo из конфига берем данные для базы + ctx := context.Background() - const ( - migrationsSourcePath = "./migrations/server" - migrationsTable = "migrations" - ) + var cfg config.Config + if err := cleanenv.ReadConfig(pathToConfig, &cfg); err != nil { + panic(err) + } pgContainer, err := container2.NewPostgreSQLContainer(ctx, container2.PostgreSQLConfig{ - Database: "master_test", - Username: "postgres", - Password: "secret", - ImageTag: "16.4-alpine3.20", + Database: cfg.Container.PG.DBName, + Username: cfg.Container.PG.DBUser, + Password: cfg.Container.PG.DBPassword, + ImageTag: cfg.Container.PG.Tag, }) if err != nil { log.Fatal(err) @@ -76,7 +78,7 @@ func TestHandler_Handle(t *testing.T) { require.NoError(t, err) defer db.Close() - m, err := migrator.Get(db, migrator.TypePG, pgContainer.Database, migrationsSourcePath, migrationsTable) + m, err := migrator.Get(db, migrator.TypePG, pgContainer.Database, cfg.Migration.SourcePath, cfg.Migration.Table) require.NoError(t, err) err = m.Up() diff --git a/test/util/container/container.go b/test/infrastructure/container/container.go similarity index 100% rename from test/util/container/container.go rename to test/infrastructure/container/container.go diff --git a/test/util/fixture/fixture.go b/test/infrastructure/fixture/fixture.go similarity index 100% rename from test/util/fixture/fixture.go rename to test/infrastructure/fixture/fixture.go diff --git a/test/util/init/init.go b/test/infrastructure/init/init.go similarity index 100% rename from test/util/init/init.go rename to test/infrastructure/init/init.go From c2868269ed1f7c519aa94248d3e3c32d32a18cd2 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Mon, 17 Mar 2025 20:18:45 +0300 Subject: [PATCH 78/94] tests: test suite --- test/functional/login/login_test.go | 154 ++++++++++++++++------------ 1 file changed, 90 insertions(+), 64 deletions(-) diff --git a/test/functional/login/login_test.go b/test/functional/login/login_test.go index 1eed68a..fbce5e9 100644 --- a/test/functional/login/login_test.go +++ b/test/functional/login/login_test.go @@ -2,108 +2,134 @@ package login_test import ( "context" + "log" + "net" + "testing" + "time" + + "github.com/ilyakaznacheev/cleanenv" + "github.com/jmoiron/sqlx" + "github.com/stretchr/testify/suite" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/test/bufconn" + "github.com/bjlag/go-keeper/internal/generated/rpc" "github.com/bjlag/go-keeper/internal/infrastructure/auth" "github.com/bjlag/go-keeper/internal/infrastructure/db/pg" "github.com/bjlag/go-keeper/internal/infrastructure/logger" "github.com/bjlag/go-keeper/internal/infrastructure/migrator" + "github.com/bjlag/go-keeper/internal/infrastructure/rpc/server" "github.com/bjlag/go-keeper/internal/infrastructure/store/server/user" rpcLogin "github.com/bjlag/go-keeper/internal/rpc/login" "github.com/bjlag/go-keeper/internal/usecase/server/user/login" "github.com/bjlag/go-keeper/test/config" - container2 "github.com/bjlag/go-keeper/test/infrastructure/container" + "github.com/bjlag/go-keeper/test/infrastructure/container" "github.com/bjlag/go-keeper/test/infrastructure/fixture" - "github.com/ilyakaznacheev/cleanenv" - "github.com/jmoiron/sqlx" - "github.com/stretchr/testify/require" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" - "google.golang.org/grpc/test/bufconn" - "log" - "net" - "testing" - "time" - - server2 "github.com/bjlag/go-keeper/internal/infrastructure/rpc/server" _ "github.com/bjlag/go-keeper/test/infrastructure/init" ) -func dialer(ctx context.Context, db *sqlx.DB) func(context.Context, string) (net.Conn, error) { - listener := bufconn.Listen(1204 * 1024) - - jwt := auth.NewJWT("secret", 15*time.Minute, 15*time.Minute) - - userStore := user.NewStore(db) - ucLogin := login.NewUsecase(userStore, jwt) - - server := server2.NewRPCServer( - server2.WithListener(listener), - server2.WithLogger(logger.Get("test")), - - server2.WithHandler(server2.LoginMethod, rpcLogin.New(ucLogin).Handle), - ) - - go func() { - if err := server.Start(ctx); err != nil { - log.Fatal(err) - } - }() +type TestSuite struct { + suite.Suite + pgContainer *container.PostgreSQLContainer + db *sqlx.DB + conn *grpc.ClientConn + client rpc.KeeperClient +} - return func(context.Context, string) (net.Conn, error) { - return listener.Dial() - } +func TestSuite_Run(t *testing.T) { + suite.Run(t, new(TestSuite)) } -func TestHandler_Handle(t *testing.T) { +func (s *TestSuite) SetupSuite() { const pathToConfig = "./config/server_test.yaml" - ctx := context.Background() + ctx, ctxCancel := context.WithTimeout(context.Background(), 30*time.Second) + defer ctxCancel() + // load config var cfg config.Config - if err := cleanenv.ReadConfig(pathToConfig, &cfg); err != nil { - panic(err) - } + err := cleanenv.ReadConfig(pathToConfig, &cfg) + s.Require().NoError(err) - pgContainer, err := container2.NewPostgreSQLContainer(ctx, container2.PostgreSQLConfig{ + // crate db container + pgContainer, err := container.NewPostgreSQLContainer(ctx, container.PostgreSQLConfig{ Database: cfg.Container.PG.DBName, Username: cfg.Container.PG.DBUser, Password: cfg.Container.PG.DBPassword, ImageTag: cfg.Container.PG.Tag, }) - if err != nil { - log.Fatal(err) - } + s.Require().NoError(err) + + s.pgContainer = pgContainer + // get db connection db, err := pg.New(pg.GetDSN(pgContainer.Host, pgContainer.Port, pgContainer.Database, pgContainer.Username, pgContainer.Password)).Connect() - require.NoError(t, err) - defer db.Close() + s.Require().NoError(err) + + s.db = db + // apply migrations m, err := migrator.Get(db, migrator.TypePG, pgContainer.Database, cfg.Migration.SourcePath, cfg.Migration.Table) - require.NoError(t, err) + s.Require().NoError(err) err = m.Up() - require.NoError(t, err) - - err = fixture.Load(db, "test/fixture") - require.NoError(t, err) + s.Require().NoError(err) + // create grpc client conn, err := grpc.NewClient("passthrough://bufnet", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithContextDialer(dialer(ctx, db))) - if err != nil { - log.Fatal(err) + s.Require().NoError(err) + + s.client = rpc.NewKeeperClient(conn) +} + +func (s *TestSuite) TearDownSuite() { + ctx, ctxCancel := context.WithTimeout(context.Background(), 5*time.Second) + defer ctxCancel() + + s.Require().NoError(s.pgContainer.Terminate(ctx)) + + err := s.conn.Close() + s.Require().NoError(err) + + err = s.db.Close() + s.Require().NoError(err) +} + +func dialer(ctx context.Context, db *sqlx.DB) func(context.Context, string) (net.Conn, error) { + listener := bufconn.Listen(1204 * 1024) + + jwt := auth.NewJWT("secret", 15*time.Minute, 15*time.Minute) + + userStore := user.NewStore(db) + ucLogin := login.NewUsecase(userStore, jwt) + + server := server.NewRPCServer( + server.WithListener(listener), + server.WithLogger(logger.Get("test")), + + server.WithHandler(server.LoginMethod, rpcLogin.New(ucLogin).Handle), + ) + + go func() { + if err := server.Start(ctx); err != nil { + log.Fatal(err) + } + }() + + return func(context.Context, string) (net.Conn, error) { + return listener.Dial() } - defer conn.Close() +} - client := rpc.NewKeeperClient(conn) +func (s *TestSuite) TestHandler_Handle() { + ctx := context.Background() - // todo выключение логгера - // todo конфиги для тестов - // todo тестовое приложение - // todo своя база - // todo фикстуры базы - // todo test suit, tear down + err := fixture.Load(s.db, "test/fixture") + s.Require().NoError(err) - t.Run("success", func(t *testing.T) { - out, err := client.Login(ctx, &rpc.LoginIn{ + s.Run("success", func() { + out, err := s.client.Login(ctx, &rpc.LoginIn{ Email: "test@test.ru", Password: "12345678", }) From e79595195f14be5ed93918f183f01ccb90f42859 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Mon, 17 Mar 2025 20:23:19 +0300 Subject: [PATCH 79/94] tests: test rpc server --- cmd/server/main.go | 23 ++++++++- config/server_test.yaml.dist | 7 +++ internal/app/server/app.go | 49 +++++++------------- internal/infrastructure/rpc/server/server.go | 3 +- test/config/config.go | 17 ------- test/functional/login/login_test.go | 46 +++++------------- test/infrastructure/config/config.go | 42 +++++++++++++++++ test/infrastructure/server/server.go | 27 +++++++++++ 8 files changed, 128 insertions(+), 86 deletions(-) delete mode 100644 test/config/config.go create mode 100644 test/infrastructure/config/config.go create mode 100644 test/infrastructure/server/server.go diff --git a/cmd/server/main.go b/cmd/server/main.go index a3caabe..46e27b7 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -7,7 +7,9 @@ package main import ( "context" "flag" + "fmt" logNative "log" + "net" "os/signal" "syscall" @@ -15,6 +17,8 @@ import ( "go.uber.org/zap" "github.com/bjlag/go-keeper/internal/app/server" + "github.com/bjlag/go-keeper/internal/infrastructure/auth" + "github.com/bjlag/go-keeper/internal/infrastructure/db/pg" "github.com/bjlag/go-keeper/internal/infrastructure/logger" ) @@ -50,8 +54,25 @@ func main() { ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) defer cancel() - err := server.NewApp(cfg, log).Run(ctx) + dbCfg := cfg.Database + db, err := pg.New(pg.GetDSN(dbCfg.Host, dbCfg.Port, dbCfg.Name, dbCfg.User, dbCfg.Password)).Connect() if err != nil { + log.Error("Failed to get db connection", zap.Error(err)) + panic(err) + } + defer func() { + _ = db.Close() + }() + + jwt := auth.NewJWT(cfg.Auth.SecretKey, cfg.Auth.AccessTokenExp, cfg.Auth.RefreshTokenExp) + + listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", cfg.Address.Host, cfg.Address.Port)) + if err != nil { + log.Error("Failed to listen", zap.Error(err)) + panic(err) + } + + if err = server.NewApp(db, jwt, listener, log).Run(ctx); err != nil { panic(err) } } diff --git a/config/server_test.yaml.dist b/config/server_test.yaml.dist index b454913..2916cb0 100644 --- a/config/server_test.yaml.dist +++ b/config/server_test.yaml.dist @@ -1,7 +1,14 @@ +env: test + migration: sourcePath: "./migrations/server" table: "migrations" +auth: + accessTokenExp: 5m + refreshTokenExp: 2h + secretKey: secret + container: pg: tag: 16.4-alpine3.20 diff --git a/internal/app/server/app.go b/internal/app/server/app.go index eb48cca..ea6387f 100644 --- a/internal/app/server/app.go +++ b/internal/app/server/app.go @@ -6,10 +6,10 @@ import ( "fmt" "net" + "github.com/jmoiron/sqlx" "go.uber.org/zap" "github.com/bjlag/go-keeper/internal/infrastructure/auth" - "github.com/bjlag/go-keeper/internal/infrastructure/db/pg" "github.com/bjlag/go-keeper/internal/infrastructure/rpc/server" "github.com/bjlag/go-keeper/internal/infrastructure/store/server/item" "github.com/bjlag/go-keeper/internal/infrastructure/store/server/user" @@ -32,37 +32,30 @@ import ( ) type App struct { - cfg Config - log *zap.Logger + db *sqlx.DB + jwt *auth.JWT + listener net.Listener + log *zap.Logger } -func NewApp(cfg Config, log *zap.Logger) *App { +func NewApp(db *sqlx.DB, jwt *auth.JWT, listener net.Listener, log *zap.Logger) *App { return &App{ - cfg: cfg, - log: log, + db: db, + jwt: jwt, + listener: listener, + log: log, } } func (a *App) Run(ctx context.Context) error { const op = "app.Run" - dbConf := a.cfg.Database - db, err := pg.New(pg.GetDSN(dbConf.Host, dbConf.Port, dbConf.Name, dbConf.User, dbConf.Password)).Connect() - if err != nil { - a.log.Error("Failed to get db connection", zap.Error(err)) - return fmt.Errorf("%s: %w", op, err) - } - defer func() { - _ = db.Close() - }() - - userStore := user.NewStore(db) - dataStore := item.NewStore(db) - jwt := auth.NewJWT(a.cfg.Auth.SecretKey, a.cfg.Auth.AccessTokenExp, a.cfg.Auth.RefreshTokenExp) + userStore := user.NewStore(a.db) + dataStore := item.NewStore(a.db) - ucRegister := register.NewUsecase(userStore, jwt) - ucLogin := login.NewUsecase(userStore, jwt) - ucRefreshTokens := rt.NewUsecase(userStore, jwt) + ucRegister := register.NewUsecase(userStore, a.jwt) + ucLogin := login.NewUsecase(userStore, a.jwt) + ucRefreshTokens := rt.NewUsecase(userStore, a.jwt) ucCreateItem := create.NewUsecase(dataStore) ucUpdateItem := update.NewUsecase(dataStore) ucRemoveItem := remove.NewUsecase(dataStore) @@ -71,14 +64,9 @@ func (a *App) Run(ctx context.Context) error { ucGetByGUID := get_by_guid.NewUsecase(dataStore) ucGetAllData := get_all.NewUsecase(dataStore) - listen, err := net.Listen("tcp", fmt.Sprintf("%s:%d", a.cfg.Address.Host, a.cfg.Address.Port)) - if err != nil { - return fmt.Errorf("%s: %w", op, err) - } - s := server.NewRPCServer( - server.WithListener(listen), - server.WithJWT(jwt), + server.WithListener(a.listener), + server.WithJWT(a.jwt), server.WithLogger(a.log), server.WithHandler(server.RegisterMethod, rpcRegister.New(ucRegister).Handle), @@ -91,8 +79,7 @@ func (a *App) Run(ctx context.Context) error { server.WithHandler(server.DeleteItemMethod, rpcDeleteItem.New(ucRemoveItem).Handle), ) - err = s.Start(ctx) - if err != nil { + if err := s.Start(ctx); err != nil { a.log.Error("Failed to start gRPC server", zap.Error(err)) return fmt.Errorf("%s: %w", op, err) } diff --git a/internal/infrastructure/rpc/server/server.go b/internal/infrastructure/rpc/server/server.go index 88ef714..73fef79 100644 --- a/internal/infrastructure/rpc/server/server.go +++ b/internal/infrastructure/rpc/server/server.go @@ -42,8 +42,7 @@ func (s RPCServer) Start(ctx context.Context) error { const op = "server.rpc.Start" s.log.Info("Starting gRPC server", - zap.String("host", s.host), - zap.Int("port", s.port), + zap.String("addr", s.listener.Addr().String()), ) grpcServer := grpc.NewServer( diff --git a/test/config/config.go b/test/config/config.go deleted file mode 100644 index 9fc8f7c..0000000 --- a/test/config/config.go +++ /dev/null @@ -1,17 +0,0 @@ -package config - -type Config struct { - Migration struct { - SourcePath string `yaml:"sourcePath" env:"MIGRATION_SOURCE_PATH" env-description:"Path to migration source" json:"source_path"` - Table string `yaml:"table" env:"MIGRATION_TABLE" env-description:"Migration table" json:"table"` - } `yaml:"migration" json:"migration"` - - Container struct { - PG struct { - Tag string `yaml:"tag" env:"CONTAINER_PG_TAG" env-description:"Container image tag" json:"tag"` - DBName string `yaml:"dbName" env:"CONTAINER_PG_DB_NAME" env-description:"DB name" json:"db_name"` - DBUser string `yaml:"dbUser" env:"CONTAINER_PG_DB_USER" env-description:"DB user" json:"db_user"` - DBPassword string `yaml:"dbPassword" env:"CONTAINER_PG_DB_PASSWORD" env-description:"DB password" json:"db_password"` - } `yaml:"pg" json:"pg"` - } `yaml:"container" json:"container"` -} diff --git a/test/functional/login/login_test.go b/test/functional/login/login_test.go index fbce5e9..2e5bd76 100644 --- a/test/functional/login/login_test.go +++ b/test/functional/login/login_test.go @@ -2,8 +2,6 @@ package login_test import ( "context" - "log" - "net" "testing" "time" @@ -12,21 +10,17 @@ import ( "github.com/stretchr/testify/suite" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" - "google.golang.org/grpc/test/bufconn" "github.com/bjlag/go-keeper/internal/generated/rpc" "github.com/bjlag/go-keeper/internal/infrastructure/auth" "github.com/bjlag/go-keeper/internal/infrastructure/db/pg" "github.com/bjlag/go-keeper/internal/infrastructure/logger" "github.com/bjlag/go-keeper/internal/infrastructure/migrator" - "github.com/bjlag/go-keeper/internal/infrastructure/rpc/server" - "github.com/bjlag/go-keeper/internal/infrastructure/store/server/user" - rpcLogin "github.com/bjlag/go-keeper/internal/rpc/login" - "github.com/bjlag/go-keeper/internal/usecase/server/user/login" - "github.com/bjlag/go-keeper/test/config" + "github.com/bjlag/go-keeper/test/infrastructure/config" "github.com/bjlag/go-keeper/test/infrastructure/container" "github.com/bjlag/go-keeper/test/infrastructure/fixture" _ "github.com/bjlag/go-keeper/test/infrastructure/init" + "github.com/bjlag/go-keeper/test/infrastructure/server" ) type TestSuite struct { @@ -77,9 +71,17 @@ func (s *TestSuite) SetupSuite() { s.Require().NoError(err) // create grpc client - conn, err := grpc.NewClient("passthrough://bufnet", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithContextDialer(dialer(ctx, db))) + jwt := auth.NewJWT(cfg.Auth.SecretKey, cfg.Auth.AccessTokenExp, cfg.Auth.RefreshTokenExp) + log := logger.Get(cfg.Env) + + conn, err := grpc.NewClient( + "passthrough://bufnet", + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithContextDialer(server.Start(context.Background(), db, jwt, log)), + ) s.Require().NoError(err) + s.conn = conn s.client = rpc.NewKeeperClient(conn) } @@ -96,32 +98,6 @@ func (s *TestSuite) TearDownSuite() { s.Require().NoError(err) } -func dialer(ctx context.Context, db *sqlx.DB) func(context.Context, string) (net.Conn, error) { - listener := bufconn.Listen(1204 * 1024) - - jwt := auth.NewJWT("secret", 15*time.Minute, 15*time.Minute) - - userStore := user.NewStore(db) - ucLogin := login.NewUsecase(userStore, jwt) - - server := server.NewRPCServer( - server.WithListener(listener), - server.WithLogger(logger.Get("test")), - - server.WithHandler(server.LoginMethod, rpcLogin.New(ucLogin).Handle), - ) - - go func() { - if err := server.Start(ctx); err != nil { - log.Fatal(err) - } - }() - - return func(context.Context, string) (net.Conn, error) { - return listener.Dial() - } -} - func (s *TestSuite) TestHandler_Handle() { ctx := context.Background() diff --git a/test/infrastructure/config/config.go b/test/infrastructure/config/config.go new file mode 100644 index 0000000..cd5fbe2 --- /dev/null +++ b/test/infrastructure/config/config.go @@ -0,0 +1,42 @@ +package config + +import "time" + +// Config хранит конфигурацию тестового окружения. +type Config struct { + // Env окружение. + Env string `yaml:"env" env:"ENV" env-default:"dev" env-description:"Environment" json:"env"` + + // Migration настройки магратора. + Migration struct { + // SourcePath путь до файлов миграций. + SourcePath string `yaml:"sourcePath" env:"MIGRATION_SOURCE_PATH" env-description:"Path to migration source" json:"source_path"` + // Table название таблицы с примененными миграциями. + Table string `yaml:"table" env:"MIGRATION_TABLE" env-description:"Migration table" json:"table"` + } `yaml:"migration" json:"migration"` + + // Auth настройки авторизации. + Auth struct { + // AccessTokenExp время жизни access токена. + AccessTokenExp time.Duration `yaml:"accessTokenExp" env:"ACCESS_TOKEN_EXP" env-description:"Access token expiration" json:"access_token_exp"` + // RefreshTokenExp время жизни refresh токена. + RefreshTokenExp time.Duration `yaml:"refreshTokenExp" env:"REFRESH_TOKEN_EXP" env-description:"Refresh token expiration" json:"refresh_token_exp"` + // SecretKey секретный ключ. + SecretKey string `yaml:"secretKey" env:"SECRET_KEY" env-description:"Secret key" json:"secret_key"` + } `yaml:"auth" json:"auth"` + + // Container настройки Docker контейнеров. + Container struct { + // PG настройки для контейнера под PostgreSQL. + PG struct { + // Tag контейнера + Tag string `yaml:"tag" env:"CONTAINER_PG_TAG" env-description:"Container image tag" json:"tag"` + // DBName значение для переменной окружения POSTGRES_DB + DBName string `yaml:"dbName" env:"CONTAINER_PG_DB_NAME" env-description:"DB name" json:"db_name"` + // DBUser значение для переменной окружения POSTGRES_USER + DBUser string `yaml:"dbUser" env:"CONTAINER_PG_DB_USER" env-description:"DB user" json:"db_user"` + // DBPassword значение для переменной окружения POSTGRES_PASSWORD + DBPassword string `yaml:"dbPassword" env:"CONTAINER_PG_DB_PASSWORD" env-description:"DB password" json:"db_password"` + } `yaml:"pg" json:"pg"` + } `yaml:"container" json:"container"` +} diff --git a/test/infrastructure/server/server.go b/test/infrastructure/server/server.go new file mode 100644 index 0000000..db97575 --- /dev/null +++ b/test/infrastructure/server/server.go @@ -0,0 +1,27 @@ +package server + +import ( + "context" + "net" + + "github.com/jmoiron/sqlx" + "go.uber.org/zap" + "google.golang.org/grpc/test/bufconn" + + "github.com/bjlag/go-keeper/internal/app/server" + "github.com/bjlag/go-keeper/internal/infrastructure/auth" +) + +func Start(ctx context.Context, db *sqlx.DB, jwt *auth.JWT, log *zap.Logger) func(context.Context, string) (net.Conn, error) { + listener := bufconn.Listen(1204 * 1024) + + go func() { + if err := server.NewApp(db, jwt, listener, log).Run(ctx); err != nil { + panic(err) + } + }() + + return func(context.Context, string) (net.Conn, error) { + return listener.Dial() + } +} From 581f5b9a44d62a97c0d6f45f9c1b3ce350d9916b Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Tue, 18 Mar 2025 00:06:05 +0300 Subject: [PATCH 80/94] tests: nop logger --- internal/infrastructure/logger/logger.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/infrastructure/logger/logger.go b/internal/infrastructure/logger/logger.go index 09b5757..ca044c9 100644 --- a/internal/infrastructure/logger/logger.go +++ b/internal/infrastructure/logger/logger.go @@ -25,6 +25,11 @@ var ( // При первом получении создается экземпляр логгера и кладется в глобальную переменную пакета. func Get(env string) *zap.Logger { once.Do(func() { + if env == "test" { + logger = zap.NewNop() + return + } + var config zap.Config if env == "prod" { From fd0640e9355d8666072d3cc2f1ff48af42261986 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Tue, 18 Mar 2025 00:39:59 +0300 Subject: [PATCH 81/94] server: test login --- internal/rpc/login/handler.go | 8 +- test/functional/client/login_test.go | 87 +++++++++++++++++++ .../login_test.go => client/setup_test.go} | 22 +---- test/infrastructure/server/server.go | 4 +- 4 files changed, 97 insertions(+), 24 deletions(-) create mode 100644 test/functional/client/login_test.go rename test/functional/{login/login_test.go => client/setup_test.go} (86%) diff --git a/internal/rpc/login/handler.go b/internal/rpc/login/handler.go index f5f6122..e381d43 100644 --- a/internal/rpc/login/handler.go +++ b/internal/rpc/login/handler.go @@ -32,6 +32,10 @@ func (h *Handler) Handle(ctx context.Context, in *pb.LoginIn) (*pb.LoginOut, err return nil, status.Error(codes.InvalidArgument, "email is invalid") } + if len(in.GetPassword()) == 0 { + return nil, status.Error(codes.InvalidArgument, "password is empty") + } + result, err := h.usecase.Do(ctx, login.Data{ Email: in.GetEmail(), Password: in.GetPassword(), @@ -39,9 +43,9 @@ func (h *Handler) Handle(ctx context.Context, in *pb.LoginIn) (*pb.LoginOut, err if err != nil { switch { case errors.Is(err, login.ErrUserNotFound): - return nil, status.Error(codes.Unauthenticated, "password incorrect") + return nil, status.Error(codes.Unauthenticated, "credentials incorrect") case errors.Is(err, login.ErrPasswordIncorrect): - return nil, status.Error(codes.Unauthenticated, "password incorrect") + return nil, status.Error(codes.Unauthenticated, "credentials incorrect") default: log.Error("Failed to login user", zap.Error(err)) return nil, status.Error(codes.Internal, "internal error") diff --git a/test/functional/client/login_test.go b/test/functional/client/login_test.go new file mode 100644 index 0000000..0af844f --- /dev/null +++ b/test/functional/client/login_test.go @@ -0,0 +1,87 @@ +package client_test + +import ( + "context" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/bjlag/go-keeper/internal/generated/rpc" + "github.com/bjlag/go-keeper/test/infrastructure/fixture" + _ "github.com/bjlag/go-keeper/test/infrastructure/init" +) + +func (s *TestSuite) TestLogin() { + ctx := context.Background() + + err := fixture.Load(s.db, "test/fixture") + s.Require().NoError(err) + + s.Run("success", func() { + out, err := s.client.Login(ctx, &rpc.LoginIn{ + Email: "test@test.ru", + Password: "12345678", + }) + + s.NoError(err) + s.NotEmpty(out.GetAccessToken()) + s.NotEmpty(out.GetRefreshToken()) + }) + + s.Run("wrong credentials", func() { + out, err := s.client.Login(ctx, &rpc.LoginIn{ + Email: "test@test.ru", + Password: "1111111", + }) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.Unauthenticated, st.Code()) + s.Equal("credentials incorrect", st.Message()) + + s.Empty(out.GetAccessToken()) + s.Empty(out.GetRefreshToken()) + }) + + s.Run("empty body", func() { + out, err := s.client.Login(ctx, &rpc.LoginIn{}) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.InvalidArgument, st.Code()) + s.Equal("email is invalid", st.Message()) + + s.Empty(out.GetAccessToken()) + s.Empty(out.GetRefreshToken()) + }) + + s.Run("wrong email", func() { + out, err := s.client.Login(ctx, &rpc.LoginIn{ + Email: "test@test", + Password: "12345678", + }) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.InvalidArgument, st.Code()) + s.Equal(st.Message(), "email is invalid") + + s.Empty(out.GetAccessToken()) + s.Empty(out.GetRefreshToken()) + }) + + s.Run("wrong password", func() { + out, err := s.client.Login(ctx, &rpc.LoginIn{ + Email: "test@test.ru", + Password: "", + }) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.InvalidArgument, st.Code()) + s.Equal("password is empty", st.Message()) + + s.Empty(out.GetAccessToken()) + s.Empty(out.GetRefreshToken()) + }) +} diff --git a/test/functional/login/login_test.go b/test/functional/client/setup_test.go similarity index 86% rename from test/functional/login/login_test.go rename to test/functional/client/setup_test.go index 2e5bd76..501e98d 100644 --- a/test/functional/login/login_test.go +++ b/test/functional/client/setup_test.go @@ -1,4 +1,4 @@ -package login_test +package client_test import ( "context" @@ -18,7 +18,6 @@ import ( "github.com/bjlag/go-keeper/internal/infrastructure/migrator" "github.com/bjlag/go-keeper/test/infrastructure/config" "github.com/bjlag/go-keeper/test/infrastructure/container" - "github.com/bjlag/go-keeper/test/infrastructure/fixture" _ "github.com/bjlag/go-keeper/test/infrastructure/init" "github.com/bjlag/go-keeper/test/infrastructure/server" ) @@ -97,22 +96,3 @@ func (s *TestSuite) TearDownSuite() { err = s.db.Close() s.Require().NoError(err) } - -func (s *TestSuite) TestHandler_Handle() { - ctx := context.Background() - - err := fixture.Load(s.db, "test/fixture") - s.Require().NoError(err) - - s.Run("success", func() { - out, err := s.client.Login(ctx, &rpc.LoginIn{ - Email: "test@test.ru", - Password: "12345678", - }) - - if err != nil { - } - - out.GetAccessToken() - }) -} diff --git a/test/infrastructure/server/server.go b/test/infrastructure/server/server.go index db97575..bd01ba4 100644 --- a/test/infrastructure/server/server.go +++ b/test/infrastructure/server/server.go @@ -12,8 +12,10 @@ import ( "github.com/bjlag/go-keeper/internal/infrastructure/auth" ) +const bufSize = 1024 * 1024 + func Start(ctx context.Context, db *sqlx.DB, jwt *auth.JWT, log *zap.Logger) func(context.Context, string) (net.Conn, error) { - listener := bufconn.Listen(1204 * 1024) + listener := bufconn.Listen(bufSize) go func() { if err := server.NewApp(db, jwt, listener, log).Run(ctx); err != nil { From cbaf52ba5d83f70d93ba5cfa1961c78aa9707e2f Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Tue, 18 Mar 2025 01:00:57 +0300 Subject: [PATCH 82/94] server: test register --- test/fixture/{ => server}/users.yaml | 0 .../{client => server}/login_test.go | 2 +- test/functional/server/register_test.go | 75 +++++++++++++++++++ .../{client => server}/setup_test.go | 2 +- 4 files changed, 77 insertions(+), 2 deletions(-) rename test/fixture/{ => server}/users.yaml (100%) rename test/functional/{client => server}/login_test.go (98%) create mode 100644 test/functional/server/register_test.go rename test/functional/{client => server}/setup_test.go (99%) diff --git a/test/fixture/users.yaml b/test/fixture/server/users.yaml similarity index 100% rename from test/fixture/users.yaml rename to test/fixture/server/users.yaml diff --git a/test/functional/client/login_test.go b/test/functional/server/login_test.go similarity index 98% rename from test/functional/client/login_test.go rename to test/functional/server/login_test.go index 0af844f..3aaa6ee 100644 --- a/test/functional/client/login_test.go +++ b/test/functional/server/login_test.go @@ -1,4 +1,4 @@ -package client_test +package server_test import ( "context" diff --git a/test/functional/server/register_test.go b/test/functional/server/register_test.go new file mode 100644 index 0000000..d939bf4 --- /dev/null +++ b/test/functional/server/register_test.go @@ -0,0 +1,75 @@ +package server_test + +import ( + "context" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/bjlag/go-keeper/internal/generated/rpc" + "github.com/bjlag/go-keeper/test/infrastructure/fixture" + _ "github.com/bjlag/go-keeper/test/infrastructure/init" +) + +func (s *TestSuite) TestRegister() { + ctx := context.Background() + + err := fixture.Load(s.db, "test/fixture/server") + s.Require().NoError(err) + + s.Run("success", func() { + out, err := s.client.Register(ctx, &rpc.RegisterIn{ + Email: "new@test.ru", + Password: "12345678", + }) + + s.NoError(err) + s.NotEmpty(out.GetAccessToken()) + s.NotEmpty(out.GetRefreshToken()) + }) + + s.Run("already exists", func() { + out, err := s.client.Register(ctx, &rpc.RegisterIn{ + Email: "test@test.ru", + Password: "12345678", + }) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.AlreadyExists, st.Code()) + s.Equal("user with this email already exists", st.Message()) + + s.Empty(out.GetAccessToken()) + s.Empty(out.GetRefreshToken()) + }) + + s.Run("invalid email", func() { + out, err := s.client.Register(ctx, &rpc.RegisterIn{ + Email: "test@test", + Password: "12345678", + }) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.InvalidArgument, st.Code()) + s.Equal("email is invalid", st.Message()) + + s.Empty(out.GetAccessToken()) + s.Empty(out.GetRefreshToken()) + }) + + s.Run("invalid password", func() { + out, err := s.client.Register(ctx, &rpc.RegisterIn{ + Email: "test@test.ru", + Password: "1234567", + }) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.InvalidArgument, st.Code()) + s.Equal("password is invalid (min. length 8 characters)", st.Message()) + + s.Empty(out.GetAccessToken()) + s.Empty(out.GetRefreshToken()) + }) +} diff --git a/test/functional/client/setup_test.go b/test/functional/server/setup_test.go similarity index 99% rename from test/functional/client/setup_test.go rename to test/functional/server/setup_test.go index 501e98d..1f1afa4 100644 --- a/test/functional/client/setup_test.go +++ b/test/functional/server/setup_test.go @@ -1,4 +1,4 @@ -package client_test +package server_test import ( "context" From 96871baa6f9d02b3f6dc77f4f55be94a5dd8a04c Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Tue, 18 Mar 2025 16:50:50 +0300 Subject: [PATCH 83/94] server: test get all items --- test/fixture/server/items.yaml | 34 +++++++++++++ test/fixture/server/users.yaml | 10 +++- test/functional/server/get_all_items_test.go | 51 ++++++++++++++++++++ test/functional/server/login_test.go | 9 ++-- test/functional/server/register_test.go | 6 ++- 5 files changed, 103 insertions(+), 7 deletions(-) create mode 100644 test/fixture/server/items.yaml create mode 100644 test/functional/server/get_all_items_test.go diff --git a/test/fixture/server/items.yaml b/test/fixture/server/items.yaml new file mode 100644 index 0000000..f6e48be --- /dev/null +++ b/test/fixture/server/items.yaml @@ -0,0 +1,34 @@ +# password +- guid: 60308368-7729-4d2d-a510-67926f5a159b + user_guid: bf4e6232-f1ae-41da-8535-73048891b1e3 + encrypted_data: 0x4CA6423E2DACC0ACD0AA60620930AF7FC3E30F8692D716FBC637C9972B76CAEC09D5643205EFFEE95567204FBE66E8E5683DFB1E7319BA25E09C4F719810985C1CFC80E5FE8A55AC142716357023DE30BF4892DA3E481055290F3B90F8EB93AFBC9CE94DEDF2B73B734EB8ED0FF10EF6A925711CA83AD7C53744 + created_at: 2025-03-15 13:00:00 + updated_at: 2025-03-15 13:00:00 + +# text +- guid: 6e7fc4fa-31aa-4d75-8b6e-0479122e0147 + user_guid: bf4e6232-f1ae-41da-8535-73048891b1e3 + encrypted_data: 0x104501B790B26B92AFB9648067356F6ADA2EE513C1CE48F93789F1B8ED23E73054998D9E3D40AB4D2D790EAC919BC558D329A0134485D2C26F8A23C27A46CF06D99B80ECCA6EE739BFFA + created_at: 2025-03-15 13:10:00 + updated_at: 2025-03-15 13:10:00 + +# file +- guid: b2bd09eb-2c84-4149-b2b8-29040472264a + user_guid: bf4e6232-f1ae-41da-8535-73048891b1e3 + encrypted_data: 0xDE8B088675B88D6922E7B789468BBBD9707A7492F65669DFF15A7EBD25F8DC489B2D20F47F461AD288AF1D66135711837D425BB66E459E271C09D7171D83F6F7B346ACA35C45A9BA04CF39CF4F656055F89A9DD9A28F5933C6B6BB1FB8166DAC946F48BA14AB9A2AE8C589ABE62464E44B4FF0656F1FF66EBBABAA47 + created_at: 2025-03-15 13:20:00 + updated_at: 2025-03-15 13:20:00 + +# bank card +- guid: 127e1a2d-1943-4fb1-ba60-7dc4fc820ed4 + user_guid: bf4e6232-f1ae-41da-8535-73048891b1e3 + encrypted_data: 0x460D40A892881F1049226D48E3FD6B9C1271C570379A1659AD366E5616A81AD7D313358AC4752C49F420C414336DBD4FF25C39E5A7B49B8B2CD765B885502A207CB6E1672E8EBD57FFC0D79A608A44211AD1F915010094B3B8FC1CDC11ED992EE30B404DF0766843388434034EE640BCCB50374CD33CE63E4843A75934693BE2CF19F6AD05E132E2160B0443187DDC + created_at: 2025-03-15 13:30:00 + updated_at: 2025-03-15 13:30:00 + +# password other@user.ru +- guid: d07f9605-0b8e-42f6-a07b-3e3f839a7bee + user_guid: 67ff83b7-d199-4014-9c59-ad1e77340d9f + encrypted_data: 0x4CA6423E2DACC0ACD0AA60620930AF7FC3E30F8692D716FBC637C9972B76CAEC09D5643205EFFEE95567204FBE66E8E5683DFB1E7319BA25E09C4F719810985C1CFC80E5FE8A55AC142716357023DE30BF4892DA3E481055290F3B90F8EB93AFBC9CE94DEDF2B73B734EB8ED0FF10EF6A925711CA83AD7C53744 + created_at: 2025-03-15 13:00:00 + updated_at: 2025-03-15 13:00:00 \ No newline at end of file diff --git a/test/fixture/server/users.yaml b/test/fixture/server/users.yaml index 59f3a4e..004b453 100644 --- a/test/fixture/server/users.yaml +++ b/test/fixture/server/users.yaml @@ -1,5 +1,11 @@ - guid: bf4e6232-f1ae-41da-8535-73048891b1e3 email: test@test.ru - password_hash: $2a$04$OxrD4jeA4SrmsnQOdYH4iu1dfsydv36kbBsmQSXZBDtDTca6Vef2K + password_hash: $2a$04$OxrD4jeA4SrmsnQOdYH4iu1dfsydv36kbBsmQSXZBDtDTca6Vef2K # 12345678 created_at: 2025-03-15 13:00:00 - updated_at: 2025-03-15 13:00:00 \ No newline at end of file + updated_at: 2025-03-15 13:00:00 + +- guid: 67ff83b7-d199-4014-9c59-ad1e77340d9f + email: other@user.ru + password_hash: $2a$04$OxrD4jeA4SrmsnQOdYH4iu1dfsydv36kbBsmQSXZBDtDTca6Vef2K # 12345678 + created_at: 2025-03-13 13:00:00 + updated_at: 2025-03-13 13:00:00 \ No newline at end of file diff --git a/test/functional/server/get_all_items_test.go b/test/functional/server/get_all_items_test.go new file mode 100644 index 0000000..24e606c --- /dev/null +++ b/test/functional/server/get_all_items_test.go @@ -0,0 +1,51 @@ +package server_test + +import ( + "context" + "fmt" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + + "github.com/bjlag/go-keeper/internal/generated/rpc" + "github.com/bjlag/go-keeper/test/infrastructure/fixture" + _ "github.com/bjlag/go-keeper/test/infrastructure/init" +) + +func (s *TestSuite) TestGetAllItems() { + + err := fixture.Load(s.db, "test/fixture/server") + s.Require().NoError(err) + + s.Run("success", func() { + ctx := context.Background() + loginOut, err := s.client.Login(ctx, &rpc.LoginIn{ + Email: "test@test.ru", + Password: "12345678", + }) + s.Require().NoError(err) + + md, ok := metadata.FromOutgoingContext(ctx) + if !ok { + md = metadata.New(nil) + } + md.Set("authorization", fmt.Sprintf("%s %s", "Bearer", loginOut.GetAccessToken())) + + getAllItemsOut, err := s.client.GetAllItems(metadata.NewOutgoingContext(ctx, md), &rpc.GetAllItemsIn{}) + s.Require().NoError(err) + s.Assert().Len(getAllItemsOut.GetItems(), 4) + }) + + s.Run("permission denied", func() { + ctx := context.Background() + out, err := s.client.GetAllItems(ctx, &rpc.GetAllItemsIn{}) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.PermissionDenied, st.Code()) + s.Equal("permission denied", st.Message()) + + s.Empty(out.GetItems()) + }) +} diff --git a/test/functional/server/login_test.go b/test/functional/server/login_test.go index 3aaa6ee..eee4dc6 100644 --- a/test/functional/server/login_test.go +++ b/test/functional/server/login_test.go @@ -12,12 +12,11 @@ import ( ) func (s *TestSuite) TestLogin() { - ctx := context.Background() - - err := fixture.Load(s.db, "test/fixture") + err := fixture.Load(s.db, "test/fixture/server") s.Require().NoError(err) s.Run("success", func() { + ctx := context.Background() out, err := s.client.Login(ctx, &rpc.LoginIn{ Email: "test@test.ru", Password: "12345678", @@ -29,6 +28,7 @@ func (s *TestSuite) TestLogin() { }) s.Run("wrong credentials", func() { + ctx := context.Background() out, err := s.client.Login(ctx, &rpc.LoginIn{ Email: "test@test.ru", Password: "1111111", @@ -44,6 +44,7 @@ func (s *TestSuite) TestLogin() { }) s.Run("empty body", func() { + ctx := context.Background() out, err := s.client.Login(ctx, &rpc.LoginIn{}) st, ok := status.FromError(err) @@ -56,6 +57,7 @@ func (s *TestSuite) TestLogin() { }) s.Run("wrong email", func() { + ctx := context.Background() out, err := s.client.Login(ctx, &rpc.LoginIn{ Email: "test@test", Password: "12345678", @@ -71,6 +73,7 @@ func (s *TestSuite) TestLogin() { }) s.Run("wrong password", func() { + ctx := context.Background() out, err := s.client.Login(ctx, &rpc.LoginIn{ Email: "test@test.ru", Password: "", diff --git a/test/functional/server/register_test.go b/test/functional/server/register_test.go index d939bf4..7c526e9 100644 --- a/test/functional/server/register_test.go +++ b/test/functional/server/register_test.go @@ -12,12 +12,11 @@ import ( ) func (s *TestSuite) TestRegister() { - ctx := context.Background() - err := fixture.Load(s.db, "test/fixture/server") s.Require().NoError(err) s.Run("success", func() { + ctx := context.Background() out, err := s.client.Register(ctx, &rpc.RegisterIn{ Email: "new@test.ru", Password: "12345678", @@ -29,6 +28,7 @@ func (s *TestSuite) TestRegister() { }) s.Run("already exists", func() { + ctx := context.Background() out, err := s.client.Register(ctx, &rpc.RegisterIn{ Email: "test@test.ru", Password: "12345678", @@ -44,6 +44,7 @@ func (s *TestSuite) TestRegister() { }) s.Run("invalid email", func() { + ctx := context.Background() out, err := s.client.Register(ctx, &rpc.RegisterIn{ Email: "test@test", Password: "12345678", @@ -59,6 +60,7 @@ func (s *TestSuite) TestRegister() { }) s.Run("invalid password", func() { + ctx := context.Background() out, err := s.client.Register(ctx, &rpc.RegisterIn{ Email: "test@test.ru", Password: "1234567", From 661ef5fee3e83835a0e46cfec868638d1d0c0a28 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Tue, 18 Mar 2025 17:13:30 +0300 Subject: [PATCH 84/94] server: test get by guid --- internal/rpc/get_by_guid/handler.go | 2 + .../server/item/get_by_guid/usecase.go | 4 +- test/functional/server/get_by_guid_test.go | 104 ++++++++++++++++++ 3 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 test/functional/server/get_by_guid_test.go diff --git a/internal/rpc/get_by_guid/handler.go b/internal/rpc/get_by_guid/handler.go index 8c23846..6f58891 100644 --- a/internal/rpc/get_by_guid/handler.go +++ b/internal/rpc/get_by_guid/handler.go @@ -49,6 +49,8 @@ func (h *Handler) Handle(ctx context.Context, in *pb.GetByGuidIn) (*pb.GetByGuid log.Error("Failed to get user item by guid", zap.Error(err)) return nil, status.Error(codes.Internal, "internal error") } + + return nil, status.Error(codes.NotFound, "item not found") } return &pb.GetByGuidOut{ diff --git a/internal/usecase/server/item/get_by_guid/usecase.go b/internal/usecase/server/item/get_by_guid/usecase.go index f1e575c..539bce0 100644 --- a/internal/usecase/server/item/get_by_guid/usecase.go +++ b/internal/usecase/server/item/get_by_guid/usecase.go @@ -7,7 +7,7 @@ import ( "fmt" "github.com/bjlag/go-keeper/internal/domain/server/data" - storeUser "github.com/bjlag/go-keeper/internal/infrastructure/store/server/user" + "github.com/bjlag/go-keeper/internal/infrastructure/store/server/item" ) var ErrNoData = errors.New("no data") @@ -27,7 +27,7 @@ func (u Usecase) Do(ctx context.Context, data Data) (*data.Item, error) { model, err := u.dataStore.UserItemByGUID(ctx, data.UserGUID, data.GUID) if err != nil { - if errors.Is(err, storeUser.ErrNotFound) { + if errors.Is(err, item.ErrNotFound) { return nil, ErrNoData } diff --git a/test/functional/server/get_by_guid_test.go b/test/functional/server/get_by_guid_test.go new file mode 100644 index 0000000..f4ca135 --- /dev/null +++ b/test/functional/server/get_by_guid_test.go @@ -0,0 +1,104 @@ +package server_test + +import ( + "context" + "fmt" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + + "github.com/bjlag/go-keeper/internal/generated/rpc" + "github.com/bjlag/go-keeper/test/infrastructure/fixture" + _ "github.com/bjlag/go-keeper/test/infrastructure/init" +) + +func (s *TestSuite) TestGetByGUID() { + + err := fixture.Load(s.db, "test/fixture/server") + s.Require().NoError(err) + + s.Run("success", func() { + ctx := context.Background() + loginOut, err := s.client.Login(ctx, &rpc.LoginIn{ + Email: "test@test.ru", + Password: "12345678", + }) + s.Require().NoError(err) + + md, ok := metadata.FromOutgoingContext(ctx) + if !ok { + md = metadata.New(nil) + } + md.Set("authorization", fmt.Sprintf("%s %s", "Bearer", loginOut.GetAccessToken())) + + getByGUIDOut, err := s.client.GetByGuid(metadata.NewOutgoingContext(ctx, md), &rpc.GetByGuidIn{ + Guid: "60308368-7729-4d2d-a510-67926f5a159b", + }) + s.Require().NoError(err) + s.Assert().Equal("60308368-7729-4d2d-a510-67926f5a159b", getByGUIDOut.GetGuid()) + }) + + s.Run("not found", func() { + ctx := context.Background() + loginOut, err := s.client.Login(ctx, &rpc.LoginIn{ + Email: "test@test.ru", + Password: "12345678", + }) + s.Require().NoError(err) + + md, ok := metadata.FromOutgoingContext(ctx) + if !ok { + md = metadata.New(nil) + } + md.Set("authorization", fmt.Sprintf("%s %s", "Bearer", loginOut.GetAccessToken())) + + getByGUIDOut, err := s.client.GetByGuid(metadata.NewOutgoingContext(ctx, md), &rpc.GetByGuidIn{ + Guid: "00000000-0000-0000-0000-000000000000", + }) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.NotFound, st.Code()) + s.Equal("item not found", st.Message()) + s.Nil(getByGUIDOut) + }) + + s.Run("not found if item belongs to other user", func() { + ctx := context.Background() + loginOut, err := s.client.Login(ctx, &rpc.LoginIn{ + Email: "test@test.ru", + Password: "12345678", + }) + s.Require().NoError(err) + + md, ok := metadata.FromOutgoingContext(ctx) + if !ok { + md = metadata.New(nil) + } + md.Set("authorization", fmt.Sprintf("%s %s", "Bearer", loginOut.GetAccessToken())) + + getByGUIDOut, err := s.client.GetByGuid(metadata.NewOutgoingContext(ctx, md), &rpc.GetByGuidIn{ + Guid: "d07f9605-0b8e-42f6-a07b-3e3f839a7bee", + }) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.NotFound, st.Code()) + s.Equal("item not found", st.Message()) + s.Nil(getByGUIDOut) + }) + + s.Run("permission denied", func() { + ctx := context.Background() + out, err := s.client.GetByGuid(ctx, &rpc.GetByGuidIn{ + Guid: "60308368-7729-4d2d-a510-67926f5a159b", + }) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.PermissionDenied, st.Code()) + s.Equal("permission denied", st.Message()) + s.Nil(out) + }) +} From cfc4411fc9378b9fbdb9d50e8c6508ce585006ac Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Tue, 18 Mar 2025 17:16:25 +0300 Subject: [PATCH 85/94] server: test get all items limit offset --- test/functional/server/get_all_items_test.go | 33 ++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/functional/server/get_all_items_test.go b/test/functional/server/get_all_items_test.go index 24e606c..fd1c527 100644 --- a/test/functional/server/get_all_items_test.go +++ b/test/functional/server/get_all_items_test.go @@ -37,6 +37,39 @@ func (s *TestSuite) TestGetAllItems() { s.Assert().Len(getAllItemsOut.GetItems(), 4) }) + s.Run("success limit offset", func() { + ctx := context.Background() + loginOut, err := s.client.Login(ctx, &rpc.LoginIn{ + Email: "test@test.ru", + Password: "12345678", + }) + s.Require().NoError(err) + + md, ok := metadata.FromOutgoingContext(ctx) + if !ok { + md = metadata.New(nil) + } + md.Set("authorization", fmt.Sprintf("%s %s", "Bearer", loginOut.GetAccessToken())) + + getAllItemsOut1, err := s.client.GetAllItems(metadata.NewOutgoingContext(ctx, md), &rpc.GetAllItemsIn{ + Offset: 0, + Limit: 2, + }) + s.Require().NoError(err) + s.Assert().Len(getAllItemsOut1.GetItems(), 2) + s.Assert().Equal(getAllItemsOut1.GetItems()[0].GetGuid(), "127e1a2d-1943-4fb1-ba60-7dc4fc820ed4") + s.Assert().Equal(getAllItemsOut1.GetItems()[1].GetGuid(), "60308368-7729-4d2d-a510-67926f5a159b") + + getAllItemsOut2, err := s.client.GetAllItems(metadata.NewOutgoingContext(ctx, md), &rpc.GetAllItemsIn{ + Offset: 2, + Limit: 2, + }) + s.Require().NoError(err) + s.Assert().Len(getAllItemsOut2.GetItems(), 2) + s.Assert().Equal(getAllItemsOut2.GetItems()[0].GetGuid(), "6e7fc4fa-31aa-4d75-8b6e-0479122e0147") + s.Assert().Equal(getAllItemsOut2.GetItems()[1].GetGuid(), "b2bd09eb-2c84-4149-b2b8-29040472264a") + }) + s.Run("permission denied", func() { ctx := context.Background() out, err := s.client.GetAllItems(ctx, &rpc.GetAllItemsIn{}) From 04c71ee6f0681d301595b5c34291821164a91dfb Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Tue, 18 Mar 2025 17:26:36 +0300 Subject: [PATCH 86/94] server: test refactoring --- test/functional/server/get_all_items_test.go | 35 ++------------ test/functional/server/get_by_guid_test.go | 48 +++----------------- test/functional/server/helper_test.go | 26 +++++++++++ 3 files changed, 37 insertions(+), 72 deletions(-) create mode 100644 test/functional/server/helper_test.go diff --git a/test/functional/server/get_all_items_test.go b/test/functional/server/get_all_items_test.go index fd1c527..fb2c916 100644 --- a/test/functional/server/get_all_items_test.go +++ b/test/functional/server/get_all_items_test.go @@ -2,10 +2,8 @@ package server_test import ( "context" - "fmt" "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" "github.com/bjlag/go-keeper/internal/generated/rpc" @@ -14,44 +12,21 @@ import ( ) func (s *TestSuite) TestGetAllItems() { - err := fixture.Load(s.db, "test/fixture/server") s.Require().NoError(err) s.Run("success", func() { - ctx := context.Background() - loginOut, err := s.client.Login(ctx, &rpc.LoginIn{ - Email: "test@test.ru", - Password: "12345678", - }) - s.Require().NoError(err) - - md, ok := metadata.FromOutgoingContext(ctx) - if !ok { - md = metadata.New(nil) - } - md.Set("authorization", fmt.Sprintf("%s %s", "Bearer", loginOut.GetAccessToken())) + ctx := s.login(context.Background(), "test@test.ru", "12345678") - getAllItemsOut, err := s.client.GetAllItems(metadata.NewOutgoingContext(ctx, md), &rpc.GetAllItemsIn{}) + getAllItemsOut, err := s.client.GetAllItems(ctx, &rpc.GetAllItemsIn{}) s.Require().NoError(err) s.Assert().Len(getAllItemsOut.GetItems(), 4) }) s.Run("success limit offset", func() { - ctx := context.Background() - loginOut, err := s.client.Login(ctx, &rpc.LoginIn{ - Email: "test@test.ru", - Password: "12345678", - }) - s.Require().NoError(err) - - md, ok := metadata.FromOutgoingContext(ctx) - if !ok { - md = metadata.New(nil) - } - md.Set("authorization", fmt.Sprintf("%s %s", "Bearer", loginOut.GetAccessToken())) + ctx := s.login(context.Background(), "test@test.ru", "12345678") - getAllItemsOut1, err := s.client.GetAllItems(metadata.NewOutgoingContext(ctx, md), &rpc.GetAllItemsIn{ + getAllItemsOut1, err := s.client.GetAllItems(ctx, &rpc.GetAllItemsIn{ Offset: 0, Limit: 2, }) @@ -60,7 +35,7 @@ func (s *TestSuite) TestGetAllItems() { s.Assert().Equal(getAllItemsOut1.GetItems()[0].GetGuid(), "127e1a2d-1943-4fb1-ba60-7dc4fc820ed4") s.Assert().Equal(getAllItemsOut1.GetItems()[1].GetGuid(), "60308368-7729-4d2d-a510-67926f5a159b") - getAllItemsOut2, err := s.client.GetAllItems(metadata.NewOutgoingContext(ctx, md), &rpc.GetAllItemsIn{ + getAllItemsOut2, err := s.client.GetAllItems(ctx, &rpc.GetAllItemsIn{ Offset: 2, Limit: 2, }) diff --git a/test/functional/server/get_by_guid_test.go b/test/functional/server/get_by_guid_test.go index f4ca135..a81222c 100644 --- a/test/functional/server/get_by_guid_test.go +++ b/test/functional/server/get_by_guid_test.go @@ -2,10 +2,8 @@ package server_test import ( "context" - "fmt" "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" "github.com/bjlag/go-keeper/internal/generated/rpc" @@ -14,25 +12,13 @@ import ( ) func (s *TestSuite) TestGetByGUID() { - err := fixture.Load(s.db, "test/fixture/server") s.Require().NoError(err) s.Run("success", func() { - ctx := context.Background() - loginOut, err := s.client.Login(ctx, &rpc.LoginIn{ - Email: "test@test.ru", - Password: "12345678", - }) - s.Require().NoError(err) + ctx := s.login(context.Background(), "test@test.ru", "12345678") - md, ok := metadata.FromOutgoingContext(ctx) - if !ok { - md = metadata.New(nil) - } - md.Set("authorization", fmt.Sprintf("%s %s", "Bearer", loginOut.GetAccessToken())) - - getByGUIDOut, err := s.client.GetByGuid(metadata.NewOutgoingContext(ctx, md), &rpc.GetByGuidIn{ + getByGUIDOut, err := s.client.GetByGuid(ctx, &rpc.GetByGuidIn{ Guid: "60308368-7729-4d2d-a510-67926f5a159b", }) s.Require().NoError(err) @@ -40,20 +26,9 @@ func (s *TestSuite) TestGetByGUID() { }) s.Run("not found", func() { - ctx := context.Background() - loginOut, err := s.client.Login(ctx, &rpc.LoginIn{ - Email: "test@test.ru", - Password: "12345678", - }) - s.Require().NoError(err) - - md, ok := metadata.FromOutgoingContext(ctx) - if !ok { - md = metadata.New(nil) - } - md.Set("authorization", fmt.Sprintf("%s %s", "Bearer", loginOut.GetAccessToken())) + ctx := s.login(context.Background(), "test@test.ru", "12345678") - getByGUIDOut, err := s.client.GetByGuid(metadata.NewOutgoingContext(ctx, md), &rpc.GetByGuidIn{ + getByGUIDOut, err := s.client.GetByGuid(ctx, &rpc.GetByGuidIn{ Guid: "00000000-0000-0000-0000-000000000000", }) @@ -65,20 +40,9 @@ func (s *TestSuite) TestGetByGUID() { }) s.Run("not found if item belongs to other user", func() { - ctx := context.Background() - loginOut, err := s.client.Login(ctx, &rpc.LoginIn{ - Email: "test@test.ru", - Password: "12345678", - }) - s.Require().NoError(err) - - md, ok := metadata.FromOutgoingContext(ctx) - if !ok { - md = metadata.New(nil) - } - md.Set("authorization", fmt.Sprintf("%s %s", "Bearer", loginOut.GetAccessToken())) + ctx := s.login(context.Background(), "test@test.ru", "12345678") - getByGUIDOut, err := s.client.GetByGuid(metadata.NewOutgoingContext(ctx, md), &rpc.GetByGuidIn{ + getByGUIDOut, err := s.client.GetByGuid(ctx, &rpc.GetByGuidIn{ Guid: "d07f9605-0b8e-42f6-a07b-3e3f839a7bee", }) diff --git a/test/functional/server/helper_test.go b/test/functional/server/helper_test.go new file mode 100644 index 0000000..9e90768 --- /dev/null +++ b/test/functional/server/helper_test.go @@ -0,0 +1,26 @@ +package server_test + +import ( + "context" + "fmt" + + "google.golang.org/grpc/metadata" + + "github.com/bjlag/go-keeper/internal/generated/rpc" +) + +func (s *TestSuite) login(ctx context.Context, email, password string) context.Context { + out, err := s.client.Login(ctx, &rpc.LoginIn{ + Email: email, + Password: password, + }) + s.Require().NoError(err) + + md, ok := metadata.FromOutgoingContext(ctx) + if !ok { + md = metadata.New(nil) + } + md.Set("authorization", fmt.Sprintf("%s %s", "Bearer", out.GetAccessToken())) + + return metadata.NewOutgoingContext(ctx, md) +} From 5cbf09262136f4a654e4dde512a1d015a38757bb Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Tue, 18 Mar 2025 17:46:35 +0300 Subject: [PATCH 87/94] server: test refresh tokens --- test/functional/server/get_all_items_test.go | 14 +-- test/functional/server/get_by_guid_test.go | 2 +- test/functional/server/refresh_tokens_test.go | 103 ++++++++++++++++++ 3 files changed, 111 insertions(+), 8 deletions(-) create mode 100644 test/functional/server/refresh_tokens_test.go diff --git a/test/functional/server/get_all_items_test.go b/test/functional/server/get_all_items_test.go index fb2c916..7224d1d 100644 --- a/test/functional/server/get_all_items_test.go +++ b/test/functional/server/get_all_items_test.go @@ -20,7 +20,7 @@ func (s *TestSuite) TestGetAllItems() { getAllItemsOut, err := s.client.GetAllItems(ctx, &rpc.GetAllItemsIn{}) s.Require().NoError(err) - s.Assert().Len(getAllItemsOut.GetItems(), 4) + s.Len(getAllItemsOut.GetItems(), 4) }) s.Run("success limit offset", func() { @@ -31,18 +31,18 @@ func (s *TestSuite) TestGetAllItems() { Limit: 2, }) s.Require().NoError(err) - s.Assert().Len(getAllItemsOut1.GetItems(), 2) - s.Assert().Equal(getAllItemsOut1.GetItems()[0].GetGuid(), "127e1a2d-1943-4fb1-ba60-7dc4fc820ed4") - s.Assert().Equal(getAllItemsOut1.GetItems()[1].GetGuid(), "60308368-7729-4d2d-a510-67926f5a159b") + s.Len(getAllItemsOut1.GetItems(), 2) + s.Equal(getAllItemsOut1.GetItems()[0].GetGuid(), "127e1a2d-1943-4fb1-ba60-7dc4fc820ed4") + s.Equal(getAllItemsOut1.GetItems()[1].GetGuid(), "60308368-7729-4d2d-a510-67926f5a159b") getAllItemsOut2, err := s.client.GetAllItems(ctx, &rpc.GetAllItemsIn{ Offset: 2, Limit: 2, }) s.Require().NoError(err) - s.Assert().Len(getAllItemsOut2.GetItems(), 2) - s.Assert().Equal(getAllItemsOut2.GetItems()[0].GetGuid(), "6e7fc4fa-31aa-4d75-8b6e-0479122e0147") - s.Assert().Equal(getAllItemsOut2.GetItems()[1].GetGuid(), "b2bd09eb-2c84-4149-b2b8-29040472264a") + s.Len(getAllItemsOut2.GetItems(), 2) + s.Equal(getAllItemsOut2.GetItems()[0].GetGuid(), "6e7fc4fa-31aa-4d75-8b6e-0479122e0147") + s.Equal(getAllItemsOut2.GetItems()[1].GetGuid(), "b2bd09eb-2c84-4149-b2b8-29040472264a") }) s.Run("permission denied", func() { diff --git a/test/functional/server/get_by_guid_test.go b/test/functional/server/get_by_guid_test.go index a81222c..a5cf1d0 100644 --- a/test/functional/server/get_by_guid_test.go +++ b/test/functional/server/get_by_guid_test.go @@ -22,7 +22,7 @@ func (s *TestSuite) TestGetByGUID() { Guid: "60308368-7729-4d2d-a510-67926f5a159b", }) s.Require().NoError(err) - s.Assert().Equal("60308368-7729-4d2d-a510-67926f5a159b", getByGUIDOut.GetGuid()) + s.Equal("60308368-7729-4d2d-a510-67926f5a159b", getByGUIDOut.GetGuid()) }) s.Run("not found", func() { diff --git a/test/functional/server/refresh_tokens_test.go b/test/functional/server/refresh_tokens_test.go new file mode 100644 index 0000000..31ef5f3 --- /dev/null +++ b/test/functional/server/refresh_tokens_test.go @@ -0,0 +1,103 @@ +package server_test + +import ( + "context" + "fmt" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + + "github.com/bjlag/go-keeper/internal/generated/rpc" + "github.com/bjlag/go-keeper/test/infrastructure/fixture" + _ "github.com/bjlag/go-keeper/test/infrastructure/init" +) + +func (s *TestSuite) TestRefreshTokens() { + err := fixture.Load(s.db, "test/fixture/server") + s.Require().NoError(err) + + s.Run("success", func() { + ctx := context.Background() + loginOut, err := s.client.Login(ctx, &rpc.LoginIn{ + Email: "test@test.ru", + Password: "12345678", + }) + s.Require().NoError(err) + + refreshTokensOut, err := s.client.RefreshTokens(ctx, &rpc.RefreshTokensIn{ + RefreshToken: loginOut.GetRefreshToken(), + }) + s.Require().NoError(err) + s.NotEmpty(refreshTokensOut.GetAccessToken()) + s.NotEmpty(refreshTokensOut.GetRefreshToken()) + + md, ok := metadata.FromOutgoingContext(ctx) + if !ok { + md = metadata.New(nil) + } + md.Set("authorization", fmt.Sprintf("%s %s", "Bearer", refreshTokensOut.GetAccessToken())) + + getAllItemsOut, err := s.client.GetAllItems(metadata.NewOutgoingContext(ctx, md), &rpc.GetAllItemsIn{}) + s.Require().NoError(err) + s.Len(getAllItemsOut.GetItems(), 4) + }) + + s.Run("sent access token", func() { + ctx := context.Background() + loginOut, err := s.client.Login(ctx, &rpc.LoginIn{ + Email: "test@test.ru", + Password: "12345678", + }) + s.Require().NoError(err) + + refreshTokensOut, err := s.client.RefreshTokens(ctx, &rpc.RefreshTokensIn{ + RefreshToken: loginOut.GetAccessToken(), + }) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.FailedPrecondition, st.Code()) + s.Equal("invalid refresh token", st.Message()) + s.Nil(refreshTokensOut) + }) + + s.Run("refresh token too short", func() { + ctx := context.Background() + refreshTokensOut, err := s.client.RefreshTokens(ctx, &rpc.RefreshTokensIn{ + RefreshToken: "refresh token too short", + }) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.InvalidArgument, st.Code()) + s.Equal("refresh token too short", st.Message()) + s.Nil(refreshTokensOut) + }) + + s.Run("refresh token is wrong", func() { + ctx := context.Background() + refreshTokensOut, err := s.client.RefreshTokens(ctx, &rpc.RefreshTokensIn{ + RefreshToken: "wrong_token_eyJhbGciOiJIUzI1NiIsInR5.eyJpc3MiOiJiZjRlNjIzMi1mMWFlLTQxZGEtODUzNS03MzA0ODg5MWIxZTMiLCJzdWIiOiJyZWZyZXNoX3Rva2VuIiwiZXhwIjoxNzQyMzE2MDQ1LCJpYXQiOjE3NDIzMDg4NDV9.YO1nbCZ-1r4u4BWBvMSPOAda5m9R_IvoTJNqsziU-ik", + }) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.FailedPrecondition, st.Code()) + s.Equal("invalid refresh token", st.Message()) + s.Nil(refreshTokensOut) + }) + + s.Run("refresh token is expired", func() { + ctx := context.Background() + refreshTokensOut, err := s.client.RefreshTokens(ctx, &rpc.RefreshTokensIn{ + RefreshToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJiZjRlNjIzMi1mMWFlLTQxZGEtODUzNS03MzA0ODg5MWIxZTMiLCJzdWIiOiJyZWZyZXNoX3Rva2VuIiwiZXhwIjoxNzQyMzA5MTQwLCJpYXQiOjE3NDIzMDkxMzB9.zd-cbzbhry50DmC94xjxeBxXpX6HkHurgLpVorA9lg0", + }) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.FailedPrecondition, st.Code()) + s.Equal("invalid refresh token", st.Message()) + s.Nil(refreshTokensOut) + }) +} From 2729d1496338978055166935f5f5e783a29c6f21 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Tue, 18 Mar 2025 18:33:38 +0300 Subject: [PATCH 88/94] server: test create item --- test/functional/server/create_item_test.go | 81 ++++++++++++++++++++++ test/functional/server/helper_test.go | 15 ++++ test/functional/server/model.go | 11 +++ 3 files changed, 107 insertions(+) create mode 100644 test/functional/server/create_item_test.go create mode 100644 test/functional/server/model.go diff --git a/test/functional/server/create_item_test.go b/test/functional/server/create_item_test.go new file mode 100644 index 0000000..cf80009 --- /dev/null +++ b/test/functional/server/create_item_test.go @@ -0,0 +1,81 @@ +package server_test + +import ( + "context" + "time" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/bjlag/go-keeper/internal/generated/rpc" + "github.com/bjlag/go-keeper/test/infrastructure/fixture" + _ "github.com/bjlag/go-keeper/test/infrastructure/init" +) + +func (s *TestSuite) TestCreateItem() { + err := fixture.Load(s.db, "test/fixture/server") + s.Require().NoError(err) + + s.Run("success", func() { + ctx := s.login(context.Background(), "test@test.ru", "12345678") + + _, err := s.client.CreateItem(ctx, &rpc.CreateItemIn{ + Guid: "c904fe47-4ec8-4ff4-a642-774a7bf4e351", + EncryptedData: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + CreatedAt: timestamppb.New(time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)), + }) + s.Require().NoError(err) + + item := s.getFromDBByGUID(ctx, "c904fe47-4ec8-4ff4-a642-774a7bf4e351") + + s.Equal("c904fe47-4ec8-4ff4-a642-774a7bf4e351", item.GUID) + s.Equal([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, item.EncryptedData) + s.Equal("bf4e6232-f1ae-41da-8535-73048891b1e3", item.UserGUID) + s.True(time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC).Equal(item.CreatedAt.UTC())) + s.True(time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC).Equal(item.UpdatedAt.UTC())) + }) + + s.Run("permission denied", func() { + _, err := s.client.CreateItem(context.Background(), &rpc.CreateItemIn{ + Guid: "c904fe47-4ec8-4ff4-a642-774a7bf4e351", + EncryptedData: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + CreatedAt: timestamppb.New(time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)), + }) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.PermissionDenied, st.Code()) + s.Equal("permission denied", st.Message()) + }) + + s.Run("invalid item guid", func() { + ctx := s.login(context.Background(), "test@test.ru", "12345678") + + _, err := s.client.CreateItem(ctx, &rpc.CreateItemIn{ + Guid: "invalid guid", + EncryptedData: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + CreatedAt: timestamppb.New(time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)), + }) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.InvalidArgument, st.Code()) + s.Equal("invalid item guid", st.Message()) + }) + + s.Run("encrypted data is empty", func() { + ctx := s.login(context.Background(), "test@test.ru", "12345678") + + _, err := s.client.CreateItem(ctx, &rpc.CreateItemIn{ + Guid: "c904fe47-4ec8-4ff4-a642-774a7bf4e351", + EncryptedData: []byte{}, + CreatedAt: timestamppb.New(time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)), + }) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.InvalidArgument, st.Code()) + s.Equal("encrypted data is empty", st.Message()) + }) +} diff --git a/test/functional/server/helper_test.go b/test/functional/server/helper_test.go index 9e90768..3f0a216 100644 --- a/test/functional/server/helper_test.go +++ b/test/functional/server/helper_test.go @@ -7,6 +7,7 @@ import ( "google.golang.org/grpc/metadata" "github.com/bjlag/go-keeper/internal/generated/rpc" + "github.com/bjlag/go-keeper/test/functional/server" ) func (s *TestSuite) login(ctx context.Context, email, password string) context.Context { @@ -24,3 +25,17 @@ func (s *TestSuite) login(ctx context.Context, email, password string) context.C return metadata.NewOutgoingContext(ctx, md) } + +func (s *TestSuite) getFromDBByGUID(ctx context.Context, guid string) server.Item { + query := ` + SELECT guid, user_guid, encrypted_data, created_at, updated_at + FROM items + WHERE guid = $1 + ` + + var row server.Item + err := s.db.GetContext(ctx, &row, query, guid) + s.Require().NoError(err) + + return row +} diff --git a/test/functional/server/model.go b/test/functional/server/model.go new file mode 100644 index 0000000..3ca3dc5 --- /dev/null +++ b/test/functional/server/model.go @@ -0,0 +1,11 @@ +package server + +import "time" + +type Item struct { + GUID string `db:"guid"` + UserGUID string `db:"user_guid"` + EncryptedData []byte `db:"encrypted_data"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` +} From 659577d4fee0fe3e3022cd28d0172979185a8c8a Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Tue, 18 Mar 2025 18:58:51 +0300 Subject: [PATCH 89/94] server: test delete item --- test/functional/server/delete_item_test.go | 74 ++++++++++++++++++++++ test/functional/server/helper_test.go | 14 ++++ 2 files changed, 88 insertions(+) create mode 100644 test/functional/server/delete_item_test.go diff --git a/test/functional/server/delete_item_test.go b/test/functional/server/delete_item_test.go new file mode 100644 index 0000000..80aacb4 --- /dev/null +++ b/test/functional/server/delete_item_test.go @@ -0,0 +1,74 @@ +package server_test + +import ( + "context" + "github.com/bjlag/go-keeper/internal/generated/rpc" + "github.com/bjlag/go-keeper/test/infrastructure/fixture" + _ "github.com/bjlag/go-keeper/test/infrastructure/init" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func (s *TestSuite) TestDeleteItem() { + err := fixture.Load(s.db, "test/fixture/server") + s.Require().NoError(err) + + s.Run("success", func() { + ctx := s.login(context.Background(), "test@test.ru", "12345678") + + _, err := s.client.DeleteItem(ctx, &rpc.DeleteItemIn{ + Guid: "60308368-7729-4d2d-a510-67926f5a159b", + }) + s.Require().NoError(err) + + items := s.getAllFromDBByUserGUID(ctx, "bf4e6232-f1ae-41da-8535-73048891b1e3") + + s.Len(items, 3) + s.Condition(func() (success bool) { + success = true + for _, item := range items { + if item.GUID == "60308368-7729-4d2d-a510-67926f5a159b" { + success = false + } + } + return + }, "Не должно быть элемента с GUID 60308368-7729-4d2d-a510-67926f5a159b") + }) + + s.Run("permission denied", func() { + _, err := s.client.DeleteItem(context.Background(), &rpc.DeleteItemIn{ + Guid: "60308368-7729-4d2d-a510-67926f5a159b", + }) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.PermissionDenied, st.Code()) + s.Equal("permission denied", st.Message()) + }) + + s.Run("not found", func() { + ctx := s.login(context.Background(), "test@test.ru", "12345678") + + _, err := s.client.DeleteItem(ctx, &rpc.DeleteItemIn{ + Guid: "00000000-0000-0000-0000-000000000000", + }) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.NotFound, st.Code()) + s.Equal("item not found", st.Message()) + }) + + s.Run("not found if item belongs to other user", func() { + ctx := s.login(context.Background(), "test@test.ru", "12345678") + + _, err := s.client.DeleteItem(ctx, &rpc.DeleteItemIn{ + Guid: "d07f9605-0b8e-42f6-a07b-3e3f839a7bee", + }) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.NotFound, st.Code()) + s.Equal("item not found", st.Message()) + }) +} diff --git a/test/functional/server/helper_test.go b/test/functional/server/helper_test.go index 3f0a216..b933a72 100644 --- a/test/functional/server/helper_test.go +++ b/test/functional/server/helper_test.go @@ -39,3 +39,17 @@ func (s *TestSuite) getFromDBByGUID(ctx context.Context, guid string) server.Ite return row } + +func (s *TestSuite) getAllFromDBByUserGUID(ctx context.Context, guid string) []server.Item { + query := ` + SELECT guid, user_guid, encrypted_data, created_at, updated_at + FROM items + WHERE user_guid = $1 + ` + + var rows []server.Item + err := s.db.SelectContext(ctx, &rows, query, guid) + s.Require().NoError(err) + + return rows +} From 0cef600fd25dad5901f2519faa10e6c34de7cc7a Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Tue, 18 Mar 2025 19:20:47 +0300 Subject: [PATCH 90/94] server: test update item --- internal/rpc/update_item/handler.go | 4 +- .../usecase/server/item/update/contract.go | 2 +- .../usecase/server/item/update/usecase.go | 10 +- test/functional/server/update_item_test.go | 110 ++++++++++++++++++ 4 files changed, 118 insertions(+), 8 deletions(-) create mode 100644 test/functional/server/update_item_test.go diff --git a/internal/rpc/update_item/handler.go b/internal/rpc/update_item/handler.go index 093d67a..50ea300 100644 --- a/internal/rpc/update_item/handler.go +++ b/internal/rpc/update_item/handler.go @@ -51,10 +51,10 @@ func (h *Handler) Handle(ctx context.Context, in *pb.UpdateItemIn) (*pb.UpdateIt }) if err != nil { if errors.Is(err, update.ErrConflict) { - return nil, status.Error(codes.FailedPrecondition, "data outdated") + return nil, status.Error(codes.FailedPrecondition, "item is outdated") } - if errors.Is(err, update.ErrNotFoundUpdatedData) { + if errors.Is(err, update.ErrItemNotFound) { return nil, status.Error(codes.NotFound, "item not found") } diff --git a/internal/usecase/server/item/update/contract.go b/internal/usecase/server/item/update/contract.go index e1bdadf..7b1b6d7 100644 --- a/internal/usecase/server/item/update/contract.go +++ b/internal/usecase/server/item/update/contract.go @@ -9,6 +9,6 @@ import ( ) type store interface { - ItemByGUID(ctx context.Context, guid uuid.UUID) (*model.Item, error) + UserItemByGUID(ctx context.Context, userGUID, itemGUID uuid.UUID) (*model.Item, error) Update(ctx context.Context, guid uuid.UUID, userGUID uuid.UUID, updatedData model.UpdatedItem) error } diff --git a/internal/usecase/server/item/update/usecase.go b/internal/usecase/server/item/update/usecase.go index 4c250ee..dcdf3e6 100644 --- a/internal/usecase/server/item/update/usecase.go +++ b/internal/usecase/server/item/update/usecase.go @@ -12,8 +12,8 @@ import ( ) var ( - ErrNotFoundUpdatedData = errors.New("updated data is not found") - ErrConflict = errors.New("conflict") + ErrItemNotFound = errors.New("item not found") + ErrConflict = errors.New("conflict") ) type Usecase struct { @@ -29,10 +29,10 @@ func NewUsecase(store store) *Usecase { func (u Usecase) Do(ctx context.Context, in In) (newVersion int64, err error) { const op = "usecase.item.update.Do" - currentItem, err := u.store.ItemByGUID(ctx, in.ItemGUID) + currentItem, err := u.store.UserItemByGUID(ctx, in.UserGUID, in.ItemGUID) if err != nil { if errors.Is(err, item.ErrNotFound) { - return 0, ErrNotFoundUpdatedData + return 0, ErrItemNotFound } } @@ -49,7 +49,7 @@ func (u Usecase) Do(ctx context.Context, in In) (newVersion int64, err error) { err = u.store.Update(ctx, in.ItemGUID, in.UserGUID, data) if err != nil { if errors.Is(err, item.ErrNotAffectedRows) { - return 0, ErrNotFoundUpdatedData + return 0, ErrItemNotFound } return 0, fmt.Errorf("%s: %w", op, err) } diff --git a/test/functional/server/update_item_test.go b/test/functional/server/update_item_test.go new file mode 100644 index 0000000..fac8642 --- /dev/null +++ b/test/functional/server/update_item_test.go @@ -0,0 +1,110 @@ +package server_test + +import ( + "context" + "time" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/bjlag/go-keeper/internal/generated/rpc" + "github.com/bjlag/go-keeper/test/infrastructure/fixture" + _ "github.com/bjlag/go-keeper/test/infrastructure/init" +) + +func (s *TestSuite) TestUpdateItem() { + err := fixture.Load(s.db, "test/fixture/server") + s.Require().NoError(err) + + s.Run("success", func() { + ctx := s.login(context.Background(), "test@test.ru", "12345678") + + _, err := s.client.UpdateItem(ctx, &rpc.UpdateItemIn{ + Guid: "60308368-7729-4d2d-a510-67926f5a159b", + EncryptedData: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + Version: time.Date(2025, time.March, 15, 13, 0, 0, 0, time.UTC).UnixMicro(), + }) + s.Require().NoError(err) + + item := s.getFromDBByGUID(ctx, "60308368-7729-4d2d-a510-67926f5a159b") + + s.Equal("60308368-7729-4d2d-a510-67926f5a159b", item.GUID) + s.Equal([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, item.EncryptedData) + s.Equal("bf4e6232-f1ae-41da-8535-73048891b1e3", item.UserGUID) + s.True(time.Date(2025, time.March, 15, 13, 0, 0, 0, time.UTC).Equal(item.CreatedAt.UTC())) + s.InDelta(time.Now().UTC().Unix(), item.UpdatedAt.UTC().Unix(), 2) + }) + + s.Run("permission denied", func() { + _, err := s.client.UpdateItem(context.Background(), &rpc.UpdateItemIn{ + Guid: "60308368-7729-4d2d-a510-67926f5a159b", + EncryptedData: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + Version: time.Date(2025, time.March, 15, 13, 0, 0, 0, time.UTC).UnixMicro(), + }) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.PermissionDenied, st.Code()) + s.Equal("permission denied", st.Message()) + }) + + s.Run("invalid item guid", func() { + ctx := s.login(context.Background(), "test@test.ru", "12345678") + + _, err := s.client.UpdateItem(ctx, &rpc.UpdateItemIn{ + Guid: "invalid guid", + EncryptedData: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + Version: time.Date(2025, time.March, 15, 13, 0, 0, 0, time.UTC).UnixMicro(), + }) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.InvalidArgument, st.Code()) + s.Equal("invalid item guid", st.Message()) + }) + + s.Run("encrypted data is empty", func() { + ctx := s.login(context.Background(), "test@test.ru", "12345678") + + _, err := s.client.UpdateItem(ctx, &rpc.UpdateItemIn{ + Guid: "60308368-7729-4d2d-a510-67926f5a159b", + EncryptedData: []byte{}, + Version: time.Date(2025, time.March, 15, 13, 0, 0, 0, time.UTC).UnixMicro(), + }) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.InvalidArgument, st.Code()) + s.Equal("encrypted data is empty", st.Message()) + }) + + s.Run("item belongs to other user", func() { + ctx := s.login(context.Background(), "test@test.ru", "12345678") + + _, err := s.client.UpdateItem(ctx, &rpc.UpdateItemIn{ + Guid: "d07f9605-0b8e-42f6-a07b-3e3f839a7bee", + EncryptedData: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + Version: time.Date(2025, time.March, 15, 12, 0, 0, 0, time.UTC).UnixMicro(), + }) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.NotFound, st.Code()) + s.Equal("item not found", st.Message()) + }) + + s.Run("item is outdated", func() { + ctx := s.login(context.Background(), "test@test.ru", "12345678") + + _, err := s.client.UpdateItem(ctx, &rpc.UpdateItemIn{ + Guid: "60308368-7729-4d2d-a510-67926f5a159b", + EncryptedData: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + Version: time.Date(2025, time.March, 15, 12, 0, 0, 0, time.UTC).UnixMicro(), + }) + + st, ok := status.FromError(err) + s.True(ok) + s.Equal(codes.FailedPrecondition, st.Code()) + s.Equal("item is outdated", st.Message()) + }) +} From b263531503c23c8bb45e1d6d4fec1035270059b1 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Tue, 18 Mar 2025 23:26:10 +0300 Subject: [PATCH 91/94] client: own db for each user --- .gitignore | 1 - Makefile | 4 +- config/client.yaml.dist | 11 +-- data/.gitignore | 1 + internal/app/client/app.go | 104 ++++++++++++++++++++++----- internal/app/client/config.go | 22 +++--- internal/cli/message/message.go | 7 +- internal/cli/model/login/model.go | 48 ++++++++----- internal/cli/model/master/model.go | 12 +--- internal/cli/model/master/option.go | 8 --- internal/cli/model/register/model.go | 23 +++--- 11 files changed, 155 insertions(+), 86 deletions(-) create mode 100644 data/.gitignore diff --git a/.gitignore b/.gitignore index fdc7541..0472a60 100644 --- a/.gitignore +++ b/.gitignore @@ -41,5 +41,4 @@ config/client_test.yaml *.log # client -client.db __debug_bin* \ No newline at end of file diff --git a/Makefile b/Makefile index 4dacf78..a523552 100644 --- a/Makefile +++ b/Makefile @@ -47,10 +47,8 @@ docker-down-clear: ## migrate: apply migrations .PHONY: migrate migrate: - @echo " > Apply migrations (server)" + @echo " > Apply migrations" @go run $(DIR)/cmd/migrator -c="./config/migrator_server.yaml" - @echo " > Apply migrations (client)" - @go run $(DIR)/cmd/migrator -c="./config/migrator_client.yaml" ## lint: start linter .PHONY: lint diff --git a/config/client.yaml.dist b/config/client.yaml.dist index 9fec616..12d5a2a 100644 --- a/config/client.yaml.dist +++ b/config/client.yaml.dist @@ -1,5 +1,9 @@ env: dev +migration: + sourcePath: "./migrations/client" + table: "migrations" + server: host: localhost port: 8080 @@ -10,8 +14,5 @@ masterKey: length: 32 database: - host: localhost - port: 5444 - name: master - user: postgres - password: secret \ No newline at end of file + dir: data + prefix: client \ No newline at end of file diff --git a/data/.gitignore b/data/.gitignore new file mode 100644 index 0000000..f59ec20 --- /dev/null +++ b/data/.gitignore @@ -0,0 +1 @@ +* \ No newline at end of file diff --git a/internal/app/client/app.go b/internal/app/client/app.go index 71d42d6..8da3e51 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -3,9 +3,14 @@ package client import ( "context" + "crypto/md5" + "errors" "fmt" + "path" tea "github.com/charmbracelet/bubbletea" + "github.com/golang-migrate/migrate/v4" + "github.com/jmoiron/sqlx" "go.uber.org/zap" formCreate "github.com/bjlag/go-keeper/internal/cli/model/create" @@ -22,6 +27,7 @@ import ( crypt "github.com/bjlag/go-keeper/internal/infrastructure/crypt/cipher" "github.com/bjlag/go-keeper/internal/infrastructure/crypt/master_key" "github.com/bjlag/go-keeper/internal/infrastructure/db/sqlite" + "github.com/bjlag/go-keeper/internal/infrastructure/migrator" rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" sItem "github.com/bjlag/go-keeper/internal/infrastructure/store/client/item" "github.com/bjlag/go-keeper/internal/infrastructure/store/client/option" @@ -51,25 +57,35 @@ func NewApp(cfg Config, log *zap.Logger) *App { func (a *App) Run(ctx context.Context) error { const op = "app.Run" - storeTokens := token.NewStore() + tokens := token.NewStore() - rpcClient, err := rpc.NewRPCClient(a.cfg.Server.Host, a.cfg.Server.Port, storeTokens, a.log) + client, err := rpc.NewRPCClient(a.cfg.Server.Host, a.cfg.Server.Port, tokens, a.log) if err != nil { - a.log.Error("failed to create rpc client", zap.Error(err)) + a.log.Error("Failed to create rpc client", zap.Error(err)) return fmt.Errorf("%s:%w", op, err) } defer func() { - _ = rpcClient.Close() + _ = client.Close() }() - // TODO базу создавать и подключаться после успешного логин - // TODO название файла базы должно быть уникальным под каждую учетку под которой авторизовались - db, err := sqlite.New("./client.db").Connect() + email, pass, err := a.login(client, tokens) if err != nil { - a.log.Error("failed to open db", zap.Error(err)) - return fmt.Errorf("%s: %w", op, err) + a.log.Error("Failed to login", zap.Error(err)) + return fmt.Errorf("%s:%w", op, err) + } + if email == "" { + return nil } + db, err := a.initDB(email) + if err != nil { + a.log.Error("Failed to init db", zap.Error(err)) + return fmt.Errorf("%s:%w", op, err) + } + defer func() { + _ = db.Close() + }() + salter := master_key.NewSaltGenerator(a.cfg.MasterKey.SaltLength) keymaker := master_key.NewKeyGenerator(a.cfg.MasterKey.IterCount, a.cfg.MasterKey.Length) cipher := new(crypt.Cipher) @@ -77,18 +93,20 @@ func (a *App) Run(ctx context.Context) error { storeItem := sItem.NewStore(db) storeOption := option.NewStore(db) - ucLogin := login.NewUsecase(rpcClient, storeTokens) - ucRegister := register.NewUsecase(rpcClient, storeTokens) - ucMasterKey := mkey.NewUsecase(storeTokens, storeOption, salter, keymaker) - ucSync := sync.NewUsecase(rpcClient, storeItem, storeTokens, cipher) - ucItemSync := itemSync.NewUsecase(rpcClient, storeItem, storeTokens, cipher) - ucCreateItem := create.NewUsecase(rpcClient, storeItem, storeTokens, cipher) - ucSaveItem := edit.NewUsecase(rpcClient, storeItem, storeTokens, cipher) - ucRemoveItem := remove.NewUsecase(rpcClient, storeItem) + ucMasterKey := mkey.NewUsecase(tokens, storeOption, salter, keymaker) + err = ucMasterKey.Do(ctx, mkey.Data{Password: pass}) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + ucSync := sync.NewUsecase(client, storeItem, tokens, cipher) + ucItemSync := itemSync.NewUsecase(client, storeItem, tokens, cipher) + ucCreateItem := create.NewUsecase(client, storeItem, tokens, cipher) + ucSaveItem := edit.NewUsecase(client, storeItem, tokens, cipher) + ucRemoveItem := remove.NewUsecase(client, storeItem) fetchItem := item.NewFetcher(storeItem) - frmRegister := formRegister.InitModel(ucRegister, ucMasterKey) frmSync := syncItem.InitModel(ucItemSync) frmPasswordItem := password.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem, frmSync) frmTextItem := text.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem, frmSync) @@ -96,7 +114,6 @@ func (a *App) Run(ctx context.Context) error { frmFileItem := file.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem, frmSync) m := master.InitModel( - master.WithLoginForm(formLogin.InitModel(ucLogin, ucMasterKey, frmRegister)), master.WithCreatForm(formCreate.InitModel(frmPasswordItem, frmTextItem, frmBankCardItem, frmFileItem)), master.WithListForm(list.InitModel(ucSync, fetchItem, frmPasswordItem, frmTextItem, frmBankCardItem, frmFileItem)), ) @@ -116,3 +133,52 @@ func (a *App) Run(ctx context.Context) error { return err } + +func (a *App) login(client *rpc.RPCClient, tokens *token.Store) (email string, pass string, err error) { + ucLogin := login.NewUsecase(client, tokens) + ucRegister := register.NewUsecase(client, tokens) + + frmRegister := formRegister.InitModel(ucRegister) + frmLogin := formLogin.InitModel(ucLogin, frmRegister) + + mLogin, err := tea.NewProgram(frmLogin, tea.WithAltScreen()).Run() + if err != nil { + a.log.Error("Failed to run cli program for login", zap.Error(err)) + return + } + + ml, ok := mLogin.(*formLogin.Model) + if !ok { + panic("failed to run model cli program") + } + + email = ml.UserEmail() + pass = ml.UserPass() + + return +} + +func (a *App) initDB(email string) (db *sqlx.DB, err error) { + emailHash := md5.Sum([]byte(email)) //nolint:gosec + dbName := fmt.Sprintf("%s_%x.db", a.cfg.Database.Prefix, emailHash) + pathToDB := path.Join(a.cfg.Database.Dir, dbName) + + db, err = sqlite.New(pathToDB).Connect() + if err != nil { + return + } + + m, err := migrator.Get(db, migrator.TypeSqlite, "", a.cfg.Migration.SourcePath, a.cfg.Migration.Table) + if err != nil { + return + } + + if err = m.Up(); err != nil { + if !errors.Is(err, migrate.ErrNoChange) { + return + } + err = nil + } + + return +} diff --git a/internal/app/client/config.go b/internal/app/client/config.go index 9e57d58..9796daf 100644 --- a/internal/app/client/config.go +++ b/internal/app/client/config.go @@ -5,6 +5,14 @@ type Config struct { // Env окружение. Env string `yaml:"env" env:"ENV" env-default:"dev" env-description:"Environment" json:"env"` + // Migration настройки магратора. + Migration struct { + // SourcePath путь до файлов миграций. + SourcePath string `yaml:"sourcePath" env:"MIGRATION_SOURCE_PATH" env-description:"Path to migration source" json:"source_path"` + // Table название таблицы с примененными миграциями. + Table string `yaml:"table" env:"MIGRATION_TABLE" env-description:"Migration table" json:"table"` + } `yaml:"migration" json:"migration"` + // Server содержит настройки подключения к серверу. Server struct { // Host хост сервера. @@ -25,15 +33,9 @@ type Config struct { // Database настройки подключения к базе данных клиента. Database struct { - // Host хост базы. - Host string `yaml:"host" env:"DB_HOST" env-description:"Database host" json:"host"` - // Port порт базы. - Port string `yaml:"port" env:"DB_PORT" env-description:"Database port" json:"port"` - // Name название базы данных. - Name string `yaml:"name" env:"DB_NAME" env-description:"Database name" json:"name"` - // User пользователь. - User string `yaml:"user" env:"DB_USER" env-description:"Database user" json:"user"` - // Password пароль. - Password string `yaml:"password" env:"DB_PASSWORD" env-description:"Database password" json:"password"` + // Dir директория, где будут лежать файлы БД. + Dir string `yaml:"dir" env:"DB_DIR" env-description:"Database directory" json:"dir"` + // Prefix префикс в названии файла базы данных. + Prefix string `yaml:"prefix" env:"DB_PREFIX" env-description:"Database prefix" json:"prefix"` } `yaml:"database" json:"database"` } diff --git a/internal/cli/message/message.go b/internal/cli/message/message.go index 48fcafe..860eccc 100644 --- a/internal/cli/message/message.go +++ b/internal/cli/message/message.go @@ -17,8 +17,11 @@ type ( LoginModel tea.Model } - // SuccessMsg вспомогательное сообщение, что какое-то событие выполнилось успешно, например, аутентификация. - SuccessMsg struct{} + // SuccessLoginMsg вспомогательное сообщение, что какое-то событие выполнилось успешно, например, аутентификация. + SuccessLoginMsg struct { + Email string + Password string + } // OpenCategoriesMsg сообщение используется для открытия списка категорий. OpenCategoriesMsg struct{} diff --git a/internal/cli/model/login/model.go b/internal/cli/model/login/model.go index 861b41e..b6e050c 100644 --- a/internal/cli/model/login/model.go +++ b/internal/cli/model/login/model.go @@ -34,7 +34,6 @@ const ( var errPasswordInvalid = common.NewFormError("Неверный email или пароль") type Model struct { - main tea.Model help help.Model header string elements []interface{} @@ -45,9 +44,20 @@ type Model struct { usecaseLogin *login.Usecase usecaseMasterKey *master_key.Usecase + + userEmail string + userPassword string +} + +func (f *Model) UserEmail() string { + return f.userEmail +} + +func (f *Model) UserPass() string { + return f.userPassword } -func InitModel(usecaseLogin *login.Usecase, usecaseMasterKey *master_key.Usecase, fromRegister tea.Model) *Model { +func InitModel(usecaseLogin *login.Usecase, fromRegister tea.Model) *Model { f := &Model{ help: help.New(), header: "Авторизация", @@ -61,8 +71,8 @@ func InitModel(usecaseLogin *login.Usecase, usecaseMasterKey *master_key.Usecase fromRegister: fromRegister, - usecaseLogin: usecaseLogin, - usecaseMasterKey: usecaseMasterKey, + usecaseLogin: usecaseLogin, + //usecaseMasterKey: usecaseMasterKey, } for i := range f.elements { @@ -81,10 +91,6 @@ func InitModel(usecaseLogin *login.Usecase, usecaseMasterKey *master_key.Usecase return f } -func (f *Model) SetMainModel(m tea.Model) { - f.main = m -} - func (f *Model) Init() tea.Cmd { return nil } @@ -105,8 +111,11 @@ func (f *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { f.elements[i] = e } } - case message.SuccessMsg: - return f.main.Update(msg) + case message.SuccessLoginMsg: + f.userEmail = msg.Email + f.userPassword = msg.Password + + return f, tea.Quit case tea.KeyMsg: switch { case key.Matches(msg, common.Keys.Quit): @@ -260,15 +269,18 @@ func (f *Model) submit() (tea.Model, tea.Cmd) { return f, nil } - err = f.usecaseMasterKey.Do(context.TODO(), master_key.Data{ - Password: password.Value(), - }) - if err != nil { - f.err = err - return f, nil - } + f.userEmail = email.Value() + f.userPassword = password.Value() + + //err = f.usecaseMasterKey.Do(context.TODO(), master_key.Data{ + // Password: password.Value(), + //}) + //if err != nil { + // f.err = err + // return f, nil + //} - return f.main.Update(message.SuccessMsg{}) + return f, tea.Quit } func (f *Model) updateInputs(msg tea.Msg) tea.Cmd { diff --git a/internal/cli/model/master/model.go b/internal/cli/model/master/model.go index 83185c0..26d73ff 100644 --- a/internal/cli/model/master/model.go +++ b/internal/cli/model/master/model.go @@ -13,7 +13,6 @@ import ( "github.com/bjlag/go-keeper/internal/cli/message" "github.com/bjlag/go-keeper/internal/cli/model/create" listf "github.com/bjlag/go-keeper/internal/cli/model/list" - "github.com/bjlag/go-keeper/internal/cli/model/login" "github.com/bjlag/go-keeper/internal/cli/style" ) @@ -30,7 +29,6 @@ type Model struct { pos int err error - formLogin *login.Model formCreate *create.Model formList *listf.Model } @@ -54,11 +52,7 @@ func InitModel(opts ...Option) *Model { } func (m *Model) Init() tea.Cmd { - return tea.Batch( - func() tea.Msg { - return message.OpenLoginMsg{} - }, - ) + return nil } func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { @@ -112,15 +106,13 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m.Update(nil) // Forms - case message.OpenLoginMsg: - return m.formLogin.Update(msg) case message.OpenCategoriesMsg: return m.formList.Update(msg) case message.OpenItemsMsg: return m.formList.Update(msg) // Success - case message.SuccessMsg: + case message.SuccessLoginMsg: return m.Update(nil) } diff --git a/internal/cli/model/master/option.go b/internal/cli/model/master/option.go index 7ff49cb..17b3cd4 100644 --- a/internal/cli/model/master/option.go +++ b/internal/cli/model/master/option.go @@ -3,18 +3,10 @@ package master import ( "github.com/bjlag/go-keeper/internal/cli/model/create" "github.com/bjlag/go-keeper/internal/cli/model/list" - "github.com/bjlag/go-keeper/internal/cli/model/login" ) type Option func(*Model) -func WithLoginForm(form *login.Model) Option { - return func(m *Model) { - form.SetMainModel(m) - m.formLogin = form - } -} - func WithCreatForm(form *create.Model) Option { return func(m *Model) { form.SetMainModel(m) diff --git a/internal/cli/model/register/model.go b/internal/cli/model/register/model.go index de4117b..504b667 100644 --- a/internal/cli/model/register/model.go +++ b/internal/cli/model/register/model.go @@ -45,7 +45,7 @@ type Model struct { usecaseMasterKey *master_key.Usecase } -func InitModel(usecaseRegister *register.Usecase, usecaseMasterKey *master_key.Usecase) *Model { +func InitModel(usecaseRegister *register.Usecase) *Model { f := &Model{ help: help.New(), header: "Регистрация", @@ -56,8 +56,8 @@ func InitModel(usecaseRegister *register.Usecase, usecaseMasterKey *master_key.U posBackBtn: button.CreateDefaultButton("Назад"), }, - usecaseRegister: usecaseRegister, - usecaseMasterKey: usecaseMasterKey, + usecaseRegister: usecaseRegister, + //usecaseMasterKey: usecaseMasterKey, } for i := range f.elements { @@ -245,15 +245,18 @@ func (f *Model) submit() (tea.Model, tea.Cmd) { return f, nil } - err = f.usecaseMasterKey.Do(context.TODO(), master_key.Data{ + //err = f.usecaseMasterKey.Do(context.TODO(), master_key.Data{ + // Password: password.Value(), + //}) + //if err != nil { + // f.err = err + // return f, nil + //} + + return f.loginModel.Update(message.SuccessLoginMsg{ + Email: email.Value(), Password: password.Value(), }) - if err != nil { - f.err = err - return f, nil - } - - return f.loginModel.Update(message.SuccessMsg{}) } func (f *Model) updateInputs(msg tea.Msg) tea.Cmd { From 6d653e3cd9ce6652bce5bf9ff8c0a43cee081761 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Wed, 19 Mar 2025 16:47:51 +0300 Subject: [PATCH 92/94] client: encrypt/decrypt local data while logout/login --- internal/app/client/app.go | 21 +++-- internal/domain/client/backup.go | 20 +++++ .../store/client/backup/model.go | 33 ++++++++ .../store/client/backup/store.go | 68 ++++++++++++++++ .../infrastructure/store/client/item/store.go | 34 +++++++- internal/usecase/client/backup/contract.go | 24 ++++++ internal/usecase/client/backup/usecase.go | 77 +++++++++++++++++++ internal/usecase/client/restore/contract.go | 24 ++++++ internal/usecase/client/restore/usecase.go | 75 ++++++++++++++++++ .../client/003_create_backup_table.up.sql | 4 + 10 files changed, 373 insertions(+), 7 deletions(-) create mode 100644 internal/domain/client/backup.go create mode 100644 internal/infrastructure/store/client/backup/model.go create mode 100644 internal/infrastructure/store/client/backup/store.go create mode 100644 internal/usecase/client/backup/contract.go create mode 100644 internal/usecase/client/backup/usecase.go create mode 100644 internal/usecase/client/restore/contract.go create mode 100644 internal/usecase/client/restore/usecase.go create mode 100644 migrations/client/003_create_backup_table.up.sql diff --git a/internal/app/client/app.go b/internal/app/client/app.go index 8da3e51..6e57938 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -29,9 +29,11 @@ import ( "github.com/bjlag/go-keeper/internal/infrastructure/db/sqlite" "github.com/bjlag/go-keeper/internal/infrastructure/migrator" rpc "github.com/bjlag/go-keeper/internal/infrastructure/rpc/client" + backups "github.com/bjlag/go-keeper/internal/infrastructure/store/client/backup" sItem "github.com/bjlag/go-keeper/internal/infrastructure/store/client/item" "github.com/bjlag/go-keeper/internal/infrastructure/store/client/option" "github.com/bjlag/go-keeper/internal/infrastructure/store/client/token" + "github.com/bjlag/go-keeper/internal/usecase/client/backup" "github.com/bjlag/go-keeper/internal/usecase/client/item/create" "github.com/bjlag/go-keeper/internal/usecase/client/item/edit" "github.com/bjlag/go-keeper/internal/usecase/client/item/remove" @@ -39,6 +41,7 @@ import ( "github.com/bjlag/go-keeper/internal/usecase/client/login" mkey "github.com/bjlag/go-keeper/internal/usecase/client/master_key" "github.com/bjlag/go-keeper/internal/usecase/client/register" + "github.com/bjlag/go-keeper/internal/usecase/client/restore" "github.com/bjlag/go-keeper/internal/usecase/client/sync" ) @@ -92,6 +95,7 @@ func (a *App) Run(ctx context.Context) error { storeItem := sItem.NewStore(db) storeOption := option.NewStore(db) + storeBackup := backups.NewStore(db) ucMasterKey := mkey.NewUsecase(tokens, storeOption, salter, keymaker) err = ucMasterKey.Do(ctx, mkey.Data{Password: pass}) @@ -104,6 +108,8 @@ func (a *App) Run(ctx context.Context) error { ucCreateItem := create.NewUsecase(client, storeItem, tokens, cipher) ucSaveItem := edit.NewUsecase(client, storeItem, tokens, cipher) ucRemoveItem := remove.NewUsecase(client, storeItem) + ucBackup := backup.NewUsecase(storeItem, tokens, storeBackup, cipher) + ucRestore := restore.NewUsecase(storeItem, tokens, storeBackup, cipher) fetchItem := item.NewFetcher(storeItem) @@ -113,17 +119,20 @@ func (a *App) Run(ctx context.Context) error { frmBankCardItem := bank_card.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem, frmSync) frmFileItem := file.InitModel(ucCreateItem, ucSaveItem, ucRemoveItem, frmSync) + err = ucRestore.Do(ctx) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + m := master.InitModel( master.WithCreatForm(formCreate.InitModel(frmPasswordItem, frmTextItem, frmBankCardItem, frmFileItem)), master.WithListForm(list.InitModel(ucSync, fetchItem, frmPasswordItem, frmTextItem, frmBankCardItem, frmFileItem)), ) - - f, err := tea.LogToFile("debug.log", "debug") - if err != nil { - panic(err) - } defer func() { - _ = f.Close() + err = ucBackup.Do(ctx) + if err != nil { + a.log.Error("Failed to backup", zap.Error(err)) + } }() _, err = tea.NewProgram(m, tea.WithAltScreen(), tea.WithContext(ctx)).Run() diff --git a/internal/domain/client/backup.go b/internal/domain/client/backup.go new file mode 100644 index 0000000..66f70c9 --- /dev/null +++ b/internal/domain/client/backup.go @@ -0,0 +1,20 @@ +package client + +import ( + "github.com/google/uuid" + "time" +) + +type Backup struct { + GUID uuid.UUID + Value []byte +} + +type BackupValue struct { + Category Category `json:"category"` + Title string `json:"title"` + Value *[]byte `json:"value"` + Notes string `json:"notes"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} diff --git a/internal/infrastructure/store/client/backup/model.go b/internal/infrastructure/store/client/backup/model.go new file mode 100644 index 0000000..1a6ecbf --- /dev/null +++ b/internal/infrastructure/store/client/backup/model.go @@ -0,0 +1,33 @@ +package backup + +import ( + "github.com/bjlag/go-keeper/internal/domain/client" + "github.com/google/uuid" +) + +type row struct { + GUID uuid.UUID `db:"guid"` + Value []byte `db:"value"` +} + +func fromModel(item client.Backup) row { + return row{ + GUID: item.GUID, + Value: item.Value, + } +} + +func (r row) toModel() client.Backup { + return client.Backup{ + GUID: r.GUID, + Value: r.Value, + } +} + +func toModels(rows []row) []client.Backup { + items := make([]client.Backup, len(rows)) + for i, item := range rows { + items[i] = item.toModel() + } + return items +} diff --git a/internal/infrastructure/store/client/backup/store.go b/internal/infrastructure/store/client/backup/store.go new file mode 100644 index 0000000..dad4728 --- /dev/null +++ b/internal/infrastructure/store/client/backup/store.go @@ -0,0 +1,68 @@ +package backup + +import ( + "context" + "fmt" + + "github.com/jmoiron/sqlx" + + "github.com/bjlag/go-keeper/internal/domain/client" +) + +const prefixOp = "store.backup." + +type Store struct { + db *sqlx.DB +} + +func NewStore(db *sqlx.DB) *Store { + return &Store{ + db: db, + } +} + +func (s *Store) Save(ctx context.Context, items []client.Backup) error { + const op = prefixOp + "Save" + + rows := make([]row, 0, len(items)) + for _, item := range items { + rows = append(rows, fromModel(item)) + } + + query := ` + INSERT INTO backup (guid, value) VALUES (:guid, :value) + ON CONFLICT (guid) DO UPDATE + SET value = excluded.value; + ` + + if _, err := s.db.NamedExecContext(ctx, query, rows); err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + return nil +} + +func (s *Store) Get(ctx context.Context) ([]client.Backup, error) { + const op = prefixOp + "Get" + + query := `SELECT guid, value FROM backup` + + var rows []row + if err := s.db.SelectContext(ctx, &rows, query); err != nil { + return nil, fmt.Errorf("%s: %w", op, err) + } + + return toModels(rows), nil +} + +func (s *Store) Erase(ctx context.Context) error { + const op = prefixOp + "Erase" + + query := `DELETE FROM backup;` + + if _, err := s.db.ExecContext(ctx, query); err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + return nil +} diff --git a/internal/infrastructure/store/client/item/store.go b/internal/infrastructure/store/client/item/store.go index 879f3bb..70ca7ae 100644 --- a/internal/infrastructure/store/client/item/store.go +++ b/internal/infrastructure/store/client/item/store.go @@ -107,7 +107,6 @@ func (s *Store) DeleteItem(ctx context.Context, guid uuid.UUID) error { // SaveItems сохраняет несколько элементов. func (s *Store) SaveItems(ctx context.Context, items []model.RawItem) error { - // todo пределать на модель model.Item const op = prefixOp + "SaveItems" tx, err := s.db.BeginTx(ctx, nil) @@ -163,3 +162,36 @@ func (s *Store) ItemsByCategory(ctx context.Context, category model.Category) ([ return toModels(rows), nil } + +func (s *Store) Items(ctx context.Context, limit, offset int64) ([]model.RawItem, error) { + const op = prefixOp + "Items" + + query := ` + SELECT guid, category_id, title, value, notes, created_at, updated_at + FROM items + LIMIT $1 OFFSET $2; + ` + + var rows []row + err := s.db.SelectContext(ctx, &rows, query, limit, offset) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, nil + } + return nil, fmt.Errorf("%s: %w", op, err) + } + + return toModels(rows), nil +} + +func (s *Store) EraseItems(ctx context.Context) error { + const op = prefixOp + "EraseItems" + + query := `DELETE FROM items;` + + if _, err := s.db.ExecContext(ctx, query); err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + return nil +} diff --git a/internal/usecase/client/backup/contract.go b/internal/usecase/client/backup/contract.go new file mode 100644 index 0000000..841b5e1 --- /dev/null +++ b/internal/usecase/client/backup/contract.go @@ -0,0 +1,24 @@ +package backup + +import ( + "context" + + model "github.com/bjlag/go-keeper/internal/domain/client" +) + +type items interface { + Items(ctx context.Context, limit, offset int64) ([]model.RawItem, error) + EraseItems(ctx context.Context) error +} + +type tokens interface { + MasterKey() []byte +} + +type backup interface { + Save(ctx context.Context, items []model.Backup) error +} + +type cipher interface { + Encrypt(data, key []byte) ([]byte, error) +} diff --git a/internal/usecase/client/backup/usecase.go b/internal/usecase/client/backup/usecase.go new file mode 100644 index 0000000..6bf34c3 --- /dev/null +++ b/internal/usecase/client/backup/usecase.go @@ -0,0 +1,77 @@ +// Package backup отвечает за сброс локальных данных в бекап в зашифрованном виде. +package backup + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/bjlag/go-keeper/internal/domain/client" +) + +type Usecase struct { + items items + tokens tokens + backup backup + cipher cipher +} + +func NewUsecase(items items, tokens tokens, backups backup, cipher cipher) *Usecase { + return &Usecase{ + items: items, + tokens: tokens, + backup: backups, + cipher: cipher, + } +} + +func (u *Usecase) Do(ctx context.Context) error { + const op = "usecase.backup.Do" + + itemModels, err := u.items.Items(ctx, 100, 0) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + if len(itemModels) == 0 { + return nil + } + + key := u.tokens.MasterKey() + backupItems := make([]client.Backup, 0, len(itemModels)) + for _, i := range itemModels { + v := &client.BackupValue{ + Category: i.Category, + Title: i.Title, + Value: i.Value, + Notes: i.Notes, + CreatedAt: i.CreatedAt, + UpdatedAt: i.UpdatedAt, + } + + marshaledValue, err := json.Marshal(v) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + encrypted, err := u.cipher.Encrypt(marshaledValue, key) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + backupItems = append(backupItems, client.Backup{ + GUID: i.GUID, + Value: encrypted, + }) + } + + if err = u.backup.Save(ctx, backupItems); err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + if err = u.items.EraseItems(ctx); err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + return nil +} diff --git a/internal/usecase/client/restore/contract.go b/internal/usecase/client/restore/contract.go new file mode 100644 index 0000000..9d283cb --- /dev/null +++ b/internal/usecase/client/restore/contract.go @@ -0,0 +1,24 @@ +package restore + +import ( + "context" + + model "github.com/bjlag/go-keeper/internal/domain/client" +) + +type items interface { + SaveItems(ctx context.Context, items []model.RawItem) error +} + +type tokens interface { + MasterKey() []byte +} + +type backup interface { + Get(ctx context.Context) ([]model.Backup, error) + Erase(ctx context.Context) error +} + +type cipher interface { + Decrypt(encryptedData, key []byte) ([]byte, error) +} diff --git a/internal/usecase/client/restore/usecase.go b/internal/usecase/client/restore/usecase.go new file mode 100644 index 0000000..de083b2 --- /dev/null +++ b/internal/usecase/client/restore/usecase.go @@ -0,0 +1,75 @@ +// Package restore отвечает за восстановление локальных данных из бекапа. +package restore + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/bjlag/go-keeper/internal/domain/client" +) + +type Usecase struct { + items items + tokens tokens + backup backup + cipher cipher +} + +func NewUsecase(items items, tokens tokens, backups backup, cipher cipher) *Usecase { + return &Usecase{ + items: items, + tokens: tokens, + backup: backups, + cipher: cipher, + } +} + +func (u *Usecase) Do(ctx context.Context) error { + const op = "usecase.backup.Do" + + backupItems, err := u.backup.Get(ctx) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + if len(backupItems) == 0 { + return nil + } + + var v client.BackupValue + + key := u.tokens.MasterKey() + itemModels := make([]client.RawItem, 0, len(backupItems)) + for _, bi := range backupItems { + decrypted, err := u.cipher.Decrypt(bi.Value, key) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + err = json.Unmarshal(decrypted, &v) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + itemModels = append(itemModels, client.RawItem{ + GUID: bi.GUID, + Category: v.Category, + Title: v.Title, + Value: v.Value, + Notes: v.Notes, + CreatedAt: v.CreatedAt, + UpdatedAt: v.UpdatedAt, + }) + } + + if err = u.items.SaveItems(ctx, itemModels); err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + if err = u.backup.Erase(ctx); err != nil { + return fmt.Errorf("%s: %w", op, err) + } + + return nil +} diff --git a/migrations/client/003_create_backup_table.up.sql b/migrations/client/003_create_backup_table.up.sql new file mode 100644 index 0000000..8975ace --- /dev/null +++ b/migrations/client/003_create_backup_table.up.sql @@ -0,0 +1,4 @@ +CREATE TABLE IF NOT EXISTS backup ( + guid text PRIMARY KEY NOT NULL, + value text NOT NULL +); From 274f9c15f547df9c6dbf6f7ef43aac14f3f04c9a Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Wed, 19 Mar 2025 17:53:30 +0300 Subject: [PATCH 93/94] client: refactoring --- cmd/client/main.go | 3 +-- internal/app/client/app.go | 2 +- internal/cli/model/login/model.go | 15 +-------------- internal/cli/model/register/model.go | 16 ++-------------- internal/domain/client/backup.go | 3 ++- internal/infrastructure/rpc/server/option.go | 11 ++--------- internal/infrastructure/rpc/server/server.go | 2 -- .../infrastructure/store/client/backup/model.go | 3 ++- test/functional/server/delete_item_test.go | 6 ++++-- test/functional/server/helper_test.go | 1 + test/infrastructure/container/container.go | 1 - 11 files changed, 16 insertions(+), 47 deletions(-) diff --git a/cmd/client/main.go b/cmd/client/main.go index b8dd390..b8896bd 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -11,7 +11,6 @@ import ( "flag" "fmt" logNative "log" - "os" "os/signal" "syscall" @@ -48,7 +47,7 @@ func main() { if viewVersion { fmt.Printf("Version: %s\nBuild: %s\n", buildVersion, buildDate) - os.Exit(0) + return } var cfg client.Config diff --git a/internal/app/client/app.go b/internal/app/client/app.go index 6e57938..5ff0a2b 100644 --- a/internal/app/client/app.go +++ b/internal/app/client/app.go @@ -3,7 +3,7 @@ package client import ( "context" - "crypto/md5" + "crypto/md5" //nolint:gosec "errors" "fmt" "path" diff --git a/internal/cli/model/login/model.go b/internal/cli/model/login/model.go index b6e050c..90eed7e 100644 --- a/internal/cli/model/login/model.go +++ b/internal/cli/model/login/model.go @@ -20,7 +20,6 @@ import ( "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/infrastructure/validator" "github.com/bjlag/go-keeper/internal/usecase/client/login" - "github.com/bjlag/go-keeper/internal/usecase/client/master_key" ) const ( @@ -41,9 +40,7 @@ type Model struct { err error fromRegister tea.Model - - usecaseLogin *login.Usecase - usecaseMasterKey *master_key.Usecase + usecaseLogin *login.Usecase userEmail string userPassword string @@ -70,9 +67,7 @@ func InitModel(usecaseLogin *login.Usecase, fromRegister tea.Model) *Model { }, fromRegister: fromRegister, - usecaseLogin: usecaseLogin, - //usecaseMasterKey: usecaseMasterKey, } for i := range f.elements { @@ -272,14 +267,6 @@ func (f *Model) submit() (tea.Model, tea.Cmd) { f.userEmail = email.Value() f.userPassword = password.Value() - //err = f.usecaseMasterKey.Do(context.TODO(), master_key.Data{ - // Password: password.Value(), - //}) - //if err != nil { - // f.err = err - // return f, nil - //} - return f, tea.Quit } diff --git a/internal/cli/model/register/model.go b/internal/cli/model/register/model.go index 504b667..b2a30e3 100644 --- a/internal/cli/model/register/model.go +++ b/internal/cli/model/register/model.go @@ -19,7 +19,6 @@ import ( "github.com/bjlag/go-keeper/internal/cli/message" "github.com/bjlag/go-keeper/internal/cli/style" "github.com/bjlag/go-keeper/internal/infrastructure/validator" - "github.com/bjlag/go-keeper/internal/usecase/client/master_key" "github.com/bjlag/go-keeper/internal/usecase/client/register" ) @@ -39,10 +38,8 @@ type Model struct { pos int err error - loginModel tea.Model - - usecaseRegister *register.Usecase - usecaseMasterKey *master_key.Usecase + loginModel tea.Model + usecaseRegister *register.Usecase } func InitModel(usecaseRegister *register.Usecase) *Model { @@ -57,7 +54,6 @@ func InitModel(usecaseRegister *register.Usecase) *Model { }, usecaseRegister: usecaseRegister, - //usecaseMasterKey: usecaseMasterKey, } for i := range f.elements { @@ -245,14 +241,6 @@ func (f *Model) submit() (tea.Model, tea.Cmd) { return f, nil } - //err = f.usecaseMasterKey.Do(context.TODO(), master_key.Data{ - // Password: password.Value(), - //}) - //if err != nil { - // f.err = err - // return f, nil - //} - return f.loginModel.Update(message.SuccessLoginMsg{ Email: email.Value(), Password: password.Value(), diff --git a/internal/domain/client/backup.go b/internal/domain/client/backup.go index 66f70c9..cbc9d60 100644 --- a/internal/domain/client/backup.go +++ b/internal/domain/client/backup.go @@ -1,8 +1,9 @@ package client import ( - "github.com/google/uuid" "time" + + "github.com/google/uuid" ) type Backup struct { diff --git a/internal/infrastructure/rpc/server/option.go b/internal/infrastructure/rpc/server/option.go index b0fad51..fae01e5 100644 --- a/internal/infrastructure/rpc/server/option.go +++ b/internal/infrastructure/rpc/server/option.go @@ -1,23 +1,16 @@ package server import ( - "go.uber.org/zap" "net" + "go.uber.org/zap" + "github.com/bjlag/go-keeper/internal/infrastructure/auth" ) // Option тип параметра сервера. type Option func(*RPCServer) -// WithAddress передача адреса сервера. -func WithAddress(host string, port int) Option { - return func(s *RPCServer) { - s.host = host - s.port = port - } -} - // WithListener передача сетевого прослушивателя сервера. func WithListener(listener net.Listener) Option { return func(s *RPCServer) { diff --git a/internal/infrastructure/rpc/server/server.go b/internal/infrastructure/rpc/server/server.go index 73fef79..e9de25d 100644 --- a/internal/infrastructure/rpc/server/server.go +++ b/internal/infrastructure/rpc/server/server.go @@ -18,8 +18,6 @@ import ( type RPCServer struct { pb.UnimplementedKeeperServer - host string - port int listener net.Listener handlers map[string]any jwt *auth.JWT diff --git a/internal/infrastructure/store/client/backup/model.go b/internal/infrastructure/store/client/backup/model.go index 1a6ecbf..d6701f3 100644 --- a/internal/infrastructure/store/client/backup/model.go +++ b/internal/infrastructure/store/client/backup/model.go @@ -1,8 +1,9 @@ package backup import ( - "github.com/bjlag/go-keeper/internal/domain/client" "github.com/google/uuid" + + "github.com/bjlag/go-keeper/internal/domain/client" ) type row struct { diff --git a/test/functional/server/delete_item_test.go b/test/functional/server/delete_item_test.go index 80aacb4..8040a60 100644 --- a/test/functional/server/delete_item_test.go +++ b/test/functional/server/delete_item_test.go @@ -2,11 +2,13 @@ package server_test import ( "context" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "github.com/bjlag/go-keeper/internal/generated/rpc" "github.com/bjlag/go-keeper/test/infrastructure/fixture" _ "github.com/bjlag/go-keeper/test/infrastructure/init" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" ) func (s *TestSuite) TestDeleteItem() { diff --git a/test/functional/server/helper_test.go b/test/functional/server/helper_test.go index b933a72..802c6f3 100644 --- a/test/functional/server/helper_test.go +++ b/test/functional/server/helper_test.go @@ -10,6 +10,7 @@ import ( "github.com/bjlag/go-keeper/test/functional/server" ) +//nolint:unparam func (s *TestSuite) login(ctx context.Context, email, password string) context.Context { out, err := s.client.Login(ctx, &rpc.LoginIn{ Email: email, diff --git a/test/infrastructure/container/container.go b/test/infrastructure/container/container.go index 333fe51..17cc97b 100644 --- a/test/infrastructure/container/container.go +++ b/test/infrastructure/container/container.go @@ -6,7 +6,6 @@ import ( "time" "github.com/docker/go-connections/nat" - "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/wait" ) From 4f25aa9e8d93eb89d7470ccb347897cadc977739 Mon Sep 17 00:00:00 2001 From: Vladislav Duplin Date: Wed, 19 Mar 2025 17:59:09 +0300 Subject: [PATCH 94/94] readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 048e7bd..2a6bf15 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ # go-keeper +Выпускная работа на курсе "Яндекс Практикум: Продвинутый Go-разработчик". + GoKeeper представляет собой клиент-серверную систему, позволяющую пользователю надёжно и безопасно хранить логины, пароли, бинарные данные и прочую приватную информацию.