diff --git a/Makefile b/Makefile
index d80ddb7351ba22863ea00e254d49d61fd409fb04..9a64367046d3ac93fdf4ec05437cfb15ffc43777 100644
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,7 @@
+.PHONY: proto logging mocks .FORCE
+
+SHELL = bash
+
 PROTODIR=perxis-proto/proto
 DSTDIR=./proto
 ALLPROTO?=$(shell find $(PROTODIR) -name '*.proto' )
@@ -6,6 +10,10 @@ PROTOFILES=	$(filter-out $(PROTODIR)/status/status.proto, $(ALLPROTO))
 PROTOGOFILES=$(PROTOFILES:.proto=.pb.go)
 PROTOGOGRPCFILES=$(PROTOFILES:.proto=_grpc.pb.go)
 
+PKGDIR=pkg
+ACCESSLOGGING=$(shell find $(PKGDIR) -name "logging_middleware.go" -type f)
+ERRORLOGGING=$(shell find $(PKGDIR) -name "error_logging_middleware.go" -type f)
+
 # Генерация grpc-клиентов для go
 proto: protoc-check protoc-gen-go-check $(PROTOGOFILES)
 	@echo "Generated all protobuf Go files"
@@ -33,26 +41,19 @@ ifeq (,$(wildcard $(GOPATH)/bin/protoc-gen-go))
 	or visit \"https://github.com/golang/protobuf/tree/v1.3.2#installation\" for more.\n")
 endif
 
-SERVICESDIR=pkg
-SERVICELOGGING=$(shell find $(SERVICESDIR) -name "logging_middleware.go" -type f)
-ERRORLOGGING=$(shell find $(SERVICESDIR) -name "error_logging_middleware.go" -type f)
-
 # Генерация логгирования (access & error) для всех сервисов. Предполагается наличие файлов `logging_middleware.go/error_middleware.go`
 # с директивой go:generate и командой генерации кода в директориях `/pkg` сервисов
 # Для установки инструмента генерации выполнить команду `go get -u github.com/hexdigest/gowrap/cmd/gowrap`
-logging: $(ERRORLOGGING) $(SERVICELOGGING)
+logging: $(ERRORLOGGING) $(ACCESSLOGGING)
 
-%/middleware/logging_middleware.go: % .FORCE
+%/middleware/logging_middleware.go: .FORCE
+	@echo "$@"
 	@go generate "$@"
 
-%/middleware/error_logging_middleware.go: % .FORCE
+%/middleware/error_logging_middleware.go: .FORCE
+	@echo "$@"
 	@go generate "$@"
 
-
-#MICROGENFILES?=$(shell find $(SERVICESDIR) -name "service.go" -exec grep -Ril "microgen" {} \;)
-#SERVICEDIRS?=$(shell find $(SERVICESDIR) -name "service" -type d -exec dirname {} \;)
-#SERVICEFILES?=$(shell find $(SERVICESDIR) -name "service.go" -exec grep -Ril "go:generate" {} \;)
-
 # Генерация моков для всех интерфейсов, найденных в директории. Выходные файлы с моками сохраняются в `./mocks`
 MOCKSDIRS?=$(shell find . -name "service.go" -exec dirname {} \;)
 MOCKS=$(MOCKSDIRS:=/mocks)
diff --git a/go.mod b/go.mod
index 597d3b27af4db4829c5b634cb9dbfacecb74ee80..c4bc532e1b85714da9e5a283f5b60f65748fa1f5 100644
--- a/go.mod
+++ b/go.mod
@@ -17,27 +17,38 @@ require (
 	github.com/stretchr/testify v1.8.0
 	go.mongodb.org/mongo-driver v1.11.4
 	go.uber.org/zap v1.19.1
-	golang.org/x/crypto v0.5.0
-	golang.org/x/net v0.8.0
+	golang.org/x/crypto v0.8.0
+	golang.org/x/net v0.9.0
 	google.golang.org/grpc v1.54.0
 	google.golang.org/protobuf v1.28.1
 	gopkg.in/yaml.v3 v3.0.1
 )
 
 require (
+	github.com/Masterminds/goutils v1.1.1 // indirect
+	github.com/Masterminds/semver/v3 v3.2.1 // indirect
+	github.com/Masterminds/sprig/v3 v3.2.3 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/go-kit/log v0.2.0 // indirect
 	github.com/go-logfmt/logfmt v0.5.1 // indirect
 	github.com/golang/snappy v0.0.1 // indirect
+	github.com/google/uuid v1.3.0 // indirect
 	github.com/gosimple/unidecode v1.0.1 // indirect
 	github.com/hashicorp/errwrap v1.0.0 // indirect
+	github.com/hexdigest/gowrap v1.3.2 // indirect
+	github.com/huandu/xstrings v1.4.0 // indirect
+	github.com/imdario/mergo v0.3.15 // indirect
 	github.com/klauspost/compress v1.13.6 // indirect
+	github.com/mitchellh/copystructure v1.2.0 // indirect
+	github.com/mitchellh/reflectwalk v1.0.2 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
 	github.com/nats-io/nkeys v0.3.0 // indirect
 	github.com/nats-io/nuid v1.0.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/shopspring/decimal v1.3.1 // indirect
+	github.com/spf13/cast v1.5.0 // indirect
 	github.com/stretchr/objx v0.4.0 // indirect
 	github.com/xdg-go/pbkdf2 v1.0.0 // indirect
 	github.com/xdg-go/scram v1.1.1 // indirect
@@ -45,8 +56,10 @@ require (
 	github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
 	go.uber.org/atomic v1.9.0 // indirect
 	go.uber.org/multierr v1.7.0 // indirect
-	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
-	golang.org/x/sys v0.6.0 // indirect
-	golang.org/x/text v0.8.0 // indirect
+	golang.org/x/mod v0.10.0 // indirect
+	golang.org/x/sync v0.1.0 // indirect
+	golang.org/x/sys v0.7.0 // indirect
+	golang.org/x/text v0.9.0 // indirect
+	golang.org/x/tools v0.8.0 // indirect
 	google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
 )
diff --git a/go.sum b/go.sum
index 4860de219faa273cc4d3b494faded8fe08297813..53493f56d5ddbe17ec5350541663552f0dcb311c 100644
--- a/go.sum
+++ b/go.sum
@@ -1,4 +1,11 @@
 github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
+github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
+github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
+github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
+github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
+github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
+github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
+github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
 github.com/antonmedv/expr v1.9.0 h1:j4HI3NHEdgDnN9p6oI6Ndr0G5QryMY0FNxT4ONrFDGU=
 github.com/antonmedv/expr v1.9.0/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmHhwGEk8=
 github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
@@ -26,6 +33,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gosimple/slug v1.13.1 h1:bQ+kpX9Qa6tHRaK+fZR0A0M2Kd7Pa5eHPPsb1JpHD+Q=
 github.com/gosimple/slug v1.13.1/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
 github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
@@ -36,6 +46,14 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
 github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
 github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
 github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
+github.com/hexdigest/gowrap v1.3.2 h1:ZDhDFhrbAHYRdt9ZnULKZyggC/3+W9EpfX6R8DjlggY=
+github.com/hexdigest/gowrap v1.3.2/go.mod h1:g8N2jI4n9AKrf843erksNTrt4sdkG+TGVfhWe8dWrJQ=
+github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
+github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
+github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
+github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
+github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM=
+github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
@@ -55,8 +73,14 @@ github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i
 github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
+github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
+github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
+github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
 github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo=
 github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
+github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
+github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -83,10 +107,17 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
 github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
 github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
 github.com/sanity-io/litter v1.2.0/go.mod h1:JF6pZUFgu2Q0sBZ+HSV35P8TVPI1TTzEwyu9FXAw2W4=
+github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
+github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
+github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -105,6 +136,7 @@ github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgk
 github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
 github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 go.mongodb.org/mongo-driver v1.11.4 h1:4ayjakA013OdpGyL2K3ZqylTac/rMjrJOMZ1EHizXas=
 go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
@@ -120,22 +152,36 @@ go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
 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-20210314154223-e6e6c4f2bb5b/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-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
 golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
 golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
+golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
+golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
 golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
+golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
 golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
 golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
+golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
+golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 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-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -145,21 +191,34 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
+golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
 golang.org/x/text v0.3.0/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.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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
 golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
+golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
 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=
@@ -178,6 +237,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/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.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/pkg/clients/middleware/error_logging_middleware.go b/pkg/clients/middleware/error_logging_middleware.go
index 3e60cc1d8e49f0712362a7e3e8f072571333a97a..0b96827a0c620ad1ca1aa5aaf6b93a821af6279d 100644
--- a/pkg/clients/middleware/error_logging_middleware.go
+++ b/pkg/clients/middleware/error_logging_middleware.go
@@ -1,9 +1,9 @@
+package middleware
+
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../assets/templates/middleware/error_log
 // gowrap: http://github.com/hexdigest/gowrap
 
-package middleware
-
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/clients -i Clients -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
 
 import (
diff --git a/pkg/clients/middleware/logging_middleware.go b/pkg/clients/middleware/logging_middleware.go
index 1bf9b763e40232dd7b5ef87a1e049394e152e021..ef3ea5947a637bfa9caffa3af64f1f7cd2b8e019 100644
--- a/pkg/clients/middleware/logging_middleware.go
+++ b/pkg/clients/middleware/logging_middleware.go
@@ -1,9 +1,9 @@
+package middleware
+
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../assets/templates/middleware/access_log
 // gowrap: http://github.com/hexdigest/gowrap
 
-package middleware
-
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/clients -i Clients -t ../../../assets/templates/middleware/access_log -o logging_middleware.go -l ""
 
 import (
diff --git a/pkg/collaborators/middleware/error_logging_middleware.go b/pkg/collaborators/middleware/error_logging_middleware.go
index 304552a2fb8b9e0181ace20d76068f2d256d8848..a45dfd8053e1718fdc139f1792a9f0b1547c08d1 100644
--- a/pkg/collaborators/middleware/error_logging_middleware.go
+++ b/pkg/collaborators/middleware/error_logging_middleware.go
@@ -1,9 +1,9 @@
+package service
+
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../assets/templates/middleware/error_log
 // gowrap: http://github.com/hexdigest/gowrap
 
-package service
-
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/collaborators -i Collaborators -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
 
 import (
diff --git a/pkg/collaborators/middleware/logging_middleware.go b/pkg/collaborators/middleware/logging_middleware.go
index f024149569452d3902ccb8f512487b22da59b069..41f541e6cc0e09f2fc6240d585159bda09c10a74 100644
--- a/pkg/collaborators/middleware/logging_middleware.go
+++ b/pkg/collaborators/middleware/logging_middleware.go
@@ -1,9 +1,9 @@
+package service
+
 // Code generated by gowrap. DO NOT EDIT.
-// template: ../../../../assets/templates/middleware/access_log
+// template: ../../../assets/templates/middleware/access_log
 // gowrap: http://github.com/hexdigest/gowrap
 
-package service
-
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/collaborators -i Collaborators -t ../../../assets/templates/middleware/access_log -o logging_middleware.go -l ""
 
 import (
diff --git a/pkg/collections/middleware/error_logging_middleware.go b/pkg/collections/middleware/error_logging_middleware.go
index cdee51d65d5c7b72d9132f1d29e08f32181de846..d1be7f66c8564cc162d93a926652c63d0a670e1d 100644
--- a/pkg/collections/middleware/error_logging_middleware.go
+++ b/pkg/collections/middleware/error_logging_middleware.go
@@ -1,9 +1,9 @@
+package service
+
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../assets/templates/middleware/error_log
 // gowrap: http://github.com/hexdigest/gowrap
 
-package service
-
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/collections -i Collections -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
 
 import (
diff --git a/pkg/collections/middleware/logging_middleware.go b/pkg/collections/middleware/logging_middleware.go
index 53fcc84690189585c24d302e4410b69c670799bb..dd43cb9ffd3e662241a1bb08e7174cfd1c5677df 100644
--- a/pkg/collections/middleware/logging_middleware.go
+++ b/pkg/collections/middleware/logging_middleware.go
@@ -1,9 +1,9 @@
+package service
+
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../assets/templates/middleware/access_log
 // gowrap: http://github.com/hexdigest/gowrap
 
-package service
-
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/collections -i Collections -t ../../../assets/templates/middleware/access_log -o logging_middleware.go -l ""
 
 import (
diff --git a/pkg/environments/middleware/error_logging_middleware.go b/pkg/environments/middleware/error_logging_middleware.go
index b365f36d591257fe74ad49f8350755b8f6da9815..6d6cb544f129f2925d33be91f6666dcf8ebb8936 100644
--- a/pkg/environments/middleware/error_logging_middleware.go
+++ b/pkg/environments/middleware/error_logging_middleware.go
@@ -1,9 +1,9 @@
+package service
+
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../assets/templates/middleware/error_log
 // gowrap: http://github.com/hexdigest/gowrap
 
-package service
-
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/environments -i Environments -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
 
 import (
diff --git a/pkg/environments/middleware/logging_middleware.go b/pkg/environments/middleware/logging_middleware.go
index 74a2efbf83a466387219ad4fba8e0c0241d71735..26f4eb279126e6a008db8998b83012c8a316ca2f 100644
--- a/pkg/environments/middleware/logging_middleware.go
+++ b/pkg/environments/middleware/logging_middleware.go
@@ -1,9 +1,9 @@
+package service
+
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../assets/templates/middleware/access_log
 // gowrap: http://github.com/hexdigest/gowrap
 
-package service
-
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/environments -i Environments -t ../../../assets/templates/middleware/access_log -o logging_middleware.go -l ""
 
 import (
diff --git a/pkg/invitations/middleware/error_logging_middleware.go b/pkg/invitations/middleware/error_logging_middleware.go
index b83d054478cafa8a5dbf3ab823adf0715dae90bc..39c823ae6fdf7fb7cb5c181c42099ca16c1a2bad 100644
--- a/pkg/invitations/middleware/error_logging_middleware.go
+++ b/pkg/invitations/middleware/error_logging_middleware.go
@@ -1,9 +1,9 @@
+package service
+
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../assets/templates/middleware/error_log
 // gowrap: http://github.com/hexdigest/gowrap
 
-package service
-
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/invitations -i Invitations -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
 
 import (
diff --git a/pkg/invitations/middleware/logging_middleware.go b/pkg/invitations/middleware/logging_middleware.go
index d0e4d9c4c4d9e51c2d219112bcfce644475e7d88..8f1ceb9959497794cc4bc7a2d6a963a949d9b1a3 100644
--- a/pkg/invitations/middleware/logging_middleware.go
+++ b/pkg/invitations/middleware/logging_middleware.go
@@ -1,9 +1,9 @@
+package service
+
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../assets/templates/middleware/access_log
 // gowrap: http://github.com/hexdigest/gowrap
 
-package service
-
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/invitations -i Invitations -t ../../../assets/templates/middleware/access_log -o logging_middleware.go -l ""
 
 import (
diff --git a/pkg/items/middleware/error_logging_middleware.go b/pkg/items/middleware/error_logging_middleware.go
index 553461112586322dfddf38ccdddf31d373a4b29a..97967808d150cf951f3fb22b16b2836765fc611f 100644
--- a/pkg/items/middleware/error_logging_middleware.go
+++ b/pkg/items/middleware/error_logging_middleware.go
@@ -1,9 +1,9 @@
+package service
+
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../assets/templates/middleware/error_log
 // gowrap: http://github.com/hexdigest/gowrap
 
-package service
-
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/items -i Items -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
 
 import (
diff --git a/pkg/items/middleware/logging_middleware.go b/pkg/items/middleware/logging_middleware.go
index ed47170e07c15f0b3d2189aa64b2a404259a4156..102b91874c63655c169e5c101b10490e375833b9 100644
--- a/pkg/items/middleware/logging_middleware.go
+++ b/pkg/items/middleware/logging_middleware.go
@@ -1,9 +1,9 @@
+package service
+
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../assets/templates/middleware/access_log
 // gowrap: http://github.com/hexdigest/gowrap
 
-package service
-
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/items -i Items -t ../../../assets/templates/middleware/access_log -o logging_middleware.go -l ""
 
 import (
diff --git a/pkg/items/pagination.go b/pkg/items/pagination.go
new file mode 100644
index 0000000000000000000000000000000000000000..7f990dc6c6e8d0684553e4039b86580236cc2ef0
--- /dev/null
+++ b/pkg/items/pagination.go
@@ -0,0 +1,137 @@
+package items
+
+import (
+	"context"
+
+	"git.perx.ru/perxis/perxis-go/pkg/content"
+	"git.perx.ru/perxis/perxis-go/pkg/data"
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+	"git.perx.ru/perxis/perxis-go/pkg/options"
+	"google.golang.org/grpc/codes"
+)
+
+type BatchProcessor struct {
+	Content                      *content.Content
+	SpaceID, EnvID, CollectionID string
+	FindOptions                  *FindOptions
+	FindPublishedOptions         *FindPublishedOptions
+	Filter                       *Filter
+
+	pageSize, pageNum int
+	sort              []string
+	processed         int
+}
+
+func (b *BatchProcessor) getBatch(ctx context.Context) ([]*Item, bool, error) {
+	var res []*Item
+	var err error
+	var total int
+
+	if b.FindPublishedOptions != nil {
+		res, total, err = b.Content.Items.FindPublished(
+			ctx,
+			b.SpaceID,
+			b.EnvID,
+			b.CollectionID,
+			b.Filter,
+			&FindPublishedOptions{
+				Regular:     b.FindPublishedOptions.Regular,
+				Hidden:      b.FindPublishedOptions.Hidden,
+				Templates:   b.FindPublishedOptions.Templates,
+				FindOptions: *options.NewFindOptions(b.pageNum, b.pageSize, b.sort...),
+			},
+		)
+	} else {
+		res, total, err = b.Content.Items.Find(
+			ctx,
+			b.SpaceID,
+			b.EnvID,
+			b.CollectionID,
+			b.Filter,
+			&FindOptions{
+				Deleted:     b.FindOptions.Deleted,
+				Regular:     b.FindOptions.Regular,
+				Hidden:      b.FindOptions.Hidden,
+				Templates:   b.FindOptions.Templates,
+				FindOptions: *options.NewFindOptions(b.pageNum, b.pageSize, b.sort...),
+			},
+		)
+	}
+
+	if err == nil {
+		b.processed += len(res)
+		b.pageNum++
+	}
+
+	return res, b.processed != total, err
+}
+
+func (b *BatchProcessor) next(ctx context.Context) (res []*Item, next bool, err error) {
+
+	for {
+		res, next, err = b.getBatch(ctx)
+		if err != nil {
+			if errors.GetStatusCode(err) == codes.ResourceExhausted && b.reducePageSize() {
+				continue
+			}
+
+			return nil, false, err
+		}
+
+		break
+	}
+
+	return res, next, nil
+}
+
+func (b *BatchProcessor) reducePageSize() bool {
+	if b.pageSize == 1 {
+		return false
+	}
+
+	b.pageNum = 2 * b.pageNum
+	b.pageSize = b.pageSize / 2
+
+	return true
+}
+
+func (b *BatchProcessor) Do(ctx context.Context, f func(batch []*Item) error) (int, error) {
+
+	if b.FindOptions == nil && b.FindPublishedOptions == nil {
+		b.FindOptions = new(FindOptions)
+	}
+	if b.FindOptions != nil {
+		b.pageSize = b.FindOptions.PageSize
+		b.sort = b.FindOptions.Sort
+	}
+	if b.FindPublishedOptions != nil {
+		b.pageSize = b.FindPublishedOptions.PageSize
+		b.sort = b.FindPublishedOptions.Sort
+	}
+
+	if b.pageSize == 0 {
+		b.pageSize = 128
+	}
+
+	if b.Filter != nil && (len(b.Filter.ID) > 0 || len(b.Filter.Q) > 0) && !data.Contains("_id", b.sort) {
+		b.sort = append(b.sort, "_id")
+	}
+
+	var err error
+
+	next := true
+	for next {
+
+		var batch []*Item
+
+		batch, next, err = b.next(ctx)
+		if err != nil {
+			return 0, err
+		}
+
+		if err = f(batch); err != nil {
+			return 0, err
+		}
+	}
+	return b.processed, nil
+}
diff --git a/pkg/locales/middleware/error_logging_middleware.go b/pkg/locales/middleware/error_logging_middleware.go
index 171482568af8d1988bb1e2e6625884e78683c71e..695c91128d6f093d93f022468d464bedbd571e04 100644
--- a/pkg/locales/middleware/error_logging_middleware.go
+++ b/pkg/locales/middleware/error_logging_middleware.go
@@ -1,9 +1,9 @@
+package service
+
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../assets/templates/middleware/error_log
 // gowrap: http://github.com/hexdigest/gowrap
 
-package service
-
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/locales -i Locales -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
 
 import (
diff --git a/pkg/locales/middleware/logging_middleware.go b/pkg/locales/middleware/logging_middleware.go
index 7e9a16b14ba1b2c619271d746245de4f1ddbcaae..fb98d25759e87d199e8c65a204f30b3acb48c1f4 100644
--- a/pkg/locales/middleware/logging_middleware.go
+++ b/pkg/locales/middleware/logging_middleware.go
@@ -1,9 +1,9 @@
+package service
+
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../assets/templates/middleware/access_log
 // gowrap: http://github.com/hexdigest/gowrap
 
-package service
-
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/locales -i Locales -t ../../../assets/templates/middleware/access_log -o logging_middleware.go -l ""
 
 import (
diff --git a/pkg/members/middleware/error_logging_middleware.go b/pkg/members/middleware/error_logging_middleware.go
index 5eb4e3b3ff6ba6417fdb96310916cca0345a055f..08d2814bf4fc8f16f0df57462770342a93e191c8 100644
--- a/pkg/members/middleware/error_logging_middleware.go
+++ b/pkg/members/middleware/error_logging_middleware.go
@@ -1,9 +1,9 @@
+package service
+
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../assets/templates/middleware/error_log
 // gowrap: http://github.com/hexdigest/gowrap
 
-package service
-
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/members -i Members -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
 
 import (
diff --git a/pkg/members/middleware/logging_middleware.go b/pkg/members/middleware/logging_middleware.go
index 3b1687039af00f3747cf75b52bd0e18bc4512f1e..1b9ead6d06d694945c89638c52d4bce07e4ee938 100644
--- a/pkg/members/middleware/logging_middleware.go
+++ b/pkg/members/middleware/logging_middleware.go
@@ -1,9 +1,9 @@
+package service
+
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../assets/templates/middleware/access_log
 // gowrap: http://github.com/hexdigest/gowrap
 
-package service
-
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/members -i Members -t ../../../assets/templates/middleware/access_log -o logging_middleware.go -l ""
 
 import (
diff --git a/pkg/organizations/middleware/error_logging_middleware.go b/pkg/organizations/middleware/error_logging_middleware.go
index 5c2612fced88cb38fdf9bb56104b75bcd12f2db6..c9631f9144db6244044379219250ca334893d0d6 100644
--- a/pkg/organizations/middleware/error_logging_middleware.go
+++ b/pkg/organizations/middleware/error_logging_middleware.go
@@ -1,9 +1,9 @@
+package service
+
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../assets/templates/middleware/error_log
 // gowrap: http://github.com/hexdigest/gowrap
 
-package service
-
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/organizations -i Organizations -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
 
 import (
diff --git a/pkg/organizations/middleware/logging_middleware.go b/pkg/organizations/middleware/logging_middleware.go
index ac446d73ef04a3f82191b686bf4b11354caaa3b0..6f33296d5cecbee19be4bff65da4280486cd1958 100644
--- a/pkg/organizations/middleware/logging_middleware.go
+++ b/pkg/organizations/middleware/logging_middleware.go
@@ -1,9 +1,9 @@
+package service
+
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../assets/templates/middleware/access_log
 // gowrap: http://github.com/hexdigest/gowrap
 
-package service
-
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/organizations -i Organizations -t ../../../assets/templates/middleware/access_log -o logging_middleware.go -l ""
 
 import (
diff --git a/pkg/references/middleware/error_logging_middleware.go b/pkg/references/middleware/error_logging_middleware.go
index e68bade9e992ead9699df8d0059d5d529167a480..9a62947f240842c6afb099a9b03cc966d9bd99b5 100644
--- a/pkg/references/middleware/error_logging_middleware.go
+++ b/pkg/references/middleware/error_logging_middleware.go
@@ -1,9 +1,9 @@
+package service
+
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../assets/templates/middleware/error_log
 // gowrap: http://github.com/hexdigest/gowrap
 
-package service
-
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/references -i References -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
 
 import (
diff --git a/pkg/references/middleware/logging_middleware.go b/pkg/references/middleware/logging_middleware.go
index 692e298dc4e9ace0be13a0a6bc639e2aff4bf9c3..62a0e9d5cf26c6ab0d131fff2fd443cffd8fb8f3 100644
--- a/pkg/references/middleware/logging_middleware.go
+++ b/pkg/references/middleware/logging_middleware.go
@@ -1,9 +1,9 @@
+package service
+
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../assets/templates/middleware/access_log
 // gowrap: http://github.com/hexdigest/gowrap
 
-package service
-
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/references -i References -t ../../../assets/templates/middleware/access_log -o logging_middleware.go -l ""
 
 import (
diff --git a/pkg/roles/middleware/error_logging_middleware.go b/pkg/roles/middleware/error_logging_middleware.go
index a6ff0af444ed4154354d0d732f2b5edae612bcdc..7afe8f1f1003031ddedb437d0422dfb4123d4526 100644
--- a/pkg/roles/middleware/error_logging_middleware.go
+++ b/pkg/roles/middleware/error_logging_middleware.go
@@ -1,9 +1,9 @@
+package service
+
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../assets/templates/middleware/error_log
 // gowrap: http://github.com/hexdigest/gowrap
 
-package service
-
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/roles -i Roles -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
 
 import (
diff --git a/pkg/roles/middleware/logging_middleware.go b/pkg/roles/middleware/logging_middleware.go
index 764a0136fc2bc72d040d3cb429ccc40103678190..ab536b6ad9e168adc79691a3127be8a47f9e5ba7 100644
--- a/pkg/roles/middleware/logging_middleware.go
+++ b/pkg/roles/middleware/logging_middleware.go
@@ -1,9 +1,9 @@
+package service
+
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../assets/templates/middleware/access_log
 // gowrap: http://github.com/hexdigest/gowrap
 
-package service
-
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/roles -i Roles -t ../../../assets/templates/middleware/access_log -o logging_middleware.go -l ""
 
 import (
diff --git a/pkg/schemaloader/context.go b/pkg/schemaloader/context.go
new file mode 100644
index 0000000000000000000000000000000000000000..7407b6b30939981f917c50d734ac770ec69b9e4e
--- /dev/null
+++ b/pkg/schemaloader/context.go
@@ -0,0 +1,30 @@
+package schemaloader
+
+import "context"
+
+type LoaderContext struct {
+	SpaceID string
+	EnvID   string
+}
+
+type loaderCtxKey struct{}
+
+func WithContext(ctx context.Context, loaderContext *LoaderContext) context.Context {
+	if ctx == nil {
+		ctx = context.Background()
+	}
+	p, _ := ctx.Value(loaderCtxKey{}).(*LoaderContext)
+	if p != nil {
+		*p = *loaderContext
+		return ctx
+	}
+	return context.WithValue(ctx, loaderCtxKey{}, loaderContext)
+}
+
+func GetContext(ctx context.Context) *LoaderContext {
+	p, _ := ctx.Value(loaderCtxKey{}).(*LoaderContext)
+	if p == nil {
+		return new(LoaderContext)
+	}
+	return p
+}
diff --git a/pkg/schemaloader/loader.go b/pkg/schemaloader/loader.go
new file mode 100644
index 0000000000000000000000000000000000000000..e27baf4ea8b509adff88f7b9c5e99a48f15960b7
--- /dev/null
+++ b/pkg/schemaloader/loader.go
@@ -0,0 +1,92 @@
+package schemaloader
+
+import (
+	"context"
+	"net/url"
+	"strings"
+
+	"git.perx.ru/perxis/perxis-go/pkg/collections"
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+	"git.perx.ru/perxis/perxis-go/pkg/schema/field"
+)
+
+// NewLoader возвращает новый загрузчик схем из коллекций
+// используется только на сервере
+// на клиенте нужно использовать методы получения полностью загруженных схем, для которых разрешение происходит на сервере
+func NewLoader(svc collections.Collections) field.Loader {
+	return &loader{svc: svc}
+}
+
+type loader struct {
+	svc collections.Collections
+}
+
+// Load - возвращает поля по референсу из коллекций (не загруженные)
+func (l *loader) Load(ctx context.Context, ref string) ([]*field.Field, error) {
+	spaceID, envID, colID, err := parseRef(ctx, ref)
+	if err != nil {
+		return nil, err
+	}
+
+	filter := &collections.Filter{ID: []string{colID}}
+
+	collections, err := l.svc.List(ctx, spaceID, envID, filter)
+	if err != nil {
+		return nil, errors.Wrapf(err, "schemaloader: failed to get collections for \"%s\"", ref)
+	}
+
+	var schemas []*field.Field
+	for _, s := range collections {
+		if s.Schema != nil {
+			schemas = append(schemas, &s.Schema.Field)
+		}
+	}
+
+	if len(schemas) == 0 {
+		return nil, errors.Errorf("schema not found \"%s\"", ref)
+	}
+
+	return schemas, nil
+}
+
+func parseRef(ctx context.Context, ref string) (spaceID, envID, colID string, err error) {
+	var u *url.URL
+	if u, err = url.Parse(ref); err != nil {
+		return
+	}
+
+	parts := strings.SplitN(u.Path, "/", 3)
+
+	switch len(parts) {
+	case 1:
+		colID = parts[0]
+	case 2:
+		spaceID = parts[0]
+		envID = "master"
+		colID = parts[1]
+	case 3:
+		spaceID = parts[0]
+		envID = parts[1]
+		colID = parts[2]
+	}
+
+	if colID == "" {
+		err = errors.Errorf("invalid schema reference \"%s\"", ref)
+	}
+
+	if loaderCtx := GetContext(ctx); loaderCtx != nil {
+		if spaceID == "" {
+			spaceID = loaderCtx.SpaceID
+		}
+
+		if envID == "" {
+			envID = loaderCtx.EnvID
+		}
+	}
+
+	if spaceID == "" {
+		err = errors.Errorf("can't identify space for reference \"%s\"", ref)
+	}
+
+	return
+}
diff --git a/pkg/schemaloader/loader_test.go b/pkg/schemaloader/loader_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..f796fd8f669e31140e4171955c4d7db8723cec26
--- /dev/null
+++ b/pkg/schemaloader/loader_test.go
@@ -0,0 +1,141 @@
+package schemaloader
+
+import (
+	"context"
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+//func Test_Load(t *testing.T) {
+//
+//	const (
+//		spaceID = "SpaceID"
+//		envID   = "envID"
+//		colID   = "colID"
+//		uri     = "/colID#fieldID"
+//	)
+//
+//	t.Run("Load schema (success)", func(t *testing.T) {
+//		collSvs := &mocks.Collections{}
+//
+//		sch := schema.New(
+//			"first_name", field.String(),
+//			"last_name", field.String(),
+//		)
+//
+//		cl := &collections.Collection{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "Collection", Schema: sch}
+//		collSvs.On("List", mock.Anything, spaceID, envID, mock.AnythingOfType("*collections.Filter")).Run(func(args mock.Arguments) {
+//			filter := args[3].(*collections.Filter)
+//
+//			assert.Equal(t, &collections.Filter{ID: []string{"colID"}}, filter, "Фильтр должен содержать идентификатор коллекции")
+//		}).Return([]*collections.Collection{cl}, nil).Once()
+//
+//		loader := NewLoader(collSvs, spaceID, envID)
+//		schemas, err := loader.Load(nil, uri)
+//
+//		require.NoError(t, err, "Ожидается успешное завершение")
+//		require.Equal(t, []*field.Field{&sch.Field}, schemas, "Метод должен возвращать срез схем")
+//		collSvs.AssertExpectations(t)
+//	})
+//
+//	t.Run("Collection doesn't have schema", func(t *testing.T) {
+//		collSvs := &mocks.Collections{}
+//
+//		cl := &collections.Collection{ID: colID, SpaceID: spaceID, EnvID: envID, Name: "Collection"}
+//		collSvs.On("List", mock.Anything, spaceID, envID, mock.AnythingOfType("*collections.Filter")).Run(func(args mock.Arguments) {
+//			filter := args[3].(*collections.Filter)
+//
+//			assert.Equal(t, &collections.Filter{ID: []string{"colID"}}, filter, "Фильтр должен содержать идентификатор коллекции")
+//		}).Return([]*collections.Collection{cl}, nil).Once()
+//
+//		loader := NewLoader(collSvs, spaceID, envID)
+//		schemas, err := loader.Load(nil, uri)
+//
+//		require.Error(t, err, "Ожидается ошибка")
+//		require.Contains(t, err.Error(), "schema not found")
+//		require.Nil(t, schemas, "Метод должен вернуть nil")
+//		//assert.Nil(t, schemas, "Метод должен вернуть nil")
+//		collSvs.AssertExpectations(t)
+//	})
+//
+//	t.Run("Loader not found collection", func(t *testing.T) {
+//		collSvs := &mocks.Collections{}
+//
+//		collSvs.On("List", mock.Anything, spaceID, envID, mock.AnythingOfType("*collections.Filter")).Run(func(args mock.Arguments) {
+//			filter := args[3].(*collections.Filter)
+//
+//			assert.Equal(t, &collections.Filter{ID: []string{"colID"}}, filter, "Фильтр должен содержать идентификатор коллекции")
+//		}).Return([]*collections.Collection{}, nil).Once()
+//
+//		loader := NewLoader(collSvs, spaceID, envID)
+//		schemas, err := loader.Load(nil, uri)
+//
+//		require.Error(t, err, "Ожидается ошибка")
+//		require.Contains(t, err.Error(), "schema not found")
+//		require.Nil(t, schemas, "Метод должен вернуть nil")
+//		collSvs.AssertExpectations(t)
+//	})
+//
+//	t.Run("Collection service return error", func(t *testing.T) {
+//		collSvs := &mocks.Collections{}
+//
+//		collSvs.On("List", mock.Anything, spaceID, envID, mock.AnythingOfType("*collections.Filter")).Run(func(args mock.Arguments) {
+//			filter := args[3].(*collections.Filter)
+//
+//			assert.Equal(t, &collections.Filter{ID: []string{"colID"}}, filter, "Фильтр должен содержать идентификатор коллекции")
+//		}).Return(nil, errors.New("storage error")).Once()
+//
+//		loader := NewLoader(collSvs, spaceID, envID)
+//		schemas, err := loader.Load(nil, uri)
+//
+//		require.Error(t, err, "Ожидается ошибка")
+//		require.Contains(t, err.Error(), "failed to get schema")
+//		require.Nil(t, schemas, "Метод должен вернуть nil")
+//		collSvs.AssertExpectations(t)
+//	})
+//
+//	t.Run("ParseMask return error", func(t *testing.T) {
+//		collSvs := &mocks.Collections{}
+//
+//		loader := NewLoader(collSvs, spaceID, envID)
+//		schemas, err := loader.Load(nil, "")
+//
+//		require.Error(t, err, "Ожидается ошибка")
+//		require.Contains(t, err.Error(), "invalid schema reference")
+//		require.Nil(t, schemas, "Метод должен вернуть nil")
+//		collSvs.AssertExpectations(t)
+//	})
+//}
+
+func Test_parseRef(t *testing.T) {
+	ctx := WithContext(nil, &LoaderContext{SpaceID: "spc", EnvID: "env"})
+	tests := []struct {
+		ref            string
+		ctx            context.Context
+		wantSpaceID    string
+		wantEnvId      string
+		wantCollection string
+		wantErr        assert.ErrorAssertionFunc
+	}{
+		{"col", ctx, "spc", "env", "col", assert.NoError},
+		{"/col", ctx, "spc", "master", "col", assert.NoError},
+		{"spc1/env1/col", ctx, "spc1", "env1", "col", assert.NoError},
+		{"spc1/env1/col#fld", ctx, "spc1", "env1", "col", assert.NoError},
+		{"col%3f*", ctx, "spc", "env", "col?*", assert.NoError},
+		{"#fld", ctx, "spc", "env", "", assert.Error},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.ref, func(t *testing.T) {
+			gotSpaceID, gotEnvId, gotCollection, err := parseRef(tt.ctx, tt.ref)
+			if !tt.wantErr(t, err, fmt.Sprintf("parseRef(%v)", tt.ref)) {
+				return
+			}
+			assert.Equalf(t, tt.wantSpaceID, gotSpaceID, "parseRef(%v)", tt.ref)
+			assert.Equalf(t, tt.wantEnvId, gotEnvId, "parseRef(%v)", tt.ref)
+			assert.Equalf(t, tt.wantCollection, gotCollection, "parseRef(%v)", tt.ref)
+		})
+	}
+}
diff --git a/pkg/spaces/middleware/error_logging_middleware.go b/pkg/spaces/middleware/error_logging_middleware.go
index d41b6678ecba526842c64aa519742eedab7e7779..85b629ba32abe38e5030077e8fe81b04206650b0 100644
--- a/pkg/spaces/middleware/error_logging_middleware.go
+++ b/pkg/spaces/middleware/error_logging_middleware.go
@@ -1,9 +1,9 @@
+package service
+
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../assets/templates/middleware/error_log
 // gowrap: http://github.com/hexdigest/gowrap
 
-package service
-
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/spaces -i Spaces -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
 
 import (
diff --git a/pkg/spaces/middleware/logging_middleware.go b/pkg/spaces/middleware/logging_middleware.go
index c2b62dbbcdd111a19538b00b5c22ceeb52896dca..1471ea19535139210e79968e5a4522001a0b2ae3 100644
--- a/pkg/spaces/middleware/logging_middleware.go
+++ b/pkg/spaces/middleware/logging_middleware.go
@@ -1,9 +1,9 @@
+package service
+
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../assets/templates/middleware/access_log
 // gowrap: http://github.com/hexdigest/gowrap
 
-package service
-
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/spaces -i Spaces -t ../../../assets/templates/middleware/access_log -o logging_middleware.go -l ""
 
 import (
diff --git a/pkg/template/builder.go b/pkg/template/builder.go
new file mode 100644
index 0000000000000000000000000000000000000000..9a4ddcdb73e6b5c585d77088630096351d19b2bd
--- /dev/null
+++ b/pkg/template/builder.go
@@ -0,0 +1,171 @@
+package template
+
+import (
+	"bytes"
+	"context"
+	"text/template"
+
+	"git.perx.ru/perxis/perxis-go/pkg/content"
+)
+
+type Builder struct {
+	ctx     context.Context
+	cnt     *content.Content
+	SpaceID string
+	EnvID   string
+	funcs   template.FuncMap
+	data    map[string]interface{}
+}
+
+func NewBuilder(cnt *content.Content, space, env string) *Builder {
+	return &Builder{
+		ctx:     context.Background(),
+		cnt:     cnt,
+		SpaceID: space,
+		EnvID:   env,
+		funcs:   make(template.FuncMap),
+	}
+}
+
+func (b *Builder) getFuncs() template.FuncMap {
+	return template.FuncMap{
+		"lookup": getLookup(b),
+		"system": getSystem(b),
+	}
+}
+
+func (b *Builder) WithData(data map[string]interface{}) *Builder {
+	bld := *b
+	bld.data = data
+	return &bld
+}
+
+func (b *Builder) WithKV(kv ...any) *Builder {
+	bld := *b
+	if bld.data == nil {
+		bld.data = make(map[string]interface{}, 10)
+	}
+	for i := 0; i < len(kv)-1; i += 2 {
+		k, _ := kv[i].(string)
+		v := kv[i+1]
+		if k != "" && v != nil {
+			bld.data[k] = v
+		}
+	}
+	return &bld
+}
+
+func (b *Builder) GetData() map[string]interface{} {
+	return b.data
+}
+
+func (b *Builder) WithSpace(space, env string) *Builder {
+	bld := *b
+	bld.SpaceID = space
+	bld.EnvID = env
+	return &bld
+}
+
+func (b *Builder) WithContext(ctx context.Context) *Builder {
+	bld := *b
+	bld.ctx = ctx
+	return &bld
+}
+
+func (b *Builder) Context() context.Context {
+	return b.ctx
+}
+
+func (b *Builder) Template() *template.Template {
+	return template.New("main").Funcs(b.getFuncs())
+}
+
+func (b *Builder) Execute(str string, data ...any) (string, error) {
+	t := b.Template()
+	buf := new(bytes.Buffer)
+	t, err := t.Parse(str)
+	if err != nil {
+		return "", err
+	}
+	if err = t.Execute(buf, b.getData(data...)); err != nil {
+		return "", err
+	}
+	return buf.String(), nil
+}
+
+func (b *Builder) ExecuteList(str []string, data ...any) ([]string, error) {
+	t := b.Template()
+	result := make([]string, len(str))
+	buffer := new(bytes.Buffer)
+	for i, tmpl := range str {
+		if tmpl == "" {
+			continue
+		}
+		t, err := t.Parse(tmpl)
+		if err != nil {
+			return []string{}, err
+		}
+		if err = t.Execute(buffer, b.getData(data...)); err != nil {
+			return []string{}, err
+		}
+		result[i] = buffer.String()
+		buffer.Reset()
+	}
+	return result, nil
+}
+
+func (b *Builder) ExecuteMap(str map[string]interface{}, data ...any) (map[string]interface{}, error) {
+	result := make(map[string]interface{}, len(str))
+	for k, v := range str {
+		switch t := v.(type) {
+		case string:
+			value, err := b.Execute(t, data...)
+			if err != nil {
+				return nil, err
+			}
+			v = value
+		case []string:
+			values, err := b.ExecuteList(append([]string{k}, t...), data...)
+			if err != nil {
+				return nil, err
+			}
+			k = values[0]
+			vv := make([]interface{}, 0, len(t))
+			for _, val := range values[1:] {
+				vv = append(vv, val)
+			}
+			v = vv
+		}
+
+		result[k] = v
+	}
+	return result, nil
+}
+
+func (b *Builder) getData(data ...any) any {
+	if len(data) == 0 {
+		return b.data
+	}
+
+	var res map[string]interface{}
+	for _, v := range data {
+		if m, ok := v.(map[string]interface{}); ok && b.data != nil {
+			res = mergeMaps(b.data, m)
+		}
+	}
+	if res != nil {
+		return res
+	}
+
+	return data[0]
+}
+
+func mergeMaps(in ...map[string]interface{}) map[string]interface{} {
+	out := make(map[string]interface{})
+	for _, i := range in {
+		for k, v := range i {
+			out[k] = v
+		}
+	}
+	return out
+}
diff --git a/pkg/template/builder_test.go b/pkg/template/builder_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..f8e2b34440dd73259a6ccd3ce9202a556caa5298
--- /dev/null
+++ b/pkg/template/builder_test.go
@@ -0,0 +1,272 @@
+package template
+
+import (
+	"context"
+	"errors"
+	"testing"
+	"text/template"
+
+	"git.perx.ru/perxis/perxis-go/pkg/content"
+	"git.perx.ru/perxis/perxis-go/pkg/items"
+	mocksitems "git.perx.ru/perxis/perxis-go/pkg/items/mocks"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestBuilder_Execute(t *testing.T) {
+	tests := []struct {
+		name    string
+		ctx     context.Context
+		cnt     *content.Content
+		SpaceID string
+		EnvID   string
+		funcs   template.FuncMap
+		str     string
+		data    any
+		want    any
+		wantErr bool
+
+		itemsCall func(itemsSvc *mocksitems.Items)
+	}{
+		{name: "error", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: "hello {{ .a }}", data: "world", want: "", wantErr: true},
+		{name: "empty", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: "", data: "", want: "", wantErr: false},
+		{name: "#1", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: "hello {{ . }}", data: "world", want: "hello world", wantErr: false},
+		{name: "#2", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: "{{ . }}", data: "world", want: "world", wantErr: false},
+		{name: "#3 ", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: "", data: "world", want: "", wantErr: false},
+		{name: "#4 ", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: "hello", data: "world", want: "hello", wantErr: false},
+		{name: "lookup", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: "hello, {{ lookup \"secrets.dev.key\" }}", data: "", want: "hello, Luk", wantErr: false, itemsCall: func(itemsSvc *mocksitems.Items) {
+			itemsSvc.On("Get", context.Background(), "space", "env", "secrets", "dev").Return(&items.Item{
+				ID:           "dev",
+				SpaceID:      "space",
+				EnvID:        "env",
+				CollectionID: "secrets",
+				Data: map[string]interface{}{
+					"id":  "dev",
+					"key": "Luk",
+				},
+			}, nil).Once()
+		}},
+		{name: "lookup with slice", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: "numbers {{ lookup \"secrets.dev.slice\" }}", data: "", want: "numbers [1 2 3]", wantErr: false, itemsCall: func(itemsSvc *mocksitems.Items) {
+			itemsSvc.On("Get", context.Background(), "space", "env", "secrets", "dev").Return(&items.Item{
+				ID:           "dev",
+				SpaceID:      "space",
+				EnvID:        "env",
+				CollectionID: "secrets",
+				Data: map[string]interface{}{
+					"id":    "dev",
+					"slice": []int{1, 2, 3},
+				},
+			}, nil).Once()
+		}},
+		{name: "lookup with empty Data", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: "numbers {{ lookup \"secrets.dev.slice\" }}", data: "", want: "numbers <no value>", wantErr: false, itemsCall: func(itemsSvc *mocksitems.Items) {
+			itemsSvc.On("Get", context.Background(), "space", "env", "secrets", "dev").Return(&items.Item{
+				ID:           "dev",
+				SpaceID:      "space",
+				EnvID:        "env",
+				CollectionID: "secrets",
+				Data:         map[string]interface{}{},
+			}, nil).Once()
+		}},
+		{name: "lookup with incorrect field", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: "hello {{ lookup \"secrets.dev.incorrect\" }}", data: "", want: "hello <no value>", wantErr: false, itemsCall: func(itemsSvc *mocksitems.Items) {
+			itemsSvc.On("Get", context.Background(), "space", "env", "secrets", "dev").Return(&items.Item{
+				ID:           "dev",
+				SpaceID:      "space",
+				EnvID:        "env",
+				CollectionID: "secrets",
+				Data: map[string]interface{}{
+					"id":  "dev",
+					"key": "1234",
+				},
+			}, nil).Once()
+		}},
+		{name: "lookup not found", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: "hello {{ lookup \"secrets.prod.pass\" }}", data: "", want: "", wantErr: true, itemsCall: func(itemsSvc *mocksitems.Items) {
+			itemsSvc.On("Get", context.Background(), "space", "env", "secrets", "prod").Return(nil, errors.New("not found")).Once()
+		}},
+		{name: "lookup without itemID", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: "hello {{ lookup \"secrets.pass\" }}", data: "", want: "", wantErr: true},
+		{name: "system ", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: "hello {{ system.SpaceID }}", data: "", want: "hello space", wantErr: false},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			itemsSvc := &mocksitems.Items{}
+			if tt.itemsCall != nil {
+				tt.itemsCall(itemsSvc)
+			}
+			tt.cnt = &content.Content{
+				Items: itemsSvc,
+			}
+			b := &Builder{
+				ctx:     tt.ctx,
+				cnt:     tt.cnt,
+				SpaceID: tt.SpaceID,
+				EnvID:   tt.EnvID,
+				funcs:   tt.funcs,
+			}
+
+			got, err := b.Execute(tt.str, tt.data)
+			if tt.wantErr == true {
+				assert.Error(t, err)
+			} else {
+				assert.NoError(t, err)
+			}
+			assert.Equal(t, tt.want, got)
+			if tt.itemsCall != nil {
+				itemsSvc.AssertExpectations(t)
+			}
+		})
+	}
+}
+
+func TestBuilder_ExecuteList(t *testing.T) {
+	tests := []struct {
+		name    string
+		ctx     context.Context
+		cnt     *content.Content
+		SpaceID string
+		EnvID   string
+		funcs   template.FuncMap
+		str     []string
+		data    any
+		want    []string
+		wantErr bool
+
+		itemsCall func(itemsSvc *mocksitems.Items)
+	}{
+		{name: "error", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: []string{"hello { . }}", "go {{ . }"}, data: "world", want: []string{}, wantErr: true},
+		{name: "empty", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: []string{""}, data: "world", want: []string{""}, wantErr: false},
+		{name: "#1", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: []string{"hello {{ . }}", "go {{ . }}"}, data: "world", want: []string{"hello world", "go world"}, wantErr: false},
+		{name: "#2", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: []string{"{{ . }}"}, data: "world", want: []string{"world"}, wantErr: false},
+		{name: "#3 ", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: []string{""}, data: "world", want: []string{""}, wantErr: false},
+		{name: "#4 ", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: []string{"hello"}, data: "world", want: []string{"hello"}, wantErr: false},
+		{name: "lookup", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: []string{"hello {{ lookup \"secrets.dev.key\" }}"}, data: "", want: []string{"hello 1234"}, wantErr: false, itemsCall: func(itemsSvc *mocksitems.Items) {
+			itemsSvc.On("Get", context.Background(), "space", "env", "secrets", "dev").Return(&items.Item{
+				ID:           "dev",
+				SpaceID:      "space",
+				EnvID:        "env",
+				CollectionID: "secrets",
+				Data: map[string]interface{}{
+					"id":  "dev",
+					"key": "1234",
+				},
+			}, nil).Once()
+		}},
+		{name: "lookup with incorrect field", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: []string{"hello {{ lookup \"secrets.dev.incorrect\" }}"}, data: "", want: []string{"hello <no value>"}, wantErr: false, itemsCall: func(itemsSvc *mocksitems.Items) {
+			itemsSvc.On("Get", context.Background(), "space", "env", "secrets", "dev").Return(&items.Item{
+				ID:           "dev",
+				SpaceID:      "space",
+				EnvID:        "env",
+				CollectionID: "secrets",
+				Data: map[string]interface{}{
+					"id":  "dev",
+					"key": "1234",
+				},
+			}, nil).Once()
+		}},
+		{name: "system ", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: []string{"hello {{ system.SpaceID }}"}, data: "", want: []string{"hello space"}, wantErr: false},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			itemsSvc := &mocksitems.Items{}
+			if tt.itemsCall != nil {
+				tt.itemsCall(itemsSvc)
+			}
+			tt.cnt = &content.Content{
+				Items: itemsSvc,
+			}
+			b := &Builder{
+				ctx:     tt.ctx,
+				cnt:     tt.cnt,
+				SpaceID: tt.SpaceID,
+				EnvID:   tt.EnvID,
+				funcs:   tt.funcs,
+			}
+
+			got, err := b.ExecuteList(tt.str, tt.data)
+			if tt.wantErr == true {
+				assert.Error(t, err)
+			} else {
+				assert.NoError(t, err)
+			}
+			assert.Equal(t, tt.want, got)
+			if tt.itemsCall != nil {
+				itemsSvc.AssertExpectations(t)
+			}
+		})
+	}
+}
+
+func TestBuilder_ExecuteMap(t *testing.T) {
+	tests := []struct {
+		name    string
+		ctx     context.Context
+		cnt     *content.Content
+		SpaceID string
+		EnvID   string
+		funcs   template.FuncMap
+		str     map[string]interface{}
+		data    any
+		want    map[string]interface{}
+		wantErr bool
+
+		itemsCall func(itemsSvc *mocksitems.Items)
+	}{
+		{name: "error", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: map[string]interface{}{"hello": "{{ . }"}, data: "world", want: nil, wantErr: true},
+		{name: "empty", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: map[string]interface{}{}, data: "", want: map[string]interface{}{}, wantErr: false},
+		{name: "#1", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: map[string]interface{}{"hello": "{{ . }}"}, data: "world", want: map[string]interface{}{"hello": "world"}, wantErr: false},
+		{name: "#2", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: map[string]interface{}{"hello": "{{ . }}", "go": "{{ . }}"}, data: "world", want: map[string]interface{}{"hello": "world", "go": "world"}, wantErr: false},
+		{name: "#3 ", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: map[string]interface{}{}, data: "world", want: map[string]interface{}{}, wantErr: false},
+		{name: "#4 ", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: map[string]interface{}{"a": "b"}, data: "world", want: map[string]interface{}{"a": "b"}, wantErr: false},
+		{name: "lookup ", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: map[string]interface{}{"hello": "{{ lookup \"secrets.dev.key\" }}"}, data: "", want: map[string]interface{}{"hello": "1234"}, wantErr: false, itemsCall: func(itemsSvc *mocksitems.Items) {
+			itemsSvc.On("Get", context.Background(), "space", "env", "secrets", "dev").Return(&items.Item{
+				ID:           "dev",
+				SpaceID:      "space",
+				EnvID:        "env",
+				CollectionID: "secrets",
+				Data: map[string]interface{}{
+					"id":  "dev",
+					"key": "1234",
+				},
+			}, nil).Once()
+		}},
+		{name: "lookup with incorrect field", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: map[string]interface{}{"hello": "{{ lookup \"secrets.dev.incorrect\" }}"}, data: "", want: map[string]interface{}{"hello": "<no value>"}, wantErr: false, itemsCall: func(itemsSvc *mocksitems.Items) {
+			itemsSvc.On("Get", context.Background(), "space", "env", "secrets", "dev").Return(&items.Item{
+				ID:           "dev",
+				SpaceID:      "space",
+				EnvID:        "env",
+				CollectionID: "secrets",
+				Data: map[string]interface{}{
+					"id":  "dev",
+					"key": "1234",
+				},
+			}, nil).Once()
+		}},
+		{name: "system ", ctx: context.Background(), cnt: &content.Content{}, SpaceID: "space", EnvID: "env", funcs: template.FuncMap{}, str: map[string]interface{}{"hello": "{{ system.SpaceID }}"}, data: "", want: map[string]interface{}{"hello": "space"}, wantErr: false},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			itemsSvc := &mocksitems.Items{}
+			if tt.itemsCall != nil {
+				tt.itemsCall(itemsSvc)
+			}
+			tt.cnt = &content.Content{
+				Items: itemsSvc,
+			}
+			b := &Builder{
+				ctx:     tt.ctx,
+				cnt:     tt.cnt,
+				SpaceID: tt.SpaceID,
+				EnvID:   tt.EnvID,
+				funcs:   tt.funcs,
+			}
+
+			got, err := b.ExecuteMap(tt.str, tt.data)
+			if tt.wantErr == true {
+				assert.Error(t, err)
+			} else {
+				assert.NoError(t, err)
+			}
+			assert.Equal(t, tt.want, got)
+			if tt.itemsCall != nil {
+				itemsSvc.AssertExpectations(t)
+			}
+		})
+	}
+}
diff --git a/pkg/template/funcs.go b/pkg/template/funcs.go
new file mode 100644
index 0000000000000000000000000000000000000000..0c320ad139e964f002b691ce097b24e70e6cfaf3
--- /dev/null
+++ b/pkg/template/funcs.go
@@ -0,0 +1,43 @@
+package template
+
+import (
+	"strings"
+
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+)
+
+// getLookup возвращает функцию для шаблонизатора для получения значений из записи коллекции
+// name указывается в виде "<collection id>.<item id>.<field>"
+// Использование в шаблонах:  {{ lookup "secrets.key.value" }}
+func getLookup(b *Builder) any {
+	return func(name string) (any, error) {
+		parsedName := strings.Split(name, ".")
+		if len(parsedName) < 3 {
+			return "", errors.Errorf("incorrect parameter \"%s\"", name)
+		}
+
+		collectionID := parsedName[0]
+		itemID := parsedName[1]
+		field := parsedName[2]
+		item, err := b.cnt.Items.Get(b.Context(), b.SpaceID, b.EnvID, collectionID, itemID)
+		if err != nil {
+			return "", errors.Wrapf(err, "failed to get \"%s\"")
+		}
+
+		if len(item.Data) > 0 {
+			if v, ok := item.Data[field]; ok {
+				return v, nil
+			}
+		}
+
+		return nil, nil
+	}
+}
+
+// getSys возвращает функцию получения System
+// Использование в шаблонах: {{ system.SpaceID }}
+func getSystem(b *Builder) any {
+	return func() *System {
+		return &System{builder: b}
+	}
+}
diff --git a/pkg/template/system.go b/pkg/template/system.go
new file mode 100644
index 0000000000000000000000000000000000000000..8f8548eb11444e72ad4f003b9225f4d4a38e4e2a
--- /dev/null
+++ b/pkg/template/system.go
@@ -0,0 +1,13 @@
+package template
+
+type System struct {
+	builder *Builder
+}
+
+func (s *System) SpaceID() string {
+	return s.builder.SpaceID
+}
+
+func (s *System) EnvID() string {
+	return s.builder.EnvID
+}
diff --git a/pkg/users/middleware/error_logging_middleware.go b/pkg/users/middleware/error_logging_middleware.go
index b0ec1fea8fa9fda00ac08a73e2e35e2dad51a549..a9084fa7a05608e45f7c5436934b5738c685a0e9 100644
--- a/pkg/users/middleware/error_logging_middleware.go
+++ b/pkg/users/middleware/error_logging_middleware.go
@@ -1,9 +1,9 @@
+package service
+
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../assets/templates/middleware/error_log
 // gowrap: http://github.com/hexdigest/gowrap
 
-package service
-
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/users -i Users -t ../../../assets/templates/middleware/error_log -o error_logging_middleware.go -l ""
 
 import (
diff --git a/pkg/users/middleware/logging_middleware.go b/pkg/users/middleware/logging_middleware.go
index a4bfb5fc53d8bc64a539eded9da47b2b6fa209c3..1fcae0626b75992ae563ff87adfd4e33edd49af6 100644
--- a/pkg/users/middleware/logging_middleware.go
+++ b/pkg/users/middleware/logging_middleware.go
@@ -1,9 +1,9 @@
+package service
+
 // Code generated by gowrap. DO NOT EDIT.
 // template: ../../../assets/templates/middleware/access_log
 // gowrap: http://github.com/hexdigest/gowrap
 
-package service
-
 //go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/users -i Users -t ../../../assets/templates/middleware/access_log -o logging_middleware.go -l ""
 
 import (