From a97e73b580d6747f69bac5bbdd4e55083d4b9f1b Mon Sep 17 00:00:00 2001
From: Valera Shaitorov <shaitorov@perx.ru>
Date: Mon, 17 Apr 2023 18:55:34 +0700
Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?=
 =?UTF-8?q?=D0=BD=D1=8B=20Members=20middlewares?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 pkg/members/middleware/caching_middleware.go  | 102 +++++++
 .../middleware/caching_middleware_test.go     | 147 ++++++++++
 .../middleware/error_logging_middleware.go    |  90 +++++++
 pkg/members/middleware/logging_middleware.go  | 251 ++++++++++++++++++
 pkg/members/middleware/middleware.go          |  28 ++
 .../middleware/recovering_middleware.go       | 103 +++++++
 6 files changed, 721 insertions(+)
 create mode 100644 pkg/members/middleware/caching_middleware.go
 create mode 100644 pkg/members/middleware/caching_middleware_test.go
 create mode 100644 pkg/members/middleware/error_logging_middleware.go
 create mode 100644 pkg/members/middleware/logging_middleware.go
 create mode 100644 pkg/members/middleware/middleware.go
 create mode 100644 pkg/members/middleware/recovering_middleware.go

diff --git a/pkg/members/middleware/caching_middleware.go b/pkg/members/middleware/caching_middleware.go
new file mode 100644
index 00000000..2faa5ce4
--- /dev/null
+++ b/pkg/members/middleware/caching_middleware.go
@@ -0,0 +1,102 @@
+package service
+
+import (
+	"context"
+	"strings"
+
+	"git.perx.ru/perxis/perxis-go/pkg/cache"
+	service "git.perx.ru/perxis/perxis-go/pkg/members"
+)
+
+func makeKey(ss ...string) string {
+	return strings.Join(ss, "-")
+}
+
+func CachingMiddleware(cache *cache.Cache) Middleware {
+	return func(next service.Members) service.Members {
+		return &cachingMiddleware{
+			cache: cache,
+			next:  next,
+		}
+	}
+}
+
+type cachingMiddleware struct {
+	cache *cache.Cache
+	next  service.Members
+}
+
+func (m cachingMiddleware) Set(ctx context.Context, orgId string, userId string, role service.Role) (err error) {
+
+	err = m.next.Set(ctx, orgId, userId, role)
+	if err == nil {
+		m.cache.Remove(makeKey(orgId, userId))
+		m.cache.Remove(makeKey(orgId))
+		m.cache.Remove(makeKey(userId))
+	}
+	return err
+}
+
+func (m cachingMiddleware) Get(ctx context.Context, orgId string, userId string) (role service.Role, err error) {
+
+	key := makeKey(orgId, userId)
+	value, e := m.cache.Get(key)
+	if e == nil {
+		return value.(service.Role), err
+	}
+	role, err = m.next.Get(ctx, orgId, userId)
+	if err == nil {
+		m.cache.Set(key, role)
+	}
+	return role, err
+}
+
+func (m cachingMiddleware) Remove(ctx context.Context, orgId string, userId string) (err error) {
+
+	err = m.next.Remove(ctx, orgId, userId)
+	if err == nil {
+		m.cache.Remove(makeKey(orgId, userId))
+		m.cache.Remove(makeKey(orgId))
+		m.cache.Remove(makeKey(userId))
+	}
+	return err
+}
+
+func (m cachingMiddleware) RemoveAll(ctx context.Context, orgId string) (err error) {
+
+	err = m.next.RemoveAll(ctx, orgId)
+	if err == nil {
+		members, _ := m.ListMembers(ctx, orgId)
+		for _, member := range members {
+			m.cache.Remove(member.UserId)
+			m.cache.Remove(makeKey(orgId, member.UserId))
+		}
+	}
+	return err
+}
+
+func (m cachingMiddleware) ListMembers(ctx context.Context, orgId string) (members []*service.Member, err error) {
+
+	value, e := m.cache.Get(makeKey(orgId))
+	if e == nil {
+		return value.([]*service.Member), err
+	}
+	members, err = m.next.ListMembers(ctx, orgId)
+	if err == nil {
+		m.cache.Set(makeKey(orgId), members)
+	}
+	return members, err
+}
+
+func (m cachingMiddleware) ListOrganizations(ctx context.Context, userId string) (members []*service.Member, err error) {
+
+	value, e := m.cache.Get(makeKey(userId))
+	if e == nil {
+		return value.([]*service.Member), err
+	}
+	members, err = m.next.ListOrganizations(ctx, userId)
+	if err == nil {
+		m.cache.Set(makeKey(userId), members)
+	}
+	return members, err
+}
diff --git a/pkg/members/middleware/caching_middleware_test.go b/pkg/members/middleware/caching_middleware_test.go
new file mode 100644
index 00000000..1844dc58
--- /dev/null
+++ b/pkg/members/middleware/caching_middleware_test.go
@@ -0,0 +1,147 @@
+package service
+
+import (
+	"context"
+	"testing"
+	"time"
+
+	"git.perx.ru/perxis/perxis-go/pkg/cache"
+	"git.perx.ru/perxis/perxis-go/pkg/members"
+	mocksmembers "git.perx.ru/perxis/perxis-go/pkg/members/mocks"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+	"github.com/stretchr/testify/require"
+)
+
+func TestMembersCache(t *testing.T) {
+
+	const (
+		orgId  = "orgId"
+		userId = "userId"
+		size   = 5
+		ttl    = 20 * time.Millisecond
+	)
+
+	ctx := context.Background()
+
+	t.Run("Get from cache", func(t *testing.T) {
+		mbrs := &mocksmembers.Members{}
+		svc := CachingMiddleware(cache.NewCache(size, ttl))(mbrs)
+
+		mbrs.On("Get", mock.Anything, orgId, userId).Return(members.RoleOwner, nil).Once()
+
+		v1, err := svc.Get(ctx, orgId, userId)
+		require.NoError(t, err)
+
+		v2, err := svc.Get(ctx, orgId, userId)
+		require.NoError(t, err)
+		assert.Equal(t, v1, v2, "Ожидается получение объекта из кэша, после повторного запроса get.")
+
+		mbrs.AssertExpectations(t)
+	})
+
+	t.Run("Invalidate cache", func(t *testing.T) {
+		t.Run("After Set", func(t *testing.T) {
+			mbrs := &mocksmembers.Members{}
+			svc := CachingMiddleware(cache.NewCache(size, ttl))(mbrs)
+
+			mbrs.On("Get", mock.Anything, orgId, userId).Return(members.RoleOwner, nil).Once()
+
+			v1, err := svc.Get(ctx, orgId, userId)
+			require.NoError(t, err)
+
+			v2, err := svc.Get(ctx, orgId, userId)
+			require.NoError(t, err)
+			assert.Equal(t, v1, v2, "Ожидается получение объекта из кэша.")
+
+			mbrs.On("Set", mock.Anything, orgId, userId, members.RoleMember).Return(nil).Once()
+
+			err = svc.Set(ctx, orgId, userId, members.RoleMember)
+			require.NoError(t, err)
+
+			mbrs.On("Get", mock.Anything, orgId, userId).Return(members.RoleMember, nil).Once()
+
+			v3, err := svc.Get(ctx, orgId, userId)
+			require.NoError(t, err)
+			assert.NotEqual(t, v2, v3, "Ожидается удаление объекта из кэша и получение заново из сервиса.")
+			mbrs.AssertExpectations(t)
+		})
+
+		t.Run("After Remove", func(t *testing.T) {
+			mbrs := &mocksmembers.Members{}
+			svc := CachingMiddleware(cache.NewCache(size, ttl))(mbrs)
+
+			mbrs.On("Get", mock.Anything, orgId, userId).Return(members.RoleOwner, nil).Once()
+
+			v1, err := svc.Get(ctx, orgId, userId)
+			require.NoError(t, err)
+
+			v2, err := svc.Get(ctx, orgId, userId)
+			require.NoError(t, err)
+			assert.Equal(t, v1, v2, "Ожидается получение объекта из кэша.")
+
+			mbrs.On("Remove", mock.Anything, orgId, userId).Return(nil).Once()
+
+			err = svc.Remove(ctx, orgId, userId)
+			require.NoError(t, err)
+
+			mbrs.On("Get", mock.Anything, orgId, userId).Return(members.NotMember, nil).Once()
+
+			v3, err := svc.Get(ctx, orgId, userId)
+			require.NoError(t, err)
+			assert.NotEqual(t, v2, v3, "Ожидается удаление объекта из кэша после удаления из хранилища и получение заново из сервиса.")
+
+			mbrs.AssertExpectations(t)
+		})
+
+		t.Run("After RemoveAll", func(t *testing.T) {
+			mbrs := &mocksmembers.Members{}
+			svc := CachingMiddleware(cache.NewCache(size, ttl))(mbrs)
+
+			mbrs.On("Get", mock.Anything, orgId, userId).Return(members.RoleOwner, nil).Once()
+			mbrs.On("ListMembers", mock.Anything, orgId).Return([]*members.Member{{OrgId: orgId, UserId: userId, Role: members.RoleOwner}}, nil)
+
+			v1, err := svc.Get(ctx, orgId, userId)
+			require.NoError(t, err)
+
+			v2, err := svc.Get(ctx, orgId, userId)
+			require.NoError(t, err)
+			assert.Equal(t, v1, v2, "Ожидается получение объекта из кэша.")
+
+			mbrs.On("RemoveAll", mock.Anything, orgId).Return(nil).Once()
+
+			err = svc.RemoveAll(ctx, orgId)
+			require.NoError(t, err)
+
+			mbrs.On("Get", mock.Anything, orgId, userId).Return(members.NotMember, nil).Once()
+
+			v3, err := svc.Get(ctx, orgId, userId)
+			require.NoError(t, err)
+			assert.NotEqual(t, v2, v3, "Ожидается удаление объекта из кэша после удаления из хранилища и получение заново из сервиса.")
+
+			mbrs.AssertExpectations(t)
+		})
+
+		t.Run("After TTL expired", func(t *testing.T) {
+			mbrs := &mocksmembers.Members{}
+			svc := CachingMiddleware(cache.NewCache(size, ttl))(mbrs)
+
+			mbrs.On("Get", mock.Anything, orgId, userId).Return(members.RoleOwner, nil).Once()
+
+			v1, err := svc.Get(ctx, orgId, userId)
+
+			v2, err := svc.Get(ctx, orgId, userId)
+			require.NoError(t, err)
+			assert.Equal(t, v1, v2, "Ожидается получение объекта из кэша.")
+
+			time.Sleep(2 * ttl)
+
+			mbrs.On("Get", mock.Anything, orgId, userId).Return(members.RoleMember, nil).Once()
+
+			v3, err := svc.Get(ctx, orgId, userId)
+			require.NoError(t, err)
+			assert.NotEqual(t, v2, v3, "Ожидается удаление объекта из кэша после истечения ttl и получение заново из сервиса.")
+			mbrs.AssertExpectations(t)
+		})
+	})
+}
diff --git a/pkg/members/middleware/error_logging_middleware.go b/pkg/members/middleware/error_logging_middleware.go
new file mode 100644
index 00000000..5eb4e3b3
--- /dev/null
+++ b/pkg/members/middleware/error_logging_middleware.go
@@ -0,0 +1,90 @@
+// 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 (
+	"context"
+
+	"git.perx.ru/perxis/perxis-go/pkg/members"
+	"go.uber.org/zap"
+)
+
+// errorLoggingMiddleware implements members.Members that is instrumented with logging
+type errorLoggingMiddleware struct {
+	logger *zap.Logger
+	next   members.Members
+}
+
+// ErrorLoggingMiddleware instruments an implementation of the members.Members with simple logging
+func ErrorLoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next members.Members) members.Members {
+		return &errorLoggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *errorLoggingMiddleware) Get(ctx context.Context, orgId string, userId string) (role members.Role, err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.Get(ctx, orgId, userId)
+}
+
+func (m *errorLoggingMiddleware) ListMembers(ctx context.Context, orgId string) (members []*members.Member, err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.ListMembers(ctx, orgId)
+}
+
+func (m *errorLoggingMiddleware) ListOrganizations(ctx context.Context, userId string) (organizations []*members.Member, err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.ListOrganizations(ctx, userId)
+}
+
+func (m *errorLoggingMiddleware) Remove(ctx context.Context, orgId string, userId string) (err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.Remove(ctx, orgId, userId)
+}
+
+func (m *errorLoggingMiddleware) RemoveAll(ctx context.Context, orgId string) (err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.RemoveAll(ctx, orgId)
+}
+
+func (m *errorLoggingMiddleware) Set(ctx context.Context, orgId string, userId string, role members.Role) (err error) {
+	logger := m.logger
+	defer func() {
+		if err != nil {
+			logger.Warn("response error", zap.Error(err))
+		}
+	}()
+	return m.next.Set(ctx, orgId, userId, role)
+}
diff --git a/pkg/members/middleware/logging_middleware.go b/pkg/members/middleware/logging_middleware.go
new file mode 100644
index 00000000..3b168703
--- /dev/null
+++ b/pkg/members/middleware/logging_middleware.go
@@ -0,0 +1,251 @@
+// 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 (
+	"context"
+	"fmt"
+	"time"
+
+	"git.perx.ru/perxis/perxis-go/pkg/auth"
+	"git.perx.ru/perxis/perxis-go/pkg/members"
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+)
+
+// loggingMiddleware implements members.Members that is instrumented with logging
+type loggingMiddleware struct {
+	logger *zap.Logger
+	next   members.Members
+}
+
+// LoggingMiddleware instruments an implementation of the members.Members with simple logging
+func LoggingMiddleware(logger *zap.Logger) Middleware {
+	return func(next members.Members) members.Members {
+		return &loggingMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *loggingMiddleware) Get(ctx context.Context, orgId string, userId string) (role members.Role, err error) {
+	begin := time.Now()
+	var fields []zapcore.Field
+	for k, v := range map[string]interface{}{
+		"ctx":    ctx,
+		"orgId":  orgId,
+		"userId": userId} {
+		if k == "ctx" {
+			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("Get.Request", fields...)
+
+	role, err = m.next.Get(ctx, orgId, userId)
+
+	fields = []zapcore.Field{
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	}
+
+	for k, v := range map[string]interface{}{
+		"role": role,
+		"err":  err} {
+		if k == "err" {
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("Get.Response", fields...)
+
+	return role, err
+}
+
+func (m *loggingMiddleware) ListMembers(ctx context.Context, orgId string) (members []*members.Member, err error) {
+	begin := time.Now()
+	var fields []zapcore.Field
+	for k, v := range map[string]interface{}{
+		"ctx":   ctx,
+		"orgId": orgId} {
+		if k == "ctx" {
+			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("ListMembers.Request", fields...)
+
+	members, err = m.next.ListMembers(ctx, orgId)
+
+	fields = []zapcore.Field{
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	}
+
+	for k, v := range map[string]interface{}{
+		"members": members,
+		"err":     err} {
+		if k == "err" {
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("ListMembers.Response", fields...)
+
+	return members, err
+}
+
+func (m *loggingMiddleware) ListOrganizations(ctx context.Context, userId string) (organizations []*members.Member, err error) {
+	begin := time.Now()
+	var fields []zapcore.Field
+	for k, v := range map[string]interface{}{
+		"ctx":    ctx,
+		"userId": userId} {
+		if k == "ctx" {
+			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("ListOrganizations.Request", fields...)
+
+	organizations, err = m.next.ListOrganizations(ctx, userId)
+
+	fields = []zapcore.Field{
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	}
+
+	for k, v := range map[string]interface{}{
+		"organizations": organizations,
+		"err":           err} {
+		if k == "err" {
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("ListOrganizations.Response", fields...)
+
+	return organizations, err
+}
+
+func (m *loggingMiddleware) Remove(ctx context.Context, orgId string, userId string) (err error) {
+	begin := time.Now()
+	var fields []zapcore.Field
+	for k, v := range map[string]interface{}{
+		"ctx":    ctx,
+		"orgId":  orgId,
+		"userId": userId} {
+		if k == "ctx" {
+			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("Remove.Request", fields...)
+
+	err = m.next.Remove(ctx, orgId, userId)
+
+	fields = []zapcore.Field{
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	}
+
+	for k, v := range map[string]interface{}{
+		"err": err} {
+		if k == "err" {
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("Remove.Response", fields...)
+
+	return err
+}
+
+func (m *loggingMiddleware) RemoveAll(ctx context.Context, orgId string) (err error) {
+	begin := time.Now()
+	var fields []zapcore.Field
+	for k, v := range map[string]interface{}{
+		"ctx":   ctx,
+		"orgId": orgId} {
+		if k == "ctx" {
+			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("RemoveAll.Request", fields...)
+
+	err = m.next.RemoveAll(ctx, orgId)
+
+	fields = []zapcore.Field{
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	}
+
+	for k, v := range map[string]interface{}{
+		"err": err} {
+		if k == "err" {
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("RemoveAll.Response", fields...)
+
+	return err
+}
+
+func (m *loggingMiddleware) Set(ctx context.Context, orgId string, userId string, role members.Role) (err error) {
+	begin := time.Now()
+	var fields []zapcore.Field
+	for k, v := range map[string]interface{}{
+		"ctx":    ctx,
+		"orgId":  orgId,
+		"userId": userId,
+		"role":   role} {
+		if k == "ctx" {
+			fields = append(fields, zap.String("principal", fmt.Sprint(auth.GetPrincipal(ctx))))
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("Set.Request", fields...)
+
+	err = m.next.Set(ctx, orgId, userId, role)
+
+	fields = []zapcore.Field{
+		zap.Duration("time", time.Since(begin)),
+		zap.Error(err),
+	}
+
+	for k, v := range map[string]interface{}{
+		"err": err} {
+		if k == "err" {
+			continue
+		}
+		fields = append(fields, zap.Reflect(k, v))
+	}
+
+	m.logger.Debug("Set.Response", fields...)
+
+	return err
+}
diff --git a/pkg/members/middleware/middleware.go b/pkg/members/middleware/middleware.go
new file mode 100644
index 00000000..1aa0cfbe
--- /dev/null
+++ b/pkg/members/middleware/middleware.go
@@ -0,0 +1,28 @@
+// Code generated by gowrap. DO NOT EDIT.
+// template: ../../../assets/templates/middleware/middleware
+// 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/middleware -o middleware.go -l ""
+
+import (
+	"git.perx.ru/perxis/perxis-go/pkg/members"
+	"go.uber.org/zap"
+)
+
+type Middleware func(members.Members) members.Members
+
+func WithLog(s members.Members, logger *zap.Logger, log_access bool) members.Members {
+	if logger == nil {
+		logger = zap.NewNop()
+	}
+
+	logger = logger.Named("Members")
+	s = ErrorLoggingMiddleware(logger)(s)
+	if log_access {
+		s = LoggingMiddleware(logger)(s)
+	}
+	s = RecoveringMiddleware(logger)(s)
+	return s
+}
diff --git a/pkg/members/middleware/recovering_middleware.go b/pkg/members/middleware/recovering_middleware.go
new file mode 100644
index 00000000..ec6db9f4
--- /dev/null
+++ b/pkg/members/middleware/recovering_middleware.go
@@ -0,0 +1,103 @@
+// Code generated by gowrap. DO NOT EDIT.
+// template: ../../../assets/templates/middleware/recovery
+// 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/recovery -o recovering_middleware.go -l ""
+
+import (
+	"context"
+	"fmt"
+
+	"git.perx.ru/perxis/perxis-go/pkg/members"
+	"go.uber.org/zap"
+)
+
+// recoveringMiddleware implements members.Members that is instrumented with logging
+type recoveringMiddleware struct {
+	logger *zap.Logger
+	next   members.Members
+}
+
+// RecoveringMiddleware instruments an implementation of the members.Members with simple logging
+func RecoveringMiddleware(logger *zap.Logger) Middleware {
+	return func(next members.Members) members.Members {
+		return &recoveringMiddleware{
+			next:   next,
+			logger: logger,
+		}
+	}
+}
+
+func (m *recoveringMiddleware) Get(ctx context.Context, orgId string, userId string) (role members.Role, err error) {
+	logger := m.logger
+	defer func() {
+		if r := recover(); r != nil {
+			logger.Error("panic", zap.Error(fmt.Errorf("%v", r)))
+			err = fmt.Errorf("%v", r)
+		}
+	}()
+
+	return m.next.Get(ctx, orgId, userId)
+}
+
+func (m *recoveringMiddleware) ListMembers(ctx context.Context, orgId string) (members []*members.Member, err error) {
+	logger := m.logger
+	defer func() {
+		if r := recover(); r != nil {
+			logger.Error("panic", zap.Error(fmt.Errorf("%v", r)))
+			err = fmt.Errorf("%v", r)
+		}
+	}()
+
+	return m.next.ListMembers(ctx, orgId)
+}
+
+func (m *recoveringMiddleware) ListOrganizations(ctx context.Context, userId string) (organizations []*members.Member, err error) {
+	logger := m.logger
+	defer func() {
+		if r := recover(); r != nil {
+			logger.Error("panic", zap.Error(fmt.Errorf("%v", r)))
+			err = fmt.Errorf("%v", r)
+		}
+	}()
+
+	return m.next.ListOrganizations(ctx, userId)
+}
+
+func (m *recoveringMiddleware) Remove(ctx context.Context, orgId string, userId string) (err error) {
+	logger := m.logger
+	defer func() {
+		if r := recover(); r != nil {
+			logger.Error("panic", zap.Error(fmt.Errorf("%v", r)))
+			err = fmt.Errorf("%v", r)
+		}
+	}()
+
+	return m.next.Remove(ctx, orgId, userId)
+}
+
+func (m *recoveringMiddleware) RemoveAll(ctx context.Context, orgId string) (err error) {
+	logger := m.logger
+	defer func() {
+		if r := recover(); r != nil {
+			logger.Error("panic", zap.Error(fmt.Errorf("%v", r)))
+			err = fmt.Errorf("%v", r)
+		}
+	}()
+
+	return m.next.RemoveAll(ctx, orgId)
+}
+
+func (m *recoveringMiddleware) Set(ctx context.Context, orgId string, userId string, role members.Role) (err error) {
+	logger := m.logger
+	defer func() {
+		if r := recover(); r != nil {
+			logger.Error("panic", zap.Error(fmt.Errorf("%v", r)))
+			err = fmt.Errorf("%v", r)
+		}
+	}()
+
+	return m.next.Set(ctx, orgId, userId, role)
+}
-- 
GitLab