diff options
Diffstat (limited to 'internal/api/client/media')
| -rw-r--r-- | internal/api/client/media/media.go | 29 | ||||
| -rw-r--r-- | internal/api/client/media/mediacreate.go | 23 | ||||
| -rw-r--r-- | internal/api/client/media/mediacreate_test.go | 121 | ||||
| -rw-r--r-- | internal/api/client/media/mediaupdate_test.go | 8 | 
4 files changed, 150 insertions, 31 deletions
diff --git a/internal/api/client/media/media.go b/internal/api/client/media/media.go index c9aee64ca..87cc2f091 100644 --- a/internal/api/client/media/media.go +++ b/internal/api/client/media/media.go @@ -26,20 +26,12 @@ import (  	"github.com/superseriousbusiness/gotosocial/internal/router"  ) -// BasePathV1 is the base API path for making media requests through v1 of the api (for mastodon API compatibility) -const BasePathV1 = "/api/v1/media" - -// BasePathV2 is the base API path for making media requests through v2 of the api (for mastodon API compatibility) -const BasePathV2 = "/api/v2/media" - -// IDKey is the key for media attachment IDs -const IDKey = "id" - -// BasePathWithIDV1 corresponds to a media attachment with the given ID -const BasePathWithIDV1 = BasePathV1 + "/:" + IDKey - -// BasePathWithIDV2 corresponds to a media attachment with the given ID -const BasePathWithIDV2 = BasePathV2 + "/:" + IDKey +const ( +	IDKey                  = "id"                                // IDKey is the key for media attachment IDs +	APIVersionKey          = "api_version"                       // APIVersionKey is the key for which version of the API to use (v1 or v2) +	BasePathWithAPIVersion = "/api/:" + APIVersionKey + "/media" // BasePathWithAPIVersion is the base API path for making media requests through v1 or v2 of the api (for mastodon API compatibility) +	BasePathWithIDV1       = "/api/v1/media/:" + IDKey           // BasePathWithID corresponds to a media attachment with the given ID +)  // Module implements the ClientAPIModule interface for media  type Module struct { @@ -55,15 +47,8 @@ func New(processor processing.Processor) api.ClientModule {  // Route satisfies the RESTAPIModule interface  func (m *Module) Route(s router.Router) error { -	// v1 handlers -	s.AttachHandler(http.MethodPost, BasePathV1, m.MediaCreatePOSTHandler) +	s.AttachHandler(http.MethodPost, BasePathWithAPIVersion, m.MediaCreatePOSTHandler)  	s.AttachHandler(http.MethodGet, BasePathWithIDV1, m.MediaGETHandler)  	s.AttachHandler(http.MethodPut, BasePathWithIDV1, m.MediaPUTHandler) - -	// v2 handlers -	s.AttachHandler(http.MethodPost, BasePathV2, m.MediaCreatePOSTHandler) -	s.AttachHandler(http.MethodGet, BasePathWithIDV2, m.MediaGETHandler) -	s.AttachHandler(http.MethodPut, BasePathWithIDV2, m.MediaPUTHandler) -  	return nil  } diff --git a/internal/api/client/media/mediacreate.go b/internal/api/client/media/mediacreate.go index f51d272b6..5a040b26c 100644 --- a/internal/api/client/media/mediacreate.go +++ b/internal/api/client/media/mediacreate.go @@ -31,7 +31,7 @@ import (  	"github.com/superseriousbusiness/gotosocial/internal/oauth"  ) -// MediaCreatePOSTHandler swagger:operation POST /api/v1/media mediaCreate +// MediaCreatePOSTHandler swagger:operation POST /api/{api_version}/media mediaCreate  //  // Upload a new media attachment.  // @@ -46,6 +46,11 @@ import (  // - application/json  //  // parameters: +// - name: api version +//   type: string +//   in: path +//   description: Version of the API to use. Must be one of v1 or v2. +//   required: true  // - name: description  //   in: formData  //   description: |- @@ -95,6 +100,13 @@ func (m *Module) MediaCreatePOSTHandler(c *gin.Context) {  		return  	} +	apiVersion := c.Param(APIVersionKey) +	if apiVersion != "v1" && apiVersion != "v2" { +		err := errors.New("api version must be one of v1 or v2") +		api.ErrorHandler(c, gtserror.NewErrorNotFound(err, err.Error()), m.processor.InstanceGet) +		return +	} +  	form := &model.AttachmentRequest{}  	if err := c.ShouldBind(&form); err != nil {  		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) @@ -112,6 +124,15 @@ func (m *Module) MediaCreatePOSTHandler(c *gin.Context) {  		return  	} +	if apiVersion == "v2" { +		// the mastodon v2 media API specifies that the URL should be null +		// and that the client should call /api/v1/media/:id to get the URL +		// +		// so even though we have the URL already, remove it now to comply +		// with the api +		apiAttachment.URL = nil +	} +  	c.JSON(http.StatusOK, apiAttachment)  } diff --git a/internal/api/client/media/mediacreate_test.go b/internal/api/client/media/mediacreate_test.go index 595edb73d..8651fd982 100644 --- a/internal/api/client/media/mediacreate_test.go +++ b/internal/api/client/media/mediacreate_test.go @@ -30,6 +30,7 @@ import (  	"net/http/httptest"  	"testing" +	"github.com/gin-gonic/gin"  	"github.com/stretchr/testify/suite"  	mediamodule "github.com/superseriousbusiness/gotosocial/internal/api/client/media"  	"github.com/superseriousbusiness/gotosocial/internal/api/model" @@ -154,9 +155,15 @@ func (suite *MediaCreateTestSuite) TestMediaCreateSuccessful() {  	if err != nil {  		panic(err)  	} -	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", mediamodule.BasePathV1), bytes.NewReader(buf.Bytes())) // the endpoint we're hitting +	ctx.Request = httptest.NewRequest(http.MethodPost, "http://localhost:8080/api/v1/media", bytes.NewReader(buf.Bytes())) // the endpoint we're hitting  	ctx.Request.Header.Set("Content-Type", w.FormDataContentType())  	ctx.Request.Header.Set("accept", "application/json") +	ctx.Params = gin.Params{ +		gin.Param{ +			Key:   mediamodule.APIVersionKey, +			Value: "v1", +		}, +	}  	// do the actual request  	suite.mediaModule.MediaCreatePOSTHandler(ctx) @@ -185,7 +192,7 @@ func (suite *MediaCreateTestSuite) TestMediaCreateSuccessful() {  	err = json.Unmarshal(b, attachmentReply)  	suite.NoError(err) -	suite.Equal("this is a test image -- a cool background from somewhere", attachmentReply.Description) +	suite.Equal("this is a test image -- a cool background from somewhere", *attachmentReply.Description)  	suite.Equal("image", attachmentReply.Type)  	suite.EqualValues(model.MediaMeta{  		Original: model.MediaDimensions{ @@ -212,6 +219,100 @@ func (suite *MediaCreateTestSuite) TestMediaCreateSuccessful() {  	suite.Equal(len(storageKeysBeforeRequest)+2, len(storageKeysAfterRequest)) // 2 images should be added to storage: the original and the thumbnail  } +func (suite *MediaCreateTestSuite) TestMediaCreateSuccessfulV2() { +	// set up the context for the request +	t := suite.testTokens["local_account_1"] +	oauthToken := oauth.DBTokenToToken(t) +	recorder := httptest.NewRecorder() +	ctx, _ := testrig.CreateGinTestContext(recorder, nil) +	ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) +	ctx.Set(oauth.SessionAuthorizedToken, oauthToken) +	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) +	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) + +	// see what's in storage *before* the request +	storageKeysBeforeRequest := []string{} +	iter, err := suite.storage.KVStore.Iterator(nil) +	if err != nil { +		panic(err) +	} +	for iter.Next() { +		storageKeysBeforeRequest = append(storageKeysBeforeRequest, iter.Key()) +	} +	iter.Release() + +	// create the request +	buf, w, err := testrig.CreateMultipartFormData("file", "../../../../testrig/media/test-jpeg.jpg", map[string]string{ +		"description": "this is a test image -- a cool background from somewhere", +		"focus":       "-0.5,0.5", +	}) +	if err != nil { +		panic(err) +	} +	ctx.Request = httptest.NewRequest(http.MethodPost, "http://localhost:8080/api/v2/media", bytes.NewReader(buf.Bytes())) // the endpoint we're hitting +	ctx.Request.Header.Set("Content-Type", w.FormDataContentType()) +	ctx.Request.Header.Set("accept", "application/json") +	ctx.Params = gin.Params{ +		gin.Param{ +			Key:   mediamodule.APIVersionKey, +			Value: "v2", +		}, +	} + +	// do the actual request +	suite.mediaModule.MediaCreatePOSTHandler(ctx) + +	// check what's in storage *after* the request +	storageKeysAfterRequest := []string{} +	iter, err = suite.storage.KVStore.Iterator(nil) +	if err != nil { +		panic(err) +	} +	for iter.Next() { +		storageKeysAfterRequest = append(storageKeysAfterRequest, iter.Key()) +	} +	iter.Release() + +	// check response +	suite.EqualValues(http.StatusOK, recorder.Code) + +	result := recorder.Result() +	defer result.Body.Close() +	b, err := ioutil.ReadAll(result.Body) +	suite.NoError(err) +	fmt.Println(string(b)) + +	attachmentReply := &model.Attachment{} +	err = json.Unmarshal(b, attachmentReply) +	suite.NoError(err) + +	suite.Equal("this is a test image -- a cool background from somewhere", *attachmentReply.Description) +	suite.Equal("image", attachmentReply.Type) +	suite.EqualValues(model.MediaMeta{ +		Original: model.MediaDimensions{ +			Width:  1920, +			Height: 1080, +			Size:   "1920x1080", +			Aspect: 1.7777778, +		}, +		Small: model.MediaDimensions{ +			Width:  512, +			Height: 288, +			Size:   "512x288", +			Aspect: 1.7777778, +		}, +		Focus: model.MediaFocus{ +			X: -0.5, +			Y: 0.5, +		}, +	}, attachmentReply.Meta) +	suite.Equal("LjBzUo#6RQR._NvzRjWF?urqV@a$", attachmentReply.Blurhash) +	suite.NotEmpty(attachmentReply.ID) +	suite.Nil(attachmentReply.URL) +	suite.NotEmpty(attachmentReply.PreviewURL) +	suite.Equal(len(storageKeysBeforeRequest)+2, len(storageKeysAfterRequest)) // 2 images should be added to storage: the original and the thumbnail +} +  func (suite *MediaCreateTestSuite) TestMediaCreateLongDescription() {  	// set up the context for the request  	t := suite.testTokens["local_account_1"] @@ -238,9 +339,15 @@ func (suite *MediaCreateTestSuite) TestMediaCreateLongDescription() {  	if err != nil {  		panic(err)  	} -	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", mediamodule.BasePathV1), bytes.NewReader(buf.Bytes())) // the endpoint we're hitting +	ctx.Request = httptest.NewRequest(http.MethodPost, "http://localhost:8080/api/v1/media", bytes.NewReader(buf.Bytes())) // the endpoint we're hitting  	ctx.Request.Header.Set("Content-Type", w.FormDataContentType())  	ctx.Request.Header.Set("accept", "application/json") +	ctx.Params = gin.Params{ +		gin.Param{ +			Key:   mediamodule.APIVersionKey, +			Value: "v1", +		}, +	}  	// do the actual request  	suite.mediaModule.MediaCreatePOSTHandler(ctx) @@ -278,9 +385,15 @@ func (suite *MediaCreateTestSuite) TestMediaCreateTooShortDescription() {  	if err != nil {  		panic(err)  	} -	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", mediamodule.BasePathV1), bytes.NewReader(buf.Bytes())) // the endpoint we're hitting +	ctx.Request = httptest.NewRequest(http.MethodPost, "http://localhost:8080/api/v1/media", bytes.NewReader(buf.Bytes())) // the endpoint we're hitting  	ctx.Request.Header.Set("Content-Type", w.FormDataContentType())  	ctx.Request.Header.Set("accept", "application/json") +	ctx.Params = gin.Params{ +		gin.Param{ +			Key:   mediamodule.APIVersionKey, +			Value: "v1", +		}, +	}  	// do the actual request  	suite.mediaModule.MediaCreatePOSTHandler(ctx) diff --git a/internal/api/client/media/mediaupdate_test.go b/internal/api/client/media/mediaupdate_test.go index 8081a5c15..607f4c31c 100644 --- a/internal/api/client/media/mediaupdate_test.go +++ b/internal/api/client/media/mediaupdate_test.go @@ -145,7 +145,7 @@ func (suite *MediaUpdateTestSuite) TestUpdateImage() {  	if err != nil {  		panic(err)  	} -	ctx.Request = httptest.NewRequest(http.MethodPut, fmt.Sprintf("http://localhost:8080/%s/%s", mediamodule.BasePathV1, toUpdate.ID), bytes.NewReader(buf.Bytes())) // the endpoint we're hitting +	ctx.Request = httptest.NewRequest(http.MethodPut, fmt.Sprintf("http://localhost:8080/api/v1/media/%s", toUpdate.ID), bytes.NewReader(buf.Bytes())) // the endpoint we're hitting  	ctx.Request.Header.Set("Content-Type", w.FormDataContentType())  	ctx.Request.Header.Set("accept", "application/json")  	ctx.Params = gin.Params{ @@ -172,7 +172,7 @@ func (suite *MediaUpdateTestSuite) TestUpdateImage() {  	suite.NoError(err)  	// the reply should contain the updated fields -	suite.Equal("new description!", attachmentReply.Description) +	suite.Equal("new description!", *attachmentReply.Description)  	suite.EqualValues("gif", attachmentReply.Type)  	suite.EqualValues(model.MediaMeta{  		Original: model.MediaDimensions{Width: 800, Height: 450, FrameRate: "", Duration: 0, Bitrate: 0, Size: "800x450", Aspect: 1.7777778}, @@ -181,7 +181,7 @@ func (suite *MediaUpdateTestSuite) TestUpdateImage() {  	}, attachmentReply.Meta)  	suite.Equal(toUpdate.Blurhash, attachmentReply.Blurhash)  	suite.Equal(toUpdate.ID, attachmentReply.ID) -	suite.Equal(toUpdate.URL, attachmentReply.URL) +	suite.Equal(toUpdate.URL, *attachmentReply.URL)  	suite.NotEmpty(toUpdate.Thumbnail.URL, attachmentReply.PreviewURL)  } @@ -210,7 +210,7 @@ func (suite *MediaUpdateTestSuite) TestUpdateImageShortDescription() {  	if err != nil {  		panic(err)  	} -	ctx.Request = httptest.NewRequest(http.MethodPut, fmt.Sprintf("http://localhost:8080/%s/%s", mediamodule.BasePathV1, toUpdate.ID), bytes.NewReader(buf.Bytes())) // the endpoint we're hitting +	ctx.Request = httptest.NewRequest(http.MethodPut, fmt.Sprintf("http://localhost:8080/api/v1/media/%s", toUpdate.ID), bytes.NewReader(buf.Bytes())) // the endpoint we're hitting  	ctx.Request.Header.Set("Content-Type", w.FormDataContentType())  	ctx.Request.Header.Set("accept", "application/json")  	ctx.Params = gin.Params{  | 
