diff --git a/http/api.go b/http/api.go index dbc22f0..ca9f3c5 100644 --- a/http/api.go +++ b/http/api.go @@ -13,6 +13,7 @@ import ( "elyby/minecraft-skinsystem/interfaces" "elyby/minecraft-skinsystem/model" + "github.com/gorilla/mux" "github.com/mono83/slf/wd" "github.com/thedevsaddam/govalidator" ) @@ -80,6 +81,28 @@ func (cfg *Config) PostSkin(resp http.ResponseWriter, req *http.Request) { resp.WriteHeader(http.StatusCreated) } +func (cfg *Config) DeleteSkinByUserId(resp http.ResponseWriter, req *http.Request) { + id, _ := strconv.Atoi(mux.Vars(req)["id"]) + skin, err := cfg.SkinsRepo.FindByUserId(id) + if err != nil { + apiNotFound(resp, "Cannot find record for requested user id") + return + } + + cfg.deleteSkin(skin, resp) +} + +func (cfg *Config) DeleteSkinByUsername(resp http.ResponseWriter, req *http.Request) { + username := mux.Vars(req)["username"] + skin, err := cfg.SkinsRepo.FindByUsername(username) + if err != nil { + apiNotFound(resp, "Cannot find record for requested username") + return + } + + cfg.deleteSkin(skin, resp) +} + func (cfg *Config) Authenticate(handler http.Handler) http.Handler { return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { err := cfg.Auth.Check(req) @@ -98,6 +121,17 @@ func (cfg *Config) Authenticate(handler http.Handler) http.Handler { }) } +func (cfg *Config) deleteSkin(skin *model.Skin, resp http.ResponseWriter) { + err := cfg.SkinsRepo.RemoveByUserId(skin.UserId) + if err != nil { + cfg.Logger.Error("Cannot delete skin by error: :err", wd.ErrParam(err)) + apiServerError(resp) + return + } + + resp.WriteHeader(http.StatusNoContent) +} + func validatePostSkinRequest(request *http.Request) map[string][]string { const maxMultipartMemory int64 = 32 << 20 const oneOfSkinOrUrlMessage = "One of url or skin should be provided, but not both" @@ -197,6 +231,15 @@ func apiForbidden(resp http.ResponseWriter, reason string) { resp.Write(result) } +func apiNotFound(resp http.ResponseWriter, reason string) { + resp.WriteHeader(http.StatusNotFound) + resp.Header().Set("Content-Type", "application/json") + result, _ := json.Marshal([]interface{}{ + reason, + }) + resp.Write(result) +} + func apiServerError(resp http.ResponseWriter) { resp.WriteHeader(http.StatusInternalServerError) } diff --git a/http/api_test.go b/http/api_test.go index 6062596..7d886ca 100644 --- a/http/api_test.go +++ b/http/api_test.go @@ -56,7 +56,7 @@ func TestConfig_PostSkin_Valid(t *testing.T) { defer resp.Body.Close() assert.Equal(201, resp.StatusCode) response, _ := ioutil.ReadAll(resp.Body) - assert.Empty(string(response)) + assert.Empty(response) } func TestConfig_PostSkin_ChangedIdentityId(t *testing.T) { @@ -102,7 +102,7 @@ func TestConfig_PostSkin_ChangedIdentityId(t *testing.T) { defer resp.Body.Close() assert.Equal(201, resp.StatusCode) response, _ := ioutil.ReadAll(resp.Body) - assert.Empty(string(response)) + assert.Empty(response) } func TestConfig_PostSkin_ChangedUsername(t *testing.T) { @@ -146,7 +146,7 @@ func TestConfig_PostSkin_ChangedUsername(t *testing.T) { defer resp.Body.Close() assert.Equal(201, resp.StatusCode) response, _ := ioutil.ReadAll(resp.Body) - assert.Empty(string(response)) + assert.Empty(response) } func TestConfig_PostSkin_CompletelyNewIdentity(t *testing.T) { @@ -190,7 +190,7 @@ func TestConfig_PostSkin_CompletelyNewIdentity(t *testing.T) { defer resp.Body.Close() assert.Equal(201, resp.StatusCode) response, _ := ioutil.ReadAll(resp.Body) - assert.Empty(string(response)) + assert.Empty(response) } func TestConfig_PostSkin_UploadSkin(t *testing.T) { @@ -323,6 +323,104 @@ func TestConfig_PostSkin_Unauthorized(t *testing.T) { ]`, string(response)) } +func TestConfig_DeleteSkinByUserId_Success(t *testing.T) { + assert := testify.New(t) + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + config, mocks := setupMocks(ctrl) + + req := httptest.NewRequest("DELETE", "http://skinsystem.ely.by/api/skins/id:1", nil) + w := httptest.NewRecorder() + + mocks.Auth.EXPECT().Check(gomock.Any()).Return(nil) + mocks.Skins.EXPECT().FindByUserId(1).Return(createSkinModel("mock_user", false), nil) + mocks.Skins.EXPECT().RemoveByUserId(1).Return(nil) + + config.CreateHandler().ServeHTTP(w, req) + + resp := w.Result() + defer resp.Body.Close() + assert.Equal(204, resp.StatusCode) + response, _ := ioutil.ReadAll(resp.Body) + assert.Empty(response) +} + +func TestConfig_DeleteSkinByUserId_NotFound(t *testing.T) { + assert := testify.New(t) + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + config, mocks := setupMocks(ctrl) + + req := httptest.NewRequest("DELETE", "http://skinsystem.ely.by/api/skins/id:2", nil) + w := httptest.NewRecorder() + + mocks.Auth.EXPECT().Check(gomock.Any()).Return(nil) + mocks.Skins.EXPECT().FindByUserId(2).Return(nil, &db.SkinNotFoundError{"unknown"}) + + config.CreateHandler().ServeHTTP(w, req) + + resp := w.Result() + defer resp.Body.Close() + assert.Equal(404, resp.StatusCode) + response, _ := ioutil.ReadAll(resp.Body) + assert.JSONEq(`[ + "Cannot find record for requested user id" + ]`, string(response)) +} + +func TestConfig_DeleteSkinByUsername_Success(t *testing.T) { + assert := testify.New(t) + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + config, mocks := setupMocks(ctrl) + + req := httptest.NewRequest("DELETE", "http://skinsystem.ely.by/api/skins/mock_user", nil) + w := httptest.NewRecorder() + + mocks.Auth.EXPECT().Check(gomock.Any()).Return(nil) + mocks.Skins.EXPECT().FindByUsername("mock_user").Return(createSkinModel("mock_user", false), nil) + mocks.Skins.EXPECT().RemoveByUserId(1).Return(nil) + + config.CreateHandler().ServeHTTP(w, req) + + resp := w.Result() + defer resp.Body.Close() + assert.Equal(204, resp.StatusCode) + response, _ := ioutil.ReadAll(resp.Body) + assert.Empty(response) +} + +func TestConfig_DeleteSkinByUsername_NotFound(t *testing.T) { + assert := testify.New(t) + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + config, mocks := setupMocks(ctrl) + + req := httptest.NewRequest("DELETE", "http://skinsystem.ely.by/api/skins/mock_user_2", nil) + w := httptest.NewRecorder() + + mocks.Auth.EXPECT().Check(gomock.Any()).Return(nil) + mocks.Skins.EXPECT().FindByUsername("mock_user_2").Return(nil, &db.SkinNotFoundError{"mock_user_2"}) + + config.CreateHandler().ServeHTTP(w, req) + + resp := w.Result() + defer resp.Body.Close() + assert.Equal(404, resp.StatusCode) + response, _ := ioutil.ReadAll(resp.Body) + assert.JSONEq(`[ + "Cannot find record for requested username" + ]`, string(response)) +} + // base64 https://github.com/mathiasbynens/small/blob/0ca3c51/png-transparent.png var OnePxPng = []byte("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==") diff --git a/http/http.go b/http/http.go index e68aedf..6213166 100644 --- a/http/http.go +++ b/http/http.go @@ -62,6 +62,8 @@ func (cfg *Config) CreateHandler() http.Handler { router.HandleFunc("/cloaks", cfg.CapeGET).Methods("GET") // API router.Handle("/api/skins", cfg.Authenticate(http.HandlerFunc(cfg.PostSkin))).Methods("POST") + router.Handle("/api/skins/id:{id:[0-9]+}", cfg.Authenticate(http.HandlerFunc(cfg.DeleteSkinByUserId))).Methods("DELETE") + router.Handle("/api/skins/{username}", cfg.Authenticate(http.HandlerFunc(cfg.DeleteSkinByUsername))).Methods("DELETE") // 404 router.NotFoundHandler = http.HandlerFunc(cfg.NotFound)