diff --git a/controller/post_test.go b/controller/post_test.go index efcebe0..593dc3e 100644 --- a/controller/post_test.go +++ b/controller/post_test.go @@ -58,7 +58,7 @@ func TestPostController_GetAll(t *testing.T) { `, }, { - name: "1つも投稿が存在しないならErrUserNotFound", + name: "1つも投稿が存在しないならErrNewErrorNotFound", prepareMockPost: func(ctx context.Context, post *mock.MockPost) { post.EXPECT().GetAll(ctx).Return(nil, entity.NewErrorNotFound("post")) }, @@ -134,7 +134,7 @@ func TestPostController_Get(t *testing.T) { wantCode: http.StatusOK, }, { - name: "存在しない投稿IDならErrUserNotFound", + name: "存在しない投稿IDならErrNewErrorNotFound", postID: "0", prepareMockPost: func(ctx context.Context, post *mock.MockPost) { post.EXPECT().FindByID(ctx, 0).Return(&entity.Post{ diff --git a/controller/user.go b/controller/user.go index a23cdcc..ca7dfa1 100644 --- a/controller/user.go +++ b/controller/user.go @@ -159,3 +159,34 @@ func (ctrl *UserController) Update(c echo.Context) error { return c.NoContent(http.StatusOK) } + +// Delete は DELETE /user/{userID} のHandler +func (ctrl *UserController) Delete(c echo.Context) error { + logger := log.New() + + userID, ok := c.Get("userID").(string) + if !ok { + logger.Errorf("Failed type assertion of userID: %#v", c.Get("userID")) + return echo.NewHTTPError(http.StatusInternalServerError) + } + + if err := ctrl.uc.Delete( + c.Request().Context(), + entity.NewUser(userID, "", "", "", ""), + ); err != nil { + errEmpty := &entity.ErrEmpty{} + if errors.As(err, errEmpty) { + return echo.NewHTTPError(http.StatusBadRequest, errEmpty.Error()) + } + errNF := &entity.ErrNotFound{} + if errors.As(err, errNF) { + return echo.NewHTTPError(http.StatusNotFound, errNF.Error()) + } + if errors.Is(err, entity.ErrIsNotAuthor) { + return echo.NewHTTPError(http.StatusForbidden, err.Error()) + } + logger.Errorf("Unexpected error PUT/user: %s", err.Error()) + return echo.NewHTTPError(http.StatusInternalServerError) + } + return c.NoContent(http.StatusOK) +} diff --git a/go.mod b/go.mod index d48ef6d..fe3419e 100644 --- a/go.mod +++ b/go.mod @@ -29,5 +29,6 @@ require ( google.golang.org/api v0.43.0 google.golang.org/genproto v0.0.0-20210324141432-3032e8ff099e // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/gorp.v1 v1.7.2 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect ) diff --git a/go.sum b/go.sum index a089f8e..cf05b71 100644 --- a/go.sum +++ b/go.sum @@ -647,6 +647,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw= +gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/infra/auth.go b/infra/auth.go index cef1661..acf8a79 100644 --- a/infra/auth.go +++ b/infra/auth.go @@ -5,6 +5,7 @@ import ( "fmt" "firebase.google.com/go/auth" + "github.com/openhacku-saboten/OmnisCode-backend/domain/entity" ) // AuthRepository は認証情報の永続化と再構成のためのリポジトリです @@ -36,3 +37,11 @@ func (a *AuthRepository) GetIconURL(ctx context.Context, uid string) (iconURL st iconURL = user.PhotoURL return } + +// Delete はuserIDからuserを削除します +func (a *AuthRepository) Delete(ctx context.Context, user *entity.User) error { + if err := a.firebase.DeleteUser(ctx, user.ID); err != nil { + return fmt.Errorf("failed firebase.DeleteUser: %w", err) + } + return nil +} diff --git a/infra/mock/mock_auth.go b/infra/mock/mock_auth.go index 62f9e23..bc12303 100644 --- a/infra/mock/mock_auth.go +++ b/infra/mock/mock_auth.go @@ -1,65 +1,72 @@ // Code generated by MockGen. DO NOT EDIT. // Source: auth.go -// Package mock is a generated GoMock package. package mock import ( context "context" - reflect "reflect" - gomock "github.com/golang/mock/gomock" + entity "github.com/openhacku-saboten/OmnisCode-backend/domain/entity" + reflect "reflect" ) -// MockAuth is a mock of Auth interface. +// MockAuth is a mock of Auth interface type MockAuth struct { ctrl *gomock.Controller recorder *MockAuthMockRecorder } -// MockAuthMockRecorder is the mock recorder for MockAuth. +// MockAuthMockRecorder is the mock recorder for MockAuth type MockAuthMockRecorder struct { mock *MockAuth } -// NewMockAuth creates a new mock instance. +// NewMockAuth creates a new mock instance func NewMockAuth(ctrl *gomock.Controller) *MockAuth { mock := &MockAuth{ctrl: ctrl} mock.recorder = &MockAuthMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockAuth) EXPECT() *MockAuthMockRecorder { - return m.recorder +// EXPECT returns an object that allows the caller to indicate expected use +func (_m *MockAuth) EXPECT() *MockAuthMockRecorder { + return _m.recorder } -// Authenticate mocks base method. -func (m *MockAuth) Authenticate(ctx context.Context, token string) (string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Authenticate", ctx, token) +// Authenticate mocks base method +func (_m *MockAuth) Authenticate(ctx context.Context, token string) (string, error) { + ret := _m.ctrl.Call(_m, "Authenticate", ctx, token) ret0, _ := ret[0].(string) ret1, _ := ret[1].(error) return ret0, ret1 } -// Authenticate indicates an expected call of Authenticate. -func (mr *MockAuthMockRecorder) Authenticate(ctx, token interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Authenticate", reflect.TypeOf((*MockAuth)(nil).Authenticate), ctx, token) +// Authenticate indicates an expected call of Authenticate +func (_mr *MockAuthMockRecorder) Authenticate(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "Authenticate", reflect.TypeOf((*MockAuth)(nil).Authenticate), arg0, arg1) } -// GetIconURL mocks base method. -func (m *MockAuth) GetIconURL(ctx context.Context, uid string) (string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetIconURL", ctx, uid) +// GetIconURL mocks base method +func (_m *MockAuth) GetIconURL(ctx context.Context, uid string) (string, error) { + ret := _m.ctrl.Call(_m, "GetIconURL", ctx, uid) ret0, _ := ret[0].(string) ret1, _ := ret[1].(error) return ret0, ret1 } -// GetIconURL indicates an expected call of GetIconURL. -func (mr *MockAuthMockRecorder) GetIconURL(ctx, uid interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIconURL", reflect.TypeOf((*MockAuth)(nil).GetIconURL), ctx, uid) +// GetIconURL indicates an expected call of GetIconURL +func (_mr *MockAuthMockRecorder) GetIconURL(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "GetIconURL", reflect.TypeOf((*MockAuth)(nil).GetIconURL), arg0, arg1) +} + +// Delete mocks base method +func (_m *MockAuth) Delete(ctx context.Context, user *entity.User) error { + ret := _m.ctrl.Call(_m, "Delete", ctx, user) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete +func (_mr *MockAuthMockRecorder) Delete(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "Delete", reflect.TypeOf((*MockAuth)(nil).Delete), arg0, arg1) } diff --git a/infra/mock/mock_comment.go b/infra/mock/mock_comment.go index 47998c9..0faeb1e 100644 --- a/infra/mock/mock_comment.go +++ b/infra/mock/mock_comment.go @@ -1,123 +1,109 @@ // Code generated by MockGen. DO NOT EDIT. // Source: comment.go -// Package mock is a generated GoMock package. package mock import ( context "context" - reflect "reflect" - gomock "github.com/golang/mock/gomock" entity "github.com/openhacku-saboten/OmnisCode-backend/domain/entity" + reflect "reflect" ) -// MockComment is a mock of Comment interface. +// MockComment is a mock of Comment interface type MockComment struct { ctrl *gomock.Controller recorder *MockCommentMockRecorder } -// MockCommentMockRecorder is the mock recorder for MockComment. +// MockCommentMockRecorder is the mock recorder for MockComment type MockCommentMockRecorder struct { mock *MockComment } -// NewMockComment creates a new mock instance. +// NewMockComment creates a new mock instance func NewMockComment(ctrl *gomock.Controller) *MockComment { mock := &MockComment{ctrl: ctrl} mock.recorder = &MockCommentMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockComment) EXPECT() *MockCommentMockRecorder { - return m.recorder -} - -// Delete mocks base method. -func (m *MockComment) Delete(ctx context.Context, comment *entity.Comment) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Delete", ctx, comment) - ret0, _ := ret[0].(error) - return ret0 +// EXPECT returns an object that allows the caller to indicate expected use +func (_m *MockComment) EXPECT() *MockCommentMockRecorder { + return _m.recorder } -// Delete indicates an expected call of Delete. -func (mr *MockCommentMockRecorder) Delete(ctx, comment interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockComment)(nil).Delete), ctx, comment) -} - -// FindByID mocks base method. -func (m *MockComment) FindByID(ctx context.Context, postID, commentID int) (*entity.Comment, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FindByID", ctx, postID, commentID) +// FindByID mocks base method +func (_m *MockComment) FindByID(ctx context.Context, postID int, commentID int) (*entity.Comment, error) { + ret := _m.ctrl.Call(_m, "FindByID", ctx, postID, commentID) ret0, _ := ret[0].(*entity.Comment) ret1, _ := ret[1].(error) return ret0, ret1 } -// FindByID indicates an expected call of FindByID. -func (mr *MockCommentMockRecorder) FindByID(ctx, postID, commentID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindByID", reflect.TypeOf((*MockComment)(nil).FindByID), ctx, postID, commentID) +// FindByID indicates an expected call of FindByID +func (_mr *MockCommentMockRecorder) FindByID(arg0, arg1, arg2 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "FindByID", reflect.TypeOf((*MockComment)(nil).FindByID), arg0, arg1, arg2) } -// FindByPostID mocks base method. -func (m *MockComment) FindByPostID(ctx context.Context, postID int) ([]*entity.Comment, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FindByPostID", ctx, postID) +// FindByUserID mocks base method +func (_m *MockComment) FindByUserID(ctx context.Context, uid string) ([]*entity.Comment, error) { + ret := _m.ctrl.Call(_m, "FindByUserID", ctx, uid) ret0, _ := ret[0].([]*entity.Comment) ret1, _ := ret[1].(error) return ret0, ret1 } -// FindByPostID indicates an expected call of FindByPostID. -func (mr *MockCommentMockRecorder) FindByPostID(ctx, postID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindByPostID", reflect.TypeOf((*MockComment)(nil).FindByPostID), ctx, postID) +// FindByUserID indicates an expected call of FindByUserID +func (_mr *MockCommentMockRecorder) FindByUserID(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "FindByUserID", reflect.TypeOf((*MockComment)(nil).FindByUserID), arg0, arg1) } -// FindByUserID mocks base method. -func (m *MockComment) FindByUserID(ctx context.Context, uid string) ([]*entity.Comment, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FindByUserID", ctx, uid) +// FindByPostID mocks base method +func (_m *MockComment) FindByPostID(ctx context.Context, postID int) ([]*entity.Comment, error) { + ret := _m.ctrl.Call(_m, "FindByPostID", ctx, postID) ret0, _ := ret[0].([]*entity.Comment) ret1, _ := ret[1].(error) return ret0, ret1 } -// FindByUserID indicates an expected call of FindByUserID. -func (mr *MockCommentMockRecorder) FindByUserID(ctx, uid interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindByUserID", reflect.TypeOf((*MockComment)(nil).FindByUserID), ctx, uid) +// FindByPostID indicates an expected call of FindByPostID +func (_mr *MockCommentMockRecorder) FindByPostID(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "FindByPostID", reflect.TypeOf((*MockComment)(nil).FindByPostID), arg0, arg1) +} + +// Insert mocks base method +func (_m *MockComment) Insert(ctx context.Context, comment *entity.Comment) error { + ret := _m.ctrl.Call(_m, "Insert", ctx, comment) + ret0, _ := ret[0].(error) + return ret0 +} + +// Insert indicates an expected call of Insert +func (_mr *MockCommentMockRecorder) Insert(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "Insert", reflect.TypeOf((*MockComment)(nil).Insert), arg0, arg1) } -// Insert mocks base method. -func (m *MockComment) Insert(ctx context.Context, comment *entity.Comment) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Insert", ctx, comment) +// Update mocks base method +func (_m *MockComment) Update(ctx context.Context, comment *entity.Comment) error { + ret := _m.ctrl.Call(_m, "Update", ctx, comment) ret0, _ := ret[0].(error) return ret0 } -// Insert indicates an expected call of Insert. -func (mr *MockCommentMockRecorder) Insert(ctx, comment interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockComment)(nil).Insert), ctx, comment) +// Update indicates an expected call of Update +func (_mr *MockCommentMockRecorder) Update(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "Update", reflect.TypeOf((*MockComment)(nil).Update), arg0, arg1) } -// Update mocks base method. -func (m *MockComment) Update(ctx context.Context, comment *entity.Comment) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Update", ctx, comment) +// Delete mocks base method +func (_m *MockComment) Delete(ctx context.Context, comment *entity.Comment) error { + ret := _m.ctrl.Call(_m, "Delete", ctx, comment) ret0, _ := ret[0].(error) return ret0 } -// Update indicates an expected call of Update. -func (mr *MockCommentMockRecorder) Update(ctx, comment interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockComment)(nil).Update), ctx, comment) +// Delete indicates an expected call of Delete +func (_mr *MockCommentMockRecorder) Delete(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "Delete", reflect.TypeOf((*MockComment)(nil).Delete), arg0, arg1) } diff --git a/infra/mock/mock_post.go b/infra/mock/mock_post.go index bd4fe8f..24ade8e 100644 --- a/infra/mock/mock_post.go +++ b/infra/mock/mock_post.go @@ -1,123 +1,109 @@ // Code generated by MockGen. DO NOT EDIT. // Source: post.go -// Package mock is a generated GoMock package. package mock import ( context "context" - reflect "reflect" - gomock "github.com/golang/mock/gomock" entity "github.com/openhacku-saboten/OmnisCode-backend/domain/entity" + reflect "reflect" ) -// MockPost is a mock of Post interface. +// MockPost is a mock of Post interface type MockPost struct { ctrl *gomock.Controller recorder *MockPostMockRecorder } -// MockPostMockRecorder is the mock recorder for MockPost. +// MockPostMockRecorder is the mock recorder for MockPost type MockPostMockRecorder struct { mock *MockPost } -// NewMockPost creates a new mock instance. +// NewMockPost creates a new mock instance func NewMockPost(ctrl *gomock.Controller) *MockPost { mock := &MockPost{ctrl: ctrl} mock.recorder = &MockPostMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockPost) EXPECT() *MockPostMockRecorder { - return m.recorder +// EXPECT returns an object that allows the caller to indicate expected use +func (_m *MockPost) EXPECT() *MockPostMockRecorder { + return _m.recorder } -// Delete mocks base method. -func (m *MockPost) Delete(ctx context.Context, post *entity.Post) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Delete", ctx, post) - ret0, _ := ret[0].(error) - return ret0 +// GetAll mocks base method +func (_m *MockPost) GetAll(ctx context.Context) ([]*entity.Post, error) { + ret := _m.ctrl.Call(_m, "GetAll", ctx) + ret0, _ := ret[0].([]*entity.Post) + ret1, _ := ret[1].(error) + return ret0, ret1 } -// Delete indicates an expected call of Delete. -func (mr *MockPostMockRecorder) Delete(ctx, post interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockPost)(nil).Delete), ctx, post) +// GetAll indicates an expected call of GetAll +func (_mr *MockPostMockRecorder) GetAll(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "GetAll", reflect.TypeOf((*MockPost)(nil).GetAll), arg0) } -// FindByID mocks base method. -func (m *MockPost) FindByID(ctx context.Context, postID int) (*entity.Post, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FindByID", ctx, postID) +// FindByID mocks base method +func (_m *MockPost) FindByID(ctx context.Context, postID int) (*entity.Post, error) { + ret := _m.ctrl.Call(_m, "FindByID", ctx, postID) ret0, _ := ret[0].(*entity.Post) ret1, _ := ret[1].(error) return ret0, ret1 } -// FindByID indicates an expected call of FindByID. -func (mr *MockPostMockRecorder) FindByID(ctx, postID interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindByID", reflect.TypeOf((*MockPost)(nil).FindByID), ctx, postID) +// FindByID indicates an expected call of FindByID +func (_mr *MockPostMockRecorder) FindByID(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "FindByID", reflect.TypeOf((*MockPost)(nil).FindByID), arg0, arg1) } -// FindByUserID mocks base method. -func (m *MockPost) FindByUserID(ctx context.Context, uid string) ([]*entity.Post, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FindByUserID", ctx, uid) +// FindByUserID mocks base method +func (_m *MockPost) FindByUserID(ctx context.Context, uid string) ([]*entity.Post, error) { + ret := _m.ctrl.Call(_m, "FindByUserID", ctx, uid) ret0, _ := ret[0].([]*entity.Post) ret1, _ := ret[1].(error) return ret0, ret1 } -// FindByUserID indicates an expected call of FindByUserID. -func (mr *MockPostMockRecorder) FindByUserID(ctx, uid interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindByUserID", reflect.TypeOf((*MockPost)(nil).FindByUserID), ctx, uid) +// FindByUserID indicates an expected call of FindByUserID +func (_mr *MockPostMockRecorder) FindByUserID(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "FindByUserID", reflect.TypeOf((*MockPost)(nil).FindByUserID), arg0, arg1) } -// GetAll mocks base method. -func (m *MockPost) GetAll(ctx context.Context) ([]*entity.Post, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetAll", ctx) - ret0, _ := ret[0].([]*entity.Post) - ret1, _ := ret[1].(error) - return ret0, ret1 +// Insert mocks base method +func (_m *MockPost) Insert(ctx context.Context, post *entity.Post) error { + ret := _m.ctrl.Call(_m, "Insert", ctx, post) + ret0, _ := ret[0].(error) + return ret0 } -// GetAll indicates an expected call of GetAll. -func (mr *MockPostMockRecorder) GetAll(ctx interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAll", reflect.TypeOf((*MockPost)(nil).GetAll), ctx) +// Insert indicates an expected call of Insert +func (_mr *MockPostMockRecorder) Insert(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "Insert", reflect.TypeOf((*MockPost)(nil).Insert), arg0, arg1) } -// Insert mocks base method. -func (m *MockPost) Insert(ctx context.Context, post *entity.Post) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Insert", ctx, post) +// Update mocks base method +func (_m *MockPost) Update(ctx context.Context, post *entity.Post) error { + ret := _m.ctrl.Call(_m, "Update", ctx, post) ret0, _ := ret[0].(error) return ret0 } -// Insert indicates an expected call of Insert. -func (mr *MockPostMockRecorder) Insert(ctx, post interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockPost)(nil).Insert), ctx, post) +// Update indicates an expected call of Update +func (_mr *MockPostMockRecorder) Update(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "Update", reflect.TypeOf((*MockPost)(nil).Update), arg0, arg1) } -// Update mocks base method. -func (m *MockPost) Update(ctx context.Context, post *entity.Post) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Update", ctx, post) +// Delete mocks base method +func (_m *MockPost) Delete(ctx context.Context, post *entity.Post) error { + ret := _m.ctrl.Call(_m, "Delete", ctx, post) ret0, _ := ret[0].(error) return ret0 } -// Update indicates an expected call of Update. -func (mr *MockPostMockRecorder) Update(ctx, post interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockPost)(nil).Update), ctx, post) +// Delete indicates an expected call of Delete +func (_mr *MockPostMockRecorder) Delete(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "Delete", reflect.TypeOf((*MockPost)(nil).Delete), arg0, arg1) } diff --git a/infra/mock/mock_user.go b/infra/mock/mock_user.go index a6102e6..2bcad6c 100644 --- a/infra/mock/mock_user.go +++ b/infra/mock/mock_user.go @@ -1,79 +1,95 @@ // Code generated by MockGen. DO NOT EDIT. // Source: user.go -// Package mock is a generated GoMock package. package mock import ( context "context" - reflect "reflect" - gomock "github.com/golang/mock/gomock" entity "github.com/openhacku-saboten/OmnisCode-backend/domain/entity" + reflect "reflect" ) -// MockUser is a mock of User interface. +// MockUser is a mock of User interface type MockUser struct { ctrl *gomock.Controller recorder *MockUserMockRecorder } -// MockUserMockRecorder is the mock recorder for MockUser. +// MockUserMockRecorder is the mock recorder for MockUser type MockUserMockRecorder struct { mock *MockUser } -// NewMockUser creates a new mock instance. +// NewMockUser creates a new mock instance func NewMockUser(ctrl *gomock.Controller) *MockUser { mock := &MockUser{ctrl: ctrl} mock.recorder = &MockUserMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockUser) EXPECT() *MockUserMockRecorder { - return m.recorder +// EXPECT returns an object that allows the caller to indicate expected use +func (_m *MockUser) EXPECT() *MockUserMockRecorder { + return _m.recorder } -// FindByID mocks base method. -func (m *MockUser) FindByID(ctx context.Context, uid string) (*entity.User, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "FindByID", ctx, uid) +// FindByID mocks base method +func (_m *MockUser) FindByID(ctx context.Context, uid string) (*entity.User, error) { + ret := _m.ctrl.Call(_m, "FindByID", ctx, uid) ret0, _ := ret[0].(*entity.User) ret1, _ := ret[1].(error) return ret0, ret1 } -// FindByID indicates an expected call of FindByID. -func (mr *MockUserMockRecorder) FindByID(ctx, uid interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindByID", reflect.TypeOf((*MockUser)(nil).FindByID), ctx, uid) +// FindByID indicates an expected call of FindByID +func (_mr *MockUserMockRecorder) FindByID(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "FindByID", reflect.TypeOf((*MockUser)(nil).FindByID), arg0, arg1) +} + +// Insert mocks base method +func (_m *MockUser) Insert(ctx context.Context, user *entity.User) error { + ret := _m.ctrl.Call(_m, "Insert", ctx, user) + ret0, _ := ret[0].(error) + return ret0 +} + +// Insert indicates an expected call of Insert +func (_mr *MockUserMockRecorder) Insert(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "Insert", reflect.TypeOf((*MockUser)(nil).Insert), arg0, arg1) +} + +// Update mocks base method +func (_m *MockUser) Update(ctx context.Context, user *entity.User) error { + ret := _m.ctrl.Call(_m, "Update", ctx, user) + ret0, _ := ret[0].(error) + return ret0 +} + +// Update indicates an expected call of Update +func (_mr *MockUserMockRecorder) Update(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "Update", reflect.TypeOf((*MockUser)(nil).Update), arg0, arg1) } -// Insert mocks base method. -func (m *MockUser) Insert(ctx context.Context, user *entity.User) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Insert", ctx, user) +// Delete mocks base method +func (_m *MockUser) Delete(ctx context.Context, user *entity.User) error { + ret := _m.ctrl.Call(_m, "Delete", ctx, user) ret0, _ := ret[0].(error) return ret0 } -// Insert indicates an expected call of Insert. -func (mr *MockUserMockRecorder) Insert(ctx, user interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockUser)(nil).Insert), ctx, user) +// Delete indicates an expected call of Delete +func (_mr *MockUserMockRecorder) Delete(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "Delete", reflect.TypeOf((*MockUser)(nil).Delete), arg0, arg1) } -// Update mocks base method. -func (m *MockUser) Update(ctx context.Context, user *entity.User) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Update", ctx, user) +// DoInTx mocks base method +func (_m *MockUser) DoInTx(ctx context.Context, f func(context.Context) error) error { + ret := _m.ctrl.Call(_m, "DoInTx", ctx, f) ret0, _ := ret[0].(error) return ret0 } -// Update indicates an expected call of Update. -func (mr *MockUserMockRecorder) Update(ctx, user interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockUser)(nil).Update), ctx, user) +// DoInTx indicates an expected call of DoInTx +func (_mr *MockUserMockRecorder) DoInTx(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCallWithMethodType(_mr.mock, "DoInTx", reflect.TypeOf((*MockUser)(nil).DoInTx), arg0, arg1) } diff --git a/infra/transaction.go b/infra/transaction.go new file mode 100644 index 0000000..7b0b04d --- /dev/null +++ b/infra/transaction.go @@ -0,0 +1,24 @@ +package infra + +import ( + "context" + + "github.com/go-gorp/gorp" +) + +// ref: https://qiita.com/miya-masa/items/316256924a1f0d7374bb +var txKey = struct{}{} + +// TransactionDAO はTransactionに関するDataAccessObjectです +// gorp.dbMapで利用するメソッドをここにインタフェースとして定義することで、 +// infraではgorpに依存しないような設計となっています +type TransactionDAO interface { + // ref: https://pkg.go.dev/github.com/go-gorp/gorp#DbMap.Delete + Delete(list ...interface{}) (int64, error) +} + +// getTx はcontextからトランザクションを取得する +func getTx(ctx context.Context) (TransactionDAO, bool) { + tx, ok := ctx.Value(&txKey).(*gorp.Transaction) + return tx, ok +} diff --git a/infra/user.go b/infra/user.go index 49b7523..18ddd8a 100644 --- a/infra/user.go +++ b/infra/user.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "errors" + "fmt" "strings" "github.com/VividCortex/mysqlerr" @@ -36,7 +37,7 @@ func (r *UserRepository) FindByID(ctx context.Context, uid string) (user *entity err = r.dbMap.SelectOne(&userDTO, "SELECT * FROM users WHERE id = ?", uid) if err != nil { if errors.Is(err, sql.ErrNoRows) { - return nil, entity.ErrUserNotFound + return nil, entity.NewErrorNotFound("user") } return nil, err } @@ -81,7 +82,7 @@ func (r *UserRepository) Insert(ctx context.Context, user *entity.User) error { } } -// Update は該当ユーザーをDBに保存する +// Update は該当ユーザーのデータを更新するDBに保存する func (r *UserRepository) Update(ctx context.Context, user *entity.User) error { select { case <-ctx.Done(): @@ -107,6 +108,65 @@ func (r *UserRepository) Update(ctx context.Context, user *entity.User) error { } } +// Delete は該当ユーザIDを満たすユーザをDBから削除します +func (r *UserRepository) Delete(ctx context.Context, user *entity.User) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + // ユーザの存在確認は参照のみなのでトランザクションには入れない + // 該当ユーザの存在確認 + _, err := r.FindByID(ctx, user.ID) + if err != nil { + return entity.NewErrorNotFound("user") + } + + userDTO := &UserDTO{ + ID: user.ID, + } + + // トランザクションオブジェクトをctxから取得する + dao, ok := getTx(ctx) + if !ok { + // 見つからなかったら、dbMapをそのまま設定する + dao = r.dbMap + } + if _, err := dao.Delete(userDTO); err != nil { + return err + } + + return nil + } +} + +// DoInTx はUserRepository内でトランザクションの中でDBにアクセスするためのラッパー関数です +func (r *UserRepository) DoInTx(ctx context.Context, f func(ctx context.Context) error) error { + tx, err := r.dbMap.Begin() + if err != nil { + return fmt.Errorf("failed dbMap.Begin(): %w", err) + } + + // トランザクションをctxに埋め込む + ctx = context.WithValue(ctx, &txKey, tx) + // 中身の処理を実行する + err = f(ctx) + if err != nil { + if errRollback := tx.Rollback(); errRollback != nil { + return fmt.Errorf("failed rollback: %w", err) + } + return fmt.Errorf("rollbacked: %w", err) + } + + // コミット時に失敗してもロールバック + if err := tx.Commit(); err != nil { + if errRollback := tx.Rollback(); errRollback != nil { + return fmt.Errorf("failed rollback: %w", err) + } + return fmt.Errorf("failed to commit: rollbacked: %w", err) + } + return nil +} + // UserDTO はDBとやり取りするためのDataTransferObject type UserDTO struct { ID string `db:"id"` diff --git a/infra/user_test.go b/infra/user_test.go index 8e979b3..0cf597c 100644 --- a/infra/user_test.go +++ b/infra/user_test.go @@ -4,9 +4,7 @@ import ( "context" "errors" "testing" - "time" - "github.com/go-gorp/gorp" "github.com/google/go-cmp/cmp" "github.com/openhacku-saboten/OmnisCode-backend/domain/entity" ) @@ -45,7 +43,7 @@ func TestUserRepository_FindByID(t *testing.T) { name: "存在しないユーザーの場合はErrNoRows", userID: "not-existing-id", wantUser: nil, - wantErr: entity.ErrUserNotFound, + wantErr: entity.NewErrorNotFound("user"), }, } for _, tt := range tests { @@ -125,7 +123,7 @@ func TestUserRepository_Update(t *testing.T) { t.Fatalf(err.Error()) } dbMap.AddTableWithName(UserDTO{}, "users") - truncateUser(t, dbMap) + truncateTable(t, dbMap, "users") userDTOs := []*UserDTO{ { ID: "existing-id", @@ -187,25 +185,60 @@ func TestUserRepository_Update(t *testing.T) { } } -func truncateUser(t *testing.T, dbMap *gorp.DbMap) { - t.Helper() - - // databaseを初期化する - if _, err := dbMap.Exec("SET FOREIGN_KEY_CHECKS = 0"); err != nil { - t.Fatal(err) +func TestUserRepository_Delete(t *testing.T) { + dbMap, err := NewDB() + if err != nil { + t.Fatalf(err.Error()) } - // タイミングの問題でTruncateが失敗することがあるので成功するまで試みる - for i := 0; i < 5; i++ { - _, err := dbMap.Exec("TRUNCATE TABLE users") - if err == nil { - break - } - if i == 4 { + dbMap.AddTableWithName(UserDTO{}, "users") + truncateTable(t, dbMap, "users") + userDTOs := []*UserDTO{ + { + ID: "existing-id", + Name: "existingUser", + Profile: "existing", + TwitterID: "existing", + }, + { + ID: "existing-id2", + Name: "existingUser2", + Profile: "existing2", + TwitterID: "existing2", + }, + } + for _, userDTO := range userDTOs { + if err := dbMap.Insert(userDTO); err != nil { t.Fatal(err) } - time.Sleep(time.Second * 1) } - if _, err := dbMap.Exec("SET FOREIGN_KEY_CHECKS = 1"); err != nil { - t.Fatal(err) + + userRepo := NewUserRepository(dbMap) + + tests := []struct { + name string + user *entity.User + wantErr error + }{ + { + name: "ユーザーIDが存在しないならErrNotFound", + user: entity.NewUser("new-id", "", "", "", ""), + wantErr: entity.NewErrorNotFound("user"), + }, + { + name: "正しくユーザーを削除できる", + user: entity.NewUser("existing-id", "", "", "", ""), + wantErr: nil, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + err := userRepo.Delete(context.Background(), tt.user) + + if !errors.Is(err, tt.wantErr) { + t.Errorf("error = %v, wantErr = %v", err, tt.wantErr) + return + } + }) } } diff --git a/main.go b/main.go index 5a32559..3bbb2dc 100644 --- a/main.go +++ b/main.go @@ -62,6 +62,7 @@ func main() { user.GET("/:userID", userController.Get) user.POST("", userController.Create, authMiddleware.Authenticate) user.PUT("", userController.Update, authMiddleware.Authenticate) + user.DELETE("", userController.Delete, authMiddleware.Authenticate) user.GET("/:userID/post", userController.GetPosts) user.GET("/:userID/comment", userController.GetComments) diff --git a/repository/auth.go b/repository/auth.go index c4acfdb..e78b703 100644 --- a/repository/auth.go +++ b/repository/auth.go @@ -2,10 +2,15 @@ package repository -import "context" +import ( + "context" + + "github.com/openhacku-saboten/OmnisCode-backend/domain/entity" +) // Auth はFirebase Authentication関連の操作を表すインターフェース type Auth interface { Authenticate(ctx context.Context, token string) (uid string, err error) GetIconURL(ctx context.Context, uid string) (iconURL string, err error) + Delete(ctx context.Context, user *entity.User) error } diff --git a/repository/user.go b/repository/user.go index 965598c..1ffa0ec 100644 --- a/repository/user.go +++ b/repository/user.go @@ -13,4 +13,8 @@ type User interface { FindByID(ctx context.Context, uid string) (user *entity.User, err error) Insert(ctx context.Context, user *entity.User) error Update(ctx context.Context, user *entity.User) error + Delete(ctx context.Context, user *entity.User) error + // FindByIDのときは返り値を持ってしまうので、全体のinfraのrepositoryを管理する + // 親みたいなものがあるとよかったかもしれない + DoInTx(ctx context.Context, f func(ctx context.Context) error) error } diff --git a/usecase/user.go b/usecase/user.go index d2b71c7..dd4d8dd 100644 --- a/usecase/user.go +++ b/usecase/user.go @@ -88,3 +88,26 @@ func (u *UserUseCase) Update(ctx context.Context, user *entity.User) error { } return nil } + +// Delete は引数のuserエンティティをもとにユーザを削除します +func (u *UserUseCase) Delete(ctx context.Context, user *entity.User) error { + if err := u.userRepo.DoInTx(ctx, u.deleteFlow(ctx, user)); err != nil { + return fmt.Errorf("failed userRepo.DoInTx: %w", err) + } + return nil +} + +func (u *UserUseCase) deleteFlow(ctx context.Context, user *entity.User) func(context.Context) error { + return func(ctx context.Context) error { + // DBから削除 + if err := u.userRepo.Delete(ctx, user); err != nil { + return fmt.Errorf("failed Delete user in DB: %w", err) + } + + // Firebaseから削除 + if err := u.authRepo.Delete(ctx, user); err != nil { + return fmt.Errorf("failed Delete user in firebase: %w", err) + } + return nil + } +}