From 250400639a47341ac8e23ef5367faa81a45bdefb Mon Sep 17 00:00:00 2001 From: Florian Schade Date: Tue, 20 May 2025 14:27:21 +0200 Subject: [PATCH] enhancement: refine the profile photo service and introduce httpDataProviders which allows reusing the endpoints --- services/graph/.mockery.yaml | 1 + .../users_user_profile_photo_provider.go | 191 ++++++++++++++++++ .../v0/api_users_user_profile_photo.go | 109 +++++----- .../v0/api_users_user_profile_photo_test.go | 115 ++++++++++- services/graph/pkg/service/v0/data.go | 42 ++++ services/graph/pkg/service/v0/service.go | 111 +++++----- 6 files changed, 452 insertions(+), 117 deletions(-) create mode 100644 services/graph/mocks/users_user_profile_photo_provider.go create mode 100644 services/graph/pkg/service/v0/data.go diff --git a/services/graph/.mockery.yaml b/services/graph/.mockery.yaml index 163e6e23c..551e991a8 100644 --- a/services/graph/.mockery.yaml +++ b/services/graph/.mockery.yaml @@ -16,6 +16,7 @@ packages: HTTPClient: Permissions: RoleService: + UsersUserProfilePhotoProvider: github.com/opencloud-eu/reva/v2/pkg/events: config: dir: "mocks" diff --git a/services/graph/mocks/users_user_profile_photo_provider.go b/services/graph/mocks/users_user_profile_photo_provider.go new file mode 100644 index 000000000..89c17e305 --- /dev/null +++ b/services/graph/mocks/users_user_profile_photo_provider.go @@ -0,0 +1,191 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + context "context" + io "io" + + mock "github.com/stretchr/testify/mock" +) + +// UsersUserProfilePhotoProvider is an autogenerated mock type for the UsersUserProfilePhotoProvider type +type UsersUserProfilePhotoProvider struct { + mock.Mock +} + +type UsersUserProfilePhotoProvider_Expecter struct { + mock *mock.Mock +} + +func (_m *UsersUserProfilePhotoProvider) EXPECT() *UsersUserProfilePhotoProvider_Expecter { + return &UsersUserProfilePhotoProvider_Expecter{mock: &_m.Mock} +} + +// DeletePhoto provides a mock function with given fields: ctx, id +func (_m *UsersUserProfilePhotoProvider) DeletePhoto(ctx context.Context, id string) error { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for DeletePhoto") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(ctx, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UsersUserProfilePhotoProvider_DeletePhoto_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeletePhoto' +type UsersUserProfilePhotoProvider_DeletePhoto_Call struct { + *mock.Call +} + +// DeletePhoto is a helper method to define mock.On call +// - ctx context.Context +// - id string +func (_e *UsersUserProfilePhotoProvider_Expecter) DeletePhoto(ctx interface{}, id interface{}) *UsersUserProfilePhotoProvider_DeletePhoto_Call { + return &UsersUserProfilePhotoProvider_DeletePhoto_Call{Call: _e.mock.On("DeletePhoto", ctx, id)} +} + +func (_c *UsersUserProfilePhotoProvider_DeletePhoto_Call) Run(run func(ctx context.Context, id string)) *UsersUserProfilePhotoProvider_DeletePhoto_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *UsersUserProfilePhotoProvider_DeletePhoto_Call) Return(_a0 error) *UsersUserProfilePhotoProvider_DeletePhoto_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *UsersUserProfilePhotoProvider_DeletePhoto_Call) RunAndReturn(run func(context.Context, string) error) *UsersUserProfilePhotoProvider_DeletePhoto_Call { + _c.Call.Return(run) + return _c +} + +// GetPhoto provides a mock function with given fields: ctx, id +func (_m *UsersUserProfilePhotoProvider) GetPhoto(ctx context.Context, id string) ([]byte, error) { + ret := _m.Called(ctx, id) + + if len(ret) == 0 { + panic("no return value specified for GetPhoto") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) ([]byte, error)); ok { + return rf(ctx, id) + } + if rf, ok := ret.Get(0).(func(context.Context, string) []byte); ok { + r0 = rf(ctx, id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UsersUserProfilePhotoProvider_GetPhoto_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPhoto' +type UsersUserProfilePhotoProvider_GetPhoto_Call struct { + *mock.Call +} + +// GetPhoto is a helper method to define mock.On call +// - ctx context.Context +// - id string +func (_e *UsersUserProfilePhotoProvider_Expecter) GetPhoto(ctx interface{}, id interface{}) *UsersUserProfilePhotoProvider_GetPhoto_Call { + return &UsersUserProfilePhotoProvider_GetPhoto_Call{Call: _e.mock.On("GetPhoto", ctx, id)} +} + +func (_c *UsersUserProfilePhotoProvider_GetPhoto_Call) Run(run func(ctx context.Context, id string)) *UsersUserProfilePhotoProvider_GetPhoto_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *UsersUserProfilePhotoProvider_GetPhoto_Call) Return(_a0 []byte, _a1 error) *UsersUserProfilePhotoProvider_GetPhoto_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *UsersUserProfilePhotoProvider_GetPhoto_Call) RunAndReturn(run func(context.Context, string) ([]byte, error)) *UsersUserProfilePhotoProvider_GetPhoto_Call { + _c.Call.Return(run) + return _c +} + +// UpdatePhoto provides a mock function with given fields: ctx, id, rc +func (_m *UsersUserProfilePhotoProvider) UpdatePhoto(ctx context.Context, id string, rc io.Reader) error { + ret := _m.Called(ctx, id, rc) + + if len(ret) == 0 { + panic("no return value specified for UpdatePhoto") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, io.Reader) error); ok { + r0 = rf(ctx, id, rc) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UsersUserProfilePhotoProvider_UpdatePhoto_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdatePhoto' +type UsersUserProfilePhotoProvider_UpdatePhoto_Call struct { + *mock.Call +} + +// UpdatePhoto is a helper method to define mock.On call +// - ctx context.Context +// - id string +// - rc io.Reader +func (_e *UsersUserProfilePhotoProvider_Expecter) UpdatePhoto(ctx interface{}, id interface{}, rc interface{}) *UsersUserProfilePhotoProvider_UpdatePhoto_Call { + return &UsersUserProfilePhotoProvider_UpdatePhoto_Call{Call: _e.mock.On("UpdatePhoto", ctx, id, rc)} +} + +func (_c *UsersUserProfilePhotoProvider_UpdatePhoto_Call) Run(run func(ctx context.Context, id string, rc io.Reader)) *UsersUserProfilePhotoProvider_UpdatePhoto_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(io.Reader)) + }) + return _c +} + +func (_c *UsersUserProfilePhotoProvider_UpdatePhoto_Call) Return(_a0 error) *UsersUserProfilePhotoProvider_UpdatePhoto_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *UsersUserProfilePhotoProvider_UpdatePhoto_Call) RunAndReturn(run func(context.Context, string, io.Reader) error) *UsersUserProfilePhotoProvider_UpdatePhoto_Call { + _c.Call.Return(run) + return _c +} + +// NewUsersUserProfilePhotoProvider creates a new instance of UsersUserProfilePhotoProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewUsersUserProfilePhotoProvider(t interface { + mock.TestingT + Cleanup(func()) +}) *UsersUserProfilePhotoProvider { + mock := &UsersUserProfilePhotoProvider{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/services/graph/pkg/service/v0/api_users_user_profile_photo.go b/services/graph/pkg/service/v0/api_users_user_profile_photo.go index 3f4b233c8..3cd43c1e1 100644 --- a/services/graph/pkg/service/v0/api_users_user_profile_photo.go +++ b/services/graph/pkg/service/v0/api_users_user_profile_photo.go @@ -7,7 +7,6 @@ import ( "net/http" "github.com/go-chi/render" - revactx "github.com/opencloud-eu/reva/v2/pkg/ctx" "github.com/opencloud-eu/reva/v2/pkg/storage/utils/metadata" "github.com/opencloud-eu/opencloud/pkg/log" @@ -21,7 +20,7 @@ type ( GetPhoto(ctx context.Context, id string) ([]byte, error) // UpdatePhoto retrieves the requested photo - UpdatePhoto(ctx context.Context, id string, rc io.Reader) error + UpdatePhoto(ctx context.Context, id string, r io.Reader) error // DeletePhoto deletes the requested photo DeletePhoto(ctx context.Context, id string) error @@ -34,9 +33,6 @@ var ( // ErrNoBytes is returned when no bytes are found ErrNoBytes = errors.New("no bytes") - - // ErrNoUser is returned when no user is found - ErrNoUser = errors.New("no user found") ) // UsersUserProfilePhotoService is the implementation of the UsersUserProfilePhotoProvider interface @@ -71,8 +67,8 @@ func (s UsersUserProfilePhotoService) DeletePhoto(ctx context.Context, id string } // UpdatePhoto updates the requested photo -func (s UsersUserProfilePhotoService) UpdatePhoto(ctx context.Context, id string, rc io.Reader) error { - photo, err := io.ReadAll(rc) +func (s UsersUserProfilePhotoService) UpdatePhoto(ctx context.Context, id string, r io.Reader) error { + photo, err := io.ReadAll(r) if err != nil { return err } @@ -98,66 +94,61 @@ func NewUsersUserProfilePhotoApi(usersUserProfilePhotoService UsersUserProfilePh }, nil } -// GetProfilePhoto provides the requested photo -func (api UsersUserProfilePhotoApi) GetProfilePhoto(w http.ResponseWriter, r *http.Request) { - id, ok := api.getUserID(w, r) - if !ok { - return - } +// GetProfilePhoto creates a handler which renders the corresponding photo +func (api UsersUserProfilePhotoApi) GetProfilePhoto(h HTTPDataHandler[string]) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + v, ok := h(w, r) + if !ok { + return + } - photo, err := api.usersUserProfilePhotoService.GetPhoto(r.Context(), id) - if err != nil { - api.logger.Debug().Err(err) - errorcode.GeneralException.Render(w, r, http.StatusNotFound, "failed to get photo") - return - } + photo, err := api.usersUserProfilePhotoService.GetPhoto(r.Context(), v) + if err != nil { + api.logger.Debug().Err(err) + errorcode.GeneralException.Render(w, r, http.StatusNotFound, "failed to get photo") + return + } - render.Status(r, http.StatusOK) - _, _ = w.Write(photo) + render.Status(r, http.StatusOK) + _, _ = w.Write(photo) + } } -// UpsertProfilePhoto updates or inserts (initial create) the requested photo -func (api UsersUserProfilePhotoApi) UpsertProfilePhoto(w http.ResponseWriter, r *http.Request) { - id, ok := api.getUserID(w, r) - if !ok { - return - } +// UpsertProfilePhoto creates a handler which updates or creates the corresponding photo +func (api UsersUserProfilePhotoApi) UpsertProfilePhoto(h HTTPDataHandler[string]) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + v, ok := h(w, r) + if !ok { + return + } - if err := api.usersUserProfilePhotoService.UpdatePhoto(r.Context(), id, r.Body); err != nil { - api.logger.Debug().Err(err) - errorcode.GeneralException.Render(w, r, http.StatusNotFound, "failed to update photo") - return - } - defer func() { - _ = r.Body.Close() - }() + if err := api.usersUserProfilePhotoService.UpdatePhoto(r.Context(), v, r.Body); err != nil { + api.logger.Debug().Err(err) + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "failed to update photo") + return + } + defer func() { + _ = r.Body.Close() + }() - render.Status(r, http.StatusOK) + render.Status(r, http.StatusOK) + } } -// DeleteProfilePhoto deletes the requested photo -func (api UsersUserProfilePhotoApi) DeleteProfilePhoto(w http.ResponseWriter, r *http.Request) { - id, ok := api.getUserID(w, r) - if !ok { - return - } +// DeleteProfilePhoto creates a handler which deletes the corresponding photo +func (api UsersUserProfilePhotoApi) DeleteProfilePhoto(h HTTPDataHandler[string]) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + v, ok := h(w, r) + if !ok { + return + } - if err := api.usersUserProfilePhotoService.DeletePhoto(r.Context(), id); err != nil { - api.logger.Debug().Err(err) - errorcode.GeneralException.Render(w, r, http.StatusNotFound, "failed to delete photo") - return - } + if err := api.usersUserProfilePhotoService.DeletePhoto(r.Context(), v); err != nil { + api.logger.Debug().Err(err) + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "failed to delete photo") + return + } - render.Status(r, http.StatusOK) -} - -func (api UsersUserProfilePhotoApi) getUserID(w http.ResponseWriter, r *http.Request) (string, bool) { - u, ok := revactx.ContextGetUser(r.Context()) - if !ok { - api.logger.Debug().Msg(ErrNoUser.Error()) - errorcode.GeneralException.Render(w, r, http.StatusMethodNotAllowed, ErrNoUser.Error()) - return "", false - } - - return u.GetId().GetOpaqueId(), true + render.Status(r, http.StatusOK) + } } diff --git a/services/graph/pkg/service/v0/api_users_user_profile_photo_test.go b/services/graph/pkg/service/v0/api_users_user_profile_photo_test.go index a51c3b2b5..cf38bcddf 100644 --- a/services/graph/pkg/service/v0/api_users_user_profile_photo_test.go +++ b/services/graph/pkg/service/v0/api_users_user_profile_photo_test.go @@ -1,13 +1,118 @@ package svc_test import ( + "context" + "errors" + "io" + "net/http" + "net/http/httptest" + "strings" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + "github.com/opencloud-eu/opencloud/pkg/log" + "github.com/opencloud-eu/opencloud/services/graph/mocks" + svc "github.com/opencloud-eu/opencloud/services/graph/pkg/service/v0" ) -func TestNewUsersUserProfilePhotoApi(t *testing.T) { - panic("add UsersUserProfilePhotoApi tests") -} +func TestUsersUserProfilePhotoApi(t *testing.T) { + var ( + usersUserProfilePhotoProvider = mocks.NewUsersUserProfilePhotoProvider(t) + dummyDataProvider = func(w http.ResponseWriter, r *http.Request) (string, bool) { + return "123", true + } + ) -func TestNewUsersUserProfilePhotoService(t *testing.T) { - panic("add UsersUserProfilePhotoService tests") + api, err := svc.NewUsersUserProfilePhotoApi(usersUserProfilePhotoProvider, log.NopLogger()) + assert.NoError(t, err) + + t.Run("GetProfilePhoto", func(t *testing.T) { + r := httptest.NewRequest(http.MethodGet, "/", nil) + ep := api.GetProfilePhoto(dummyDataProvider) + + t.Run("fails if photo provider errors", func(t *testing.T) { + w := httptest.NewRecorder() + + usersUserProfilePhotoProvider.EXPECT().GetPhoto(mock.Anything, mock.Anything).RunAndReturn(func(ctx context.Context, s string) ([]byte, error) { + return nil, errors.New("any") + }).Once() + + ep.ServeHTTP(w, r) + + assert.Equal(t, http.StatusNotFound, w.Code) + }) + + t.Run("successfully returns the requested photo", func(t *testing.T) { + w := httptest.NewRecorder() + + usersUserProfilePhotoProvider.EXPECT().GetPhoto(mock.Anything, mock.Anything).RunAndReturn(func(ctx context.Context, s string) ([]byte, error) { + return []byte("photo"), nil + }).Once() + + ep.ServeHTTP(w, r) + + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "photo", w.Body.String()) + }) + }) + + t.Run("DeleteProfilePhoto", func(t *testing.T) { + r := httptest.NewRequest(http.MethodDelete, "/", nil) + ep := api.DeleteProfilePhoto(dummyDataProvider) + + t.Run("fails if photo provider errors", func(t *testing.T) { + w := httptest.NewRecorder() + + usersUserProfilePhotoProvider.EXPECT().DeletePhoto(mock.Anything, mock.Anything).RunAndReturn(func(ctx context.Context, s string) error { + return errors.New("any") + }).Once() + + ep.ServeHTTP(w, r) + + assert.Equal(t, http.StatusInternalServerError, w.Code) + }) + + t.Run("successfully deletes the requested photo", func(t *testing.T) { + w := httptest.NewRecorder() + + usersUserProfilePhotoProvider.EXPECT().DeletePhoto(mock.Anything, mock.Anything).RunAndReturn(func(ctx context.Context, s string) error { + return nil + }).Once() + + ep.ServeHTTP(w, r) + + assert.Equal(t, http.StatusOK, w.Code) + }) + }) + + t.Run("UpsertProfilePhoto", func(t *testing.T) { + r := httptest.NewRequest(http.MethodPut, "/", strings.NewReader("body")) + ep := api.UpsertProfilePhoto(dummyDataProvider) + + t.Run("fails if photo provider errors", func(t *testing.T) { + w := httptest.NewRecorder() + + usersUserProfilePhotoProvider.EXPECT().UpdatePhoto(mock.Anything, mock.Anything, mock.Anything).RunAndReturn(func(ctx context.Context, s string, r io.Reader) error { + return errors.New("any") + }).Once() + + ep.ServeHTTP(w, r) + + assert.Equal(t, http.StatusInternalServerError, w.Code) + }) + + t.Run("successfully upserts the photo", func(t *testing.T) { + w := httptest.NewRecorder() + + usersUserProfilePhotoProvider.EXPECT().UpdatePhoto(mock.Anything, mock.Anything, mock.Anything).RunAndReturn(func(ctx context.Context, s string, r io.Reader) error { + return nil + }).Once() + + ep.ServeHTTP(w, r) + + assert.Equal(t, http.StatusOK, w.Code) + }) + }) } diff --git a/services/graph/pkg/service/v0/data.go b/services/graph/pkg/service/v0/data.go new file mode 100644 index 000000000..5738183f4 --- /dev/null +++ b/services/graph/pkg/service/v0/data.go @@ -0,0 +1,42 @@ +package svc + +import ( + "errors" + "fmt" + "net/http" + "net/url" + + "github.com/go-chi/chi/v5" + revactx "github.com/opencloud-eu/reva/v2/pkg/ctx" + + "github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode" +) + +// HTTPDataHandler returns data from the request, it should exit early and return false in the case of any error +type HTTPDataHandler[T any] func(w http.ResponseWriter, r *http.Request) (T, bool) + +var ( + // ErrNoUser is returned when no user is found + ErrNoUser = errors.New("no user found") +) + +// GetUserIDFromCTX extracts the user from the request +func GetUserIDFromCTX(w http.ResponseWriter, r *http.Request) (string, bool) { + u, ok := revactx.ContextGetUser(r.Context()) + if !ok { + errorcode.GeneralException.Render(w, r, http.StatusMethodNotAllowed, ErrNoUser.Error()) + } + + return u.GetId().GetOpaqueId(), ok +} + +func GetSlugValue(key string) HTTPDataHandler[string] { + return func(w http.ResponseWriter, r *http.Request) (string, bool) { + v, err := url.PathUnescape(chi.URLParam(r, key)) + if err != nil { + errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, fmt.Sprintf(`failed to get slug: "%s"`, key)) + } + + return v, err == nil + } +} diff --git a/services/graph/pkg/service/v0/service.go b/services/graph/pkg/service/v0/service.go index 919f07aa6..7236c98b9 100644 --- a/services/graph/pkg/service/v0/service.go +++ b/services/graph/pkg/service/v0/service.go @@ -144,14 +144,57 @@ func NewService(opts ...Option) (Graph, error) { //nolint:maintidx identity.IdentityCacheWithGroupsTTL(time.Duration(options.Config.Spaces.GroupsCacheTTL)), ) + storage, err := metadata.NewCS3Storage( + options.Config.Metadata.GatewayAddress, + options.Config.Metadata.StorageAddress, + options.Config.Metadata.SystemUserID, + options.Config.Metadata.SystemUserIDP, + options.Config.Metadata.SystemUserAPIKey, + ) + if err != nil { + return Graph{}, err + } + + baseGraphService := BaseGraphService{ + logger: &options.Logger, + identityCache: identityCache, + gatewaySelector: options.GatewaySelector, + config: options.Config, + availableRoles: unifiedrole.GetRoles(unifiedrole.RoleFilterIDs(options.Config.UnifiedRoles.AvailableRoles...)), + } + + drivesDriveItemService, err := NewDrivesDriveItemService(options.Logger, options.GatewaySelector) + if err != nil { + return Graph{}, err + } + + drivesDriveItemApi, err := NewDrivesDriveItemApi(drivesDriveItemService, baseGraphService, options.Logger) + if err != nil { + return Graph{}, err + } + + driveItemPermissionsService, err := NewDriveItemPermissionsService(options.Logger, options.GatewaySelector, identityCache, options.Config) + if err != nil { + return Graph{}, err + } + + driveItemPermissionsApi, err := NewDriveItemPermissionsApi(driveItemPermissionsService, options.Logger, options.Config) + if err != nil { + return Graph{}, err + } + + usersUserProfilePhotoService, err := NewUsersUserProfilePhotoService(storage) + if err != nil { + return Graph{}, err + } + + usersUserProfilePhotoApi, err := NewUsersUserProfilePhotoApi(usersUserProfilePhotoService, options.Logger) + if err != nil { + return Graph{}, err + } + svc := Graph{ - BaseGraphService: BaseGraphService{ - logger: &options.Logger, - identityCache: identityCache, - gatewaySelector: options.GatewaySelector, - config: options.Config, - availableRoles: unifiedrole.GetRoles(unifiedrole.RoleFilterIDs(options.Config.UnifiedRoles.AvailableRoles...)), - }, + BaseGraphService: baseGraphService, mux: m, specialDriveItemsCache: spacePropertiesCache, eventsPublisher: options.EventsPublisher, @@ -206,47 +249,6 @@ func NewService(opts ...Option) (Graph, error) { //nolint:maintidx requireAdmin = options.RequireAdminMiddleware } - drivesDriveItemService, err := NewDrivesDriveItemService(options.Logger, options.GatewaySelector) - if err != nil { - return svc, err - } - - drivesDriveItemApi, err := NewDrivesDriveItemApi(drivesDriveItemService, svc.BaseGraphService, options.Logger) - if err != nil { - return svc, err - } - - driveItemPermissionsService, err := NewDriveItemPermissionsService(options.Logger, options.GatewaySelector, identityCache, options.Config) - if err != nil { - return svc, err - } - - driveItemPermissionsApi, err := NewDriveItemPermissionsApi(driveItemPermissionsService, options.Logger, options.Config) - if err != nil { - return svc, err - } - - storage, err := metadata.NewCS3Storage( - options.Config.Metadata.GatewayAddress, - options.Config.Metadata.StorageAddress, - options.Config.Metadata.SystemUserID, - options.Config.Metadata.SystemUserIDP, - options.Config.Metadata.SystemUserAPIKey, - ) - if err != nil { - return svc, err - } - - usersUserProfilePhotoService, err := NewUsersUserProfilePhotoService(storage) - if err != nil { - return svc, err - } - - usersUserProfilePhotoApi, err := NewUsersUserProfilePhotoApi(usersUserProfilePhotoService, options.Logger) - if err != nil { - return svc, err - } - m.Route(options.Config.HTTP.Root, func(r chi.Router) { r.Use(middleware.StripSlashes) @@ -315,11 +317,11 @@ func NewService(opts ...Option) (Graph, error) { //nolint:maintidx }) r.Get("/drives", svc.GetDrives(APIVersion_1)) r.Post("/changePassword", svc.ChangeOwnPassword) - r.Route("/photo", func(r chi.Router) { - r.Get("/", usersUserProfilePhotoApi.GetProfilePhoto) - r.Put("/", usersUserProfilePhotoApi.UpsertProfilePhoto) - r.Patch("/", usersUserProfilePhotoApi.UpsertProfilePhoto) - r.Delete("/", usersUserProfilePhotoApi.DeleteProfilePhoto) + r.Route("/photo/$value", func(r chi.Router) { + r.Get("/", usersUserProfilePhotoApi.GetProfilePhoto(GetUserIDFromCTX)) + r.Put("/", usersUserProfilePhotoApi.UpsertProfilePhoto(GetUserIDFromCTX)) + r.Patch("/", usersUserProfilePhotoApi.UpsertProfilePhoto(GetUserIDFromCTX)) + r.Delete("/", usersUserProfilePhotoApi.DeleteProfilePhoto(GetUserIDFromCTX)) }) }) r.Route("/users", func(r chi.Router) { @@ -329,6 +331,9 @@ func NewService(opts ...Option) (Graph, error) { //nolint:maintidx r.Get("/", svc.GetUser) r.Get("/drive", svc.GetUserDrive) r.Post("/exportPersonalData", svc.ExportPersonalData) + r.Route("/photo/$value", func(r chi.Router) { + r.Get("/", usersUserProfilePhotoApi.GetProfilePhoto(GetSlugValue("userID"))) + }) r.With(requireAdmin).Delete("/", svc.DeleteUser) r.With(requireAdmin).Patch("/", svc.PatchUser) if svc.roleService != nil {