Skip to content
Snippets Groups Projects
Commit 92827f1d authored by Semyon Krestyaninov's avatar Semyon Krestyaninov :dog2: Committed by Pavel Antonov
Browse files

Обновлено API Locales для соответствия спецификации Proto

parent 95c4ff6b
No related branches found
No related tags found
No related merge requests found
Showing
with 328 additions and 31 deletions
package locales
import "git.perx.ru/perxis/perxis-go/pkg/service"
var (
ErrNotFound = service.ErrNotFound
ErrAlreadyExists = service.ErrAlreadyExists
)
......@@ -2,5 +2,6 @@ package locales
const (
EventCreate = "locales.create"
EventUpdate = "locales.update"
EventDelete = "locales.delete"
)
package locales
const (
DefaultID = "default" // DefaultID идентификатор локали по умолчанию
DefaultDirection = "ltr" // DefaultDirection направление письма по умолчанию
)
type Locale struct {
ID string `json:"id" bson:"_id"` // (Пример: "en", "en-US")
SpaceID string `json:"spaceId" bson:"-"`
Name string `json:"name" bson:"name"` // (Пример: "English", "English (US)" )
ID string `json:"id" bson:"_id"` // Идентификатор локали, генерируется автоматически. Для локали по умолчанию устанавливается как "default".
SpaceID string `json:"spaceId" bson:"-"` // Идентификатор пространства.
Name string `json:"name" bson:"name"` // Название локали. Опционально, заполняется автоматически (Пример: russian, english)
NativeName string `json:"native_name" bson:"native_name"` // Название локали на языке локали. Опционально, заполняется автоматически (Пример: Русский, English)
Code string `json:"code" bson:"code"` // Код локали https://en.wikipedia.org/wiki/IETF_language_tag
Fallback string `json:"fallback" bson:"fallback"` // Идентификатор локали, который будет использоваться при отсутствии перевода
Direction string `json:"direction" bson:"direction"` // Направление письма - слева направо (ltr) или справа налево (rtl). По умолчанию устанавливается ltr.
Weight int `json:"weight" bson:"weight"` // Вес локали.
NoPublish bool `json:"no_publish" bson:"no_publish"` // Не публиковать контент данной локали. Не будет доступен контент через Delivery API. (кроме default)
Disabled bool `json:"disabled" bson:"disabled"` // Запретить использование локали. Нельзя создавать и редактировать контент для данной локали (кроме default)
}
func (locale Locale) IsDefault() bool {
return locale.ID == DefaultID
}
......@@ -87,3 +87,21 @@ func (m *accessLoggingMiddleware) List(ctx context.Context, spaceId string) (loc
return locales, err
}
func (m *accessLoggingMiddleware) Update(ctx context.Context, locale *locales.Locale) (err error) {
begin := time.Now()
m.logger.Debug("Update.Request",
zap.Reflect("principal", auth.GetPrincipal(ctx)),
zap.Reflect("locale", locale),
)
err = m.next.Update(ctx, locale)
m.logger.Debug("Update.Response",
zap.Duration("time", time.Since(begin)),
zap.Error(err),
)
return err
}
......@@ -30,6 +30,15 @@ func (m cachingMiddleware) Create(ctx context.Context, locale *service.Locale) (
return loc, err
}
func (m cachingMiddleware) Update(ctx context.Context, locale *service.Locale) (err error) {
err = m.next.Update(ctx, locale)
if err == nil {
_ = m.cache.Remove(locale.SpaceID)
}
return err
}
func (m cachingMiddleware) List(ctx context.Context, spaceId string) (locales []*service.Locale, err error) {
value, e := m.cache.Get(spaceId)
......
......@@ -103,6 +103,38 @@ func TestLocalesCache(t *testing.T) {
loc.AssertExpectations(t)
})
t.Run("After Update", func(t *testing.T) {
loc := &locmocks.Locales{}
svc := CachingMiddleware(cache.NewMemoryCache(size, ttl))(loc)
loc.On("List", mock.Anything, spaceID).Return([]*locales.Locale{{ID: loc1, Name: "name1", SpaceID: spaceID}}, nil).Once()
vl1, err := svc.List(ctx, spaceID)
require.NoError(t, err)
vl2, err := svc.List(ctx, spaceID)
require.NoError(t, err)
assert.Same(t, vl1[0], vl2[0], "Ожидается что при повторном запросе объекты будут получены из кэша.")
loc.On("Update", mock.Anything, mock.Anything).Return(nil).Once()
err = svc.Update(ctx, &locales.Locale{ID: loc2, Name: "name2", SpaceID: spaceID})
require.NoError(t, err)
loc.On("List", mock.Anything, spaceID).
Return([]*locales.Locale{
{ID: loc1, Name: "name1", SpaceID: spaceID},
{ID: loc2, Name: "name2", SpaceID: spaceID},
}, nil).Once()
vl3, err := svc.List(ctx, spaceID)
require.NoError(t, err)
assert.Len(t, vl3, 2, "Ожидается что после обновления объекта данные будут удалены из кеша и получены из сервиса.")
loc.AssertExpectations(t)
})
t.Run("After TTL expired", func(t *testing.T) {
loc := &locmocks.Locales{}
......
......@@ -58,3 +58,13 @@ func (m *errorLoggingMiddleware) List(ctx context.Context, spaceId string) (loca
}()
return m.next.List(ctx, spaceId)
}
func (m *errorLoggingMiddleware) Update(ctx context.Context, locale *locales.Locale) (err error) {
logger := m.logger
defer func() {
if err != nil {
logger.Warn("response error", zap.Error(err))
}
}()
return m.next.Update(ctx, locale)
}
......@@ -39,6 +39,23 @@ func (m *loggingMiddleware) Create(ctx context.Context, locale *locales.Locale)
return created, err
}
func (m *loggingMiddleware) Update(ctx context.Context, locale *locales.Locale) (err error) {
logger := m.logger.With(
logzap.Caller(ctx),
logzap.Event(locales.EventUpdate),
logzap.Object(locale),
)
err = m.next.Update(ctx, locale)
if err != nil {
logger.Error("Failed to update", zap.Error(err), logzap.Channels(logzap.Userlog, logzap.Syslog))
return
}
logger.Info("Locale updated", logzap.Channels(logzap.Userlog))
return err
}
func (m *loggingMiddleware) List(ctx context.Context, spaceId string) (locales []*locales.Locale, err error) {
logger := m.logger.With(
logzap.Caller(ctx),
......
......@@ -65,3 +65,15 @@ func (m *recoveringMiddleware) List(ctx context.Context, spaceId string) (locale
return m.next.List(ctx, spaceId)
}
func (m *recoveringMiddleware) Update(ctx context.Context, locale *locales.Locale) (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.Update(ctx, locale)
}
// Code generated by gowrap. DO NOT EDIT.
// template: ..\..\..\assets\templates\middleware\telemetry
// template: ../../../assets/templates/middleware/telemetry
// gowrap: http://github.com/hexdigest/gowrap
package middleware
//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/locales -i Locales -t ..\..\..\assets\templates\middleware\telemetry -o telemetry_middleware.go -l ""
//go:generate gowrap gen -p git.perx.ru/perxis/perxis-go/pkg/locales -i Locales -t ../../../assets/templates/middleware/telemetry -o telemetry_middleware.go -l ""
// source template: https://github.com/hexdigest/gowrap/blob/master/templates/opentelemetry
......@@ -149,3 +149,36 @@ func (_d telemetryMiddleware) List(ctx context.Context, spaceId string) (locales
}()
return _d.Locales.List(ctx, spaceId)
}
// Update implements locales.Locales
func (_d telemetryMiddleware) Update(ctx context.Context, locale *locales.Locale) (err error) {
attributes := otelmetric.WithAttributeSet(attribute.NewSet(
attribute.String("service", "Locales"),
attribute.String("method", "Update"),
))
_d.requestMetrics.Total.Add(ctx, 1, attributes)
start := time.Now()
ctx, _span := otel.Tracer(_d._instance).Start(ctx, "Locales.Update")
defer func() {
_d.requestMetrics.DurationMilliseconds.Record(ctx, time.Since(start).Milliseconds(), attributes)
if _d._spanDecorator != nil {
_d._spanDecorator(_span, map[string]interface{}{
"ctx": ctx,
"locale": locale}, map[string]interface{}{
"err": err})
} else if err != nil {
_d.requestMetrics.FailedTotal.Add(ctx, 1, attributes)
_span.RecordError(err)
_span.SetAttributes(attribute.String("event", "error"))
_span.SetAttributes(attribute.String("message", err.Error()))
}
_span.End()
}()
return _d.Locales.Update(ctx, locale)
}
// Code generated by mockery v2.15.0. DO NOT EDIT.
// Code generated by mockery v2.43.2. DO NOT EDIT.
package mocks
......@@ -18,7 +18,15 @@ type Locales struct {
func (_m *Locales) Create(ctx context.Context, locale *locales.Locale) (*locales.Locale, error) {
ret := _m.Called(ctx, locale)
if len(ret) == 0 {
panic("no return value specified for Create")
}
var r0 *locales.Locale
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *locales.Locale) (*locales.Locale, error)); ok {
return rf(ctx, locale)
}
if rf, ok := ret.Get(0).(func(context.Context, *locales.Locale) *locales.Locale); ok {
r0 = rf(ctx, locale)
} else {
......@@ -27,7 +35,6 @@ func (_m *Locales) Create(ctx context.Context, locale *locales.Locale) (*locales
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *locales.Locale) error); ok {
r1 = rf(ctx, locale)
} else {
......@@ -41,6 +48,10 @@ func (_m *Locales) Create(ctx context.Context, locale *locales.Locale) (*locales
func (_m *Locales) Delete(ctx context.Context, spaceId string, localeId string) error {
ret := _m.Called(ctx, spaceId, localeId)
if len(ret) == 0 {
panic("no return value specified for Delete")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
r0 = rf(ctx, spaceId, localeId)
......@@ -55,7 +66,15 @@ func (_m *Locales) Delete(ctx context.Context, spaceId string, localeId string)
func (_m *Locales) List(ctx context.Context, spaceId string) ([]*locales.Locale, error) {
ret := _m.Called(ctx, spaceId)
if len(ret) == 0 {
panic("no return value specified for List")
}
var r0 []*locales.Locale
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) ([]*locales.Locale, error)); ok {
return rf(ctx, spaceId)
}
if rf, ok := ret.Get(0).(func(context.Context, string) []*locales.Locale); ok {
r0 = rf(ctx, spaceId)
} else {
......@@ -64,7 +83,6 @@ func (_m *Locales) List(ctx context.Context, spaceId string) ([]*locales.Locale,
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, spaceId)
} else {
......@@ -74,13 +92,30 @@ func (_m *Locales) List(ctx context.Context, spaceId string) ([]*locales.Locale,
return r0, r1
}
type mockConstructorTestingTNewLocales interface {
mock.TestingT
Cleanup(func())
// Update provides a mock function with given fields: ctx, locale
func (_m *Locales) Update(ctx context.Context, locale *locales.Locale) error {
ret := _m.Called(ctx, locale)
if len(ret) == 0 {
panic("no return value specified for Update")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *locales.Locale) error); ok {
r0 = rf(ctx, locale)
} else {
r0 = ret.Error(0)
}
return r0
}
// NewLocales creates a new instance of Locales. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewLocales(t mockConstructorTestingTNewLocales) *Locales {
// The first argument is typically a *testing.T value.
func NewLocales(t interface {
mock.TestingT
Cleanup(func())
}) *Locales {
mock := &Locales{}
mock.Mock.Test(t)
......
// Code generated by mockery v2.15.0. DO NOT EDIT.
// Code generated by mockery v2.43.2. DO NOT EDIT.
package mocks
......@@ -20,7 +20,15 @@ type Storage struct {
func (_m *Storage) Create(ctx context.Context, locale *locales.Locale) (*locales.Locale, error) {
ret := _m.Called(ctx, locale)
if len(ret) == 0 {
panic("no return value specified for Create")
}
var r0 *locales.Locale
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, *locales.Locale) (*locales.Locale, error)); ok {
return rf(ctx, locale)
}
if rf, ok := ret.Get(0).(func(context.Context, *locales.Locale) *locales.Locale); ok {
r0 = rf(ctx, locale)
} else {
......@@ -29,7 +37,6 @@ func (_m *Storage) Create(ctx context.Context, locale *locales.Locale) (*locales
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *locales.Locale) error); ok {
r1 = rf(ctx, locale)
} else {
......@@ -43,14 +50,21 @@ func (_m *Storage) Create(ctx context.Context, locale *locales.Locale) (*locales
func (_m *Storage) Delete(ctx context.Context, spaceID string, filter *locales.Filter) (int, error) {
ret := _m.Called(ctx, spaceID, filter)
if len(ret) == 0 {
panic("no return value specified for Delete")
}
var r0 int
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, *locales.Filter) (int, error)); ok {
return rf(ctx, spaceID, filter)
}
if rf, ok := ret.Get(0).(func(context.Context, string, *locales.Filter) int); ok {
r0 = rf(ctx, spaceID, filter)
} else {
r0 = ret.Get(0).(int)
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, string, *locales.Filter) error); ok {
r1 = rf(ctx, spaceID, filter)
} else {
......@@ -64,7 +78,16 @@ func (_m *Storage) Delete(ctx context.Context, spaceID string, filter *locales.F
func (_m *Storage) Find(ctx context.Context, spaceID string, filter *locales.Filter, opts *options.FindOptions) ([]*locales.Locale, int, error) {
ret := _m.Called(ctx, spaceID, filter, opts)
if len(ret) == 0 {
panic("no return value specified for Find")
}
var r0 []*locales.Locale
var r1 int
var r2 error
if rf, ok := ret.Get(0).(func(context.Context, string, *locales.Filter, *options.FindOptions) ([]*locales.Locale, int, error)); ok {
return rf(ctx, spaceID, filter, opts)
}
if rf, ok := ret.Get(0).(func(context.Context, string, *locales.Filter, *options.FindOptions) []*locales.Locale); ok {
r0 = rf(ctx, spaceID, filter, opts)
} else {
......@@ -73,14 +96,12 @@ func (_m *Storage) Find(ctx context.Context, spaceID string, filter *locales.Fil
}
}
var r1 int
if rf, ok := ret.Get(1).(func(context.Context, string, *locales.Filter, *options.FindOptions) int); ok {
r1 = rf(ctx, spaceID, filter, opts)
} else {
r1 = ret.Get(1).(int)
}
var r2 error
if rf, ok := ret.Get(2).(func(context.Context, string, *locales.Filter, *options.FindOptions) error); ok {
r2 = rf(ctx, spaceID, filter, opts)
} else {
......@@ -94,6 +115,10 @@ func (_m *Storage) Find(ctx context.Context, spaceID string, filter *locales.Fil
func (_m *Storage) Reset(ctx context.Context, spaceID string) error {
ret := _m.Called(ctx, spaceID)
if len(ret) == 0 {
panic("no return value specified for Reset")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
r0 = rf(ctx, spaceID)
......@@ -104,13 +129,30 @@ func (_m *Storage) Reset(ctx context.Context, spaceID string) error {
return r0
}
type mockConstructorTestingTNewStorage interface {
mock.TestingT
Cleanup(func())
// Update provides a mock function with given fields: ctx, locale
func (_m *Storage) Update(ctx context.Context, locale *locales.Locale) error {
ret := _m.Called(ctx, locale)
if len(ret) == 0 {
panic("no return value specified for Update")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *locales.Locale) error); ok {
r0 = rf(ctx, locale)
} else {
r0 = ret.Error(0)
}
return r0
}
// NewStorage creates a new instance of Storage. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewStorage(t mockConstructorTestingTNewStorage) *Storage {
// The first argument is typically a *testing.T value.
func NewStorage(t interface {
mock.TestingT
Cleanup(func())
}) *Storage {
mock := &Storage{}
mock.Mock.Test(t)
......
......@@ -9,6 +9,7 @@ import (
// @grpc-addr content.locales.Locales
type Locales interface {
Create(ctx context.Context, locale *Locale) (created *Locale, err error)
Update(ctx context.Context, locale *Locale) (err error)
List(ctx context.Context, spaceId string) (locales []*Locale, err error)
Delete(ctx context.Context, spaceId, localeId string) (err error)
}
......@@ -10,11 +10,18 @@ type Storage interface {
Reset(ctx context.Context, spaceID string) error
Create(ctx context.Context, locale *Locale) (created *Locale, err error)
Update(ctx context.Context, locale *Locale) (err error)
Find(ctx context.Context, spaceID string, filter *Filter, opts *options.FindOptions) (locales []*Locale, total int, err error)
Delete(ctx context.Context, spaceID string, filter *Filter) (total int, err error)
}
type Filter struct {
ID []string
Name []string
ID []string `json:"id"`
Name []string `json:"name"`
NativeName []string `json:"native_name"`
Code []string `json:"code"`
Fallback []string `json:"fallback"`
Direction []string `json:"direction"`
NoPublish *bool `json:"no_publish"`
Disabled *bool `json:"disabled"`
}
......@@ -17,6 +17,15 @@ func (set EndpointsSet) Create(arg0 context.Context, arg1 *locales.Locale) (res0
return response.(*CreateResponse).Created, res1
}
func (set EndpointsSet) Update(arg0 context.Context, arg1 *locales.Locale) (res1 error) {
request := UpdateRequest{Locale: arg1}
_, res1 = set.UpdateEndpoint(arg0, &request)
if res1 != nil {
return
}
return res1
}
func (set EndpointsSet) List(arg0 context.Context, arg1 string) (res0 []*locales.Locale, res1 error) {
request := ListRequest{SpaceId: arg1}
response, res1 := set.ListEndpoint(arg0, &request)
......@@ -28,7 +37,7 @@ func (set EndpointsSet) List(arg0 context.Context, arg1 string) (res0 []*locales
func (set EndpointsSet) Delete(arg0 context.Context, arg1 string, arg2 string) (res0 error) {
request := DeleteRequest{
LocaleId: arg2,
Id: arg2,
SpaceId: arg1,
}
_, res0 = set.DeleteEndpoint(arg0, &request)
......
......@@ -7,6 +7,7 @@ import endpoint "github.com/go-kit/kit/endpoint"
// EndpointsSet implements Locales API and used for transport purposes.
type EndpointsSet struct {
CreateEndpoint endpoint.Endpoint
UpdateEndpoint endpoint.Endpoint
ListEndpoint endpoint.Endpoint
DeleteEndpoint endpoint.Endpoint
}
......@@ -12,6 +12,12 @@ type (
Created *locales.Locale `json:"created"`
}
UpdateRequest struct {
Locale *locales.Locale `json:"locale"`
}
// Formal exchange type, please do not delete.
UpdateResponse struct{}
ListRequest struct {
SpaceId string `json:"space_id"`
}
......@@ -20,8 +26,8 @@ type (
}
DeleteRequest struct {
Id string `json:"id"`
SpaceId string `json:"space_id"`
LocaleId string `json:"locale_id"`
}
// Formal exchange type, please do not delete.
DeleteResponse struct{}
......
......@@ -13,6 +13,7 @@ func NewClient(conn *grpc.ClientConn, opts ...grpckit.ClientOption) transport.En
c := NewGRPCClient(conn, "", opts...)
return transport.EndpointsSet{
CreateEndpoint: grpcerr.ClientMiddleware(c.CreateEndpoint),
UpdateEndpoint: grpcerr.ClientMiddleware(c.UpdateEndpoint),
DeleteEndpoint: grpcerr.ClientMiddleware(c.DeleteEndpoint),
ListEndpoint: grpcerr.ClientMiddleware(c.ListEndpoint),
}
......
......@@ -22,6 +22,13 @@ func NewGRPCClient(conn *grpc.ClientConn, addr string, opts ...grpckit.ClientOpt
pb.CreateResponse{},
opts...,
).Endpoint(),
UpdateEndpoint: grpckit.NewClient(
conn, addr, "Update",
_Encode_Update_Request,
_Decode_Update_Response,
empty.Empty{},
opts...,
).Endpoint(),
DeleteEndpoint: grpckit.NewClient(
conn, addr, "Delete",
_Encode_Delete_Request,
......
......@@ -24,6 +24,18 @@ func _Encode_Create_Request(ctx context.Context, request interface{}) (interface
return &pb.CreateRequest{Locale: pbLocale}, nil
}
func _Encode_Update_Request(ctx context.Context, request interface{}) (interface{}, error) {
if request == nil {
return nil, errors.New("nil UpdateRequest")
}
req := request.(*transport.UpdateRequest)
pbLocale, err := PtrLocaleToProto(req.Locale)
if err != nil {
return nil, err
}
return &pb.UpdateRequest{Locale: pbLocale}, nil
}
func _Encode_List_Request(ctx context.Context, request interface{}) (interface{}, error) {
if request == nil {
return nil, errors.New("nil ListRequest")
......@@ -38,7 +50,7 @@ func _Encode_Delete_Request(ctx context.Context, request interface{}) (interface
}
req := request.(*transport.DeleteRequest)
return &pb.DeleteRequest{
LocaleId: req.LocaleId,
Id: req.Id,
SpaceId: req.SpaceId,
}, nil
}
......@@ -55,6 +67,10 @@ func _Encode_Create_Response(ctx context.Context, response interface{}) (interfa
return &pb.CreateResponse{Locale: respLocale}, nil
}
func _Encode_Update_Response(ctx context.Context, response interface{}) (interface{}, error) {
return &empty.Empty{}, nil
}
func _Encode_List_Response(ctx context.Context, response interface{}) (interface{}, error) {
if response == nil {
return nil, errors.New("nil ListResponse")
......@@ -83,6 +99,18 @@ func _Decode_Create_Request(ctx context.Context, request interface{}) (interface
return &transport.CreateRequest{Locale: locale}, nil
}
func _Decode_Update_Request(ctx context.Context, request interface{}) (interface{}, error) {
if request == nil {
return nil, errors.New("nil UpdateRequest")
}
req := request.(*pb.UpdateRequest)
locale, err := ProtoToPtrLocale(req.Locale)
if err != nil {
return nil, err
}
return &transport.UpdateRequest{Locale: locale}, nil
}
func _Decode_List_Request(ctx context.Context, request interface{}) (interface{}, error) {
if request == nil {
return nil, errors.New("nil ListRequest")
......@@ -97,7 +125,7 @@ func _Decode_Delete_Request(ctx context.Context, request interface{}) (interface
}
req := request.(*pb.DeleteRequest)
return &transport.DeleteRequest{
LocaleId: string(req.LocaleId),
Id: string(req.Id),
SpaceId: string(req.SpaceId),
}, nil
}
......@@ -114,6 +142,10 @@ func _Decode_Create_Response(ctx context.Context, response interface{}) (interfa
return &transport.CreateResponse{Created: respLocale}, nil
}
func _Decode_Update_Response(ctx context.Context, response interface{}) (interface{}, error) {
return &empty.Empty{}, nil
}
func _Decode_List_Response(ctx context.Context, response interface{}) (interface{}, error) {
if response == nil {
return nil, errors.New("nil ListResponse")
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment