mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 18:32:25 -05:00 
			
		
		
		
	[bugfix] Make /api/v2/media more compatible with masto API (#724)
		
	* update docs * make api version into a path param * update tests * workaround to unset URL if using v2 of api * make some fields into pointers
This commit is contained in:
		
					parent
					
						
							
								d20ec967c4
							
						
					
				
			
			
				commit
				
					
						73b8839c5d
					
				
			
		
					 8 changed files with 245 additions and 94 deletions
				
			
		|  | @ -971,6 +971,14 @@ definitions: | ||||||
|     x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model |     x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model | ||||||
|   instance: |   instance: | ||||||
|     properties: |     properties: | ||||||
|  |       account_domain: | ||||||
|  |         description: |- | ||||||
|  |           The domain of accounts on this instance. | ||||||
|  |           This will not necessarily be the same as | ||||||
|  |           simply the Host part of the URI. | ||||||
|  |         example: example.org | ||||||
|  |         type: string | ||||||
|  |         x-go-name: AccountDomain | ||||||
|       approval_required: |       approval_required: | ||||||
|         description: New account registrations require admin approval. |         description: New account registrations require admin approval. | ||||||
|         type: boolean |         type: boolean | ||||||
|  | @ -1045,7 +1053,7 @@ definitions: | ||||||
|         x-go-name: Title |         x-go-name: Title | ||||||
|       uri: |       uri: | ||||||
|         description: The URI of the instance. |         description: The URI of the instance. | ||||||
|         example: https://example.org |         example: https://gts.example.org | ||||||
|         type: string |         type: string | ||||||
|         x-go-name: URI |         x-go-name: URI | ||||||
|       urls: |       urls: | ||||||
|  | @ -2000,6 +2008,57 @@ paths: | ||||||
|       summary: Handles webfinger account lookup requests. |       summary: Handles webfinger account lookup requests. | ||||||
|       tags: |       tags: | ||||||
|       - webfinger |       - webfinger | ||||||
|  |   /api/{api_version}/media: | ||||||
|  |     post: | ||||||
|  |       consumes: | ||||||
|  |       - multipart/form-data | ||||||
|  |       operationId: mediaCreate | ||||||
|  |       parameters: | ||||||
|  |       - description: Version of the API to use. Must be one of v1 or v2. | ||||||
|  |         in: path | ||||||
|  |         name: api version | ||||||
|  |         required: true | ||||||
|  |         type: string | ||||||
|  |       - description: |- | ||||||
|  |           Image or media description to use as alt-text on the attachment. | ||||||
|  |           This is very useful for users of screenreaders. | ||||||
|  |           May or may not be required, depending on your instance settings. | ||||||
|  |         in: formData | ||||||
|  |         name: description | ||||||
|  |         type: string | ||||||
|  |       - description: |- | ||||||
|  |           Focus of the media file. | ||||||
|  |           If present, it should be in the form of two comma-separated floats between -1 and 1. | ||||||
|  |           For example: `-0.5,0.25`. | ||||||
|  |         in: formData | ||||||
|  |         name: focus | ||||||
|  |         type: string | ||||||
|  |       - description: The media attachment to upload. | ||||||
|  |         in: formData | ||||||
|  |         name: file | ||||||
|  |         required: true | ||||||
|  |         type: file | ||||||
|  |       produces: | ||||||
|  |       - application/json | ||||||
|  |       responses: | ||||||
|  |         "200": | ||||||
|  |           description: The newly-created media attachment. | ||||||
|  |           schema: | ||||||
|  |             $ref: '#/definitions/attachment' | ||||||
|  |         "400": | ||||||
|  |           description: bad request | ||||||
|  |         "401": | ||||||
|  |           description: unauthorized | ||||||
|  |         "422": | ||||||
|  |           description: unprocessable | ||||||
|  |         "500": | ||||||
|  |           description: internal server error | ||||||
|  |       security: | ||||||
|  |       - OAuth2 Bearer: | ||||||
|  |         - write:media | ||||||
|  |       summary: Upload a new media attachment. | ||||||
|  |       tags: | ||||||
|  |       - media | ||||||
|   /api/v1/accounts: |   /api/v1/accounts: | ||||||
|     post: |     post: | ||||||
|       consumes: |       consumes: | ||||||
|  | @ -3255,52 +3314,6 @@ paths: | ||||||
|           description: internal server error |           description: internal server error | ||||||
|       tags: |       tags: | ||||||
|       - instance |       - instance | ||||||
|   /api/v1/media: |  | ||||||
|     post: |  | ||||||
|       consumes: |  | ||||||
|       - multipart/form-data |  | ||||||
|       operationId: mediaCreate |  | ||||||
|       parameters: |  | ||||||
|       - description: |- |  | ||||||
|           Image or media description to use as alt-text on the attachment. |  | ||||||
|           This is very useful for users of screenreaders. |  | ||||||
|           May or may not be required, depending on your instance settings. |  | ||||||
|         in: formData |  | ||||||
|         name: description |  | ||||||
|         type: string |  | ||||||
|       - description: |- |  | ||||||
|           Focus of the media file. |  | ||||||
|           If present, it should be in the form of two comma-separated floats between -1 and 1. |  | ||||||
|           For example: `-0.5,0.25`. |  | ||||||
|         in: formData |  | ||||||
|         name: focus |  | ||||||
|         type: string |  | ||||||
|       - description: The media attachment to upload. |  | ||||||
|         in: formData |  | ||||||
|         name: file |  | ||||||
|         required: true |  | ||||||
|         type: file |  | ||||||
|       produces: |  | ||||||
|       - application/json |  | ||||||
|       responses: |  | ||||||
|         "200": |  | ||||||
|           description: The newly-created media attachment. |  | ||||||
|           schema: |  | ||||||
|             $ref: '#/definitions/attachment' |  | ||||||
|         "400": |  | ||||||
|           description: bad request |  | ||||||
|         "401": |  | ||||||
|           description: unauthorized |  | ||||||
|         "422": |  | ||||||
|           description: unprocessable |  | ||||||
|         "500": |  | ||||||
|           description: internal server error |  | ||||||
|       security: |  | ||||||
|       - OAuth2 Bearer: |  | ||||||
|         - write:media |  | ||||||
|       summary: Upload a new media attachment. |  | ||||||
|       tags: |  | ||||||
|       - media |  | ||||||
|   /api/v1/media/{id}: |   /api/v1/media/{id}: | ||||||
|     get: |     get: | ||||||
|       operationId: mediaGet |       operationId: mediaGet | ||||||
|  |  | ||||||
|  | @ -26,20 +26,12 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/router" | 	"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 ( | ||||||
| const BasePathV1 = "/api/v1/media" | 	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) | ||||||
| // BasePathV2 is the base API path for making media requests through v2 of the api (for mastodon API compatibility) | 	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) | ||||||
| const BasePathV2 = "/api/v2/media" | 	BasePathWithIDV1       = "/api/v1/media/:" + IDKey           // BasePathWithID corresponds to a media attachment with the given ID | ||||||
| 
 | ) | ||||||
| // 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 |  | ||||||
| 
 | 
 | ||||||
| // Module implements the ClientAPIModule interface for media | // Module implements the ClientAPIModule interface for media | ||||||
| type Module struct { | type Module struct { | ||||||
|  | @ -55,15 +47,8 @@ func New(processor processing.Processor) api.ClientModule { | ||||||
| 
 | 
 | ||||||
| // Route satisfies the RESTAPIModule interface | // Route satisfies the RESTAPIModule interface | ||||||
| func (m *Module) Route(s router.Router) error { | func (m *Module) Route(s router.Router) error { | ||||||
| 	// v1 handlers | 	s.AttachHandler(http.MethodPost, BasePathWithAPIVersion, m.MediaCreatePOSTHandler) | ||||||
| 	s.AttachHandler(http.MethodPost, BasePathV1, m.MediaCreatePOSTHandler) |  | ||||||
| 	s.AttachHandler(http.MethodGet, BasePathWithIDV1, m.MediaGETHandler) | 	s.AttachHandler(http.MethodGet, BasePathWithIDV1, m.MediaGETHandler) | ||||||
| 	s.AttachHandler(http.MethodPut, BasePathWithIDV1, m.MediaPUTHandler) | 	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 | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -31,7 +31,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | 	"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. | // Upload a new media attachment. | ||||||
| // | // | ||||||
|  | @ -46,6 +46,11 @@ import ( | ||||||
| // - application/json | // - application/json | ||||||
| // | // | ||||||
| // parameters: | // 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 | // - name: description | ||||||
| //   in: formData | //   in: formData | ||||||
| //   description: |- | //   description: |- | ||||||
|  | @ -95,6 +100,13 @@ func (m *Module) MediaCreatePOSTHandler(c *gin.Context) { | ||||||
| 		return | 		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{} | 	form := &model.AttachmentRequest{} | ||||||
| 	if err := c.ShouldBind(&form); err != nil { | 	if err := c.ShouldBind(&form); err != nil { | ||||||
| 		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) | 		api.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGet) | ||||||
|  | @ -112,6 +124,15 @@ func (m *Module) MediaCreatePOSTHandler(c *gin.Context) { | ||||||
| 		return | 		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) | 	c.JSON(http.StatusOK, apiAttachment) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -30,6 +30,7 @@ import ( | ||||||
| 	"net/http/httptest" | 	"net/http/httptest" | ||||||
| 	"testing" | 	"testing" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/gin-gonic/gin" | ||||||
| 	"github.com/stretchr/testify/suite" | 	"github.com/stretchr/testify/suite" | ||||||
| 	mediamodule "github.com/superseriousbusiness/gotosocial/internal/api/client/media" | 	mediamodule "github.com/superseriousbusiness/gotosocial/internal/api/client/media" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/api/model" | 	"github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||||
|  | @ -154,9 +155,15 @@ func (suite *MediaCreateTestSuite) TestMediaCreateSuccessful() { | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		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("Content-Type", w.FormDataContentType()) | ||||||
| 	ctx.Request.Header.Set("accept", "application/json") | 	ctx.Request.Header.Set("accept", "application/json") | ||||||
|  | 	ctx.Params = gin.Params{ | ||||||
|  | 		gin.Param{ | ||||||
|  | 			Key:   mediamodule.APIVersionKey, | ||||||
|  | 			Value: "v1", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	// do the actual request | 	// do the actual request | ||||||
| 	suite.mediaModule.MediaCreatePOSTHandler(ctx) | 	suite.mediaModule.MediaCreatePOSTHandler(ctx) | ||||||
|  | @ -185,7 +192,7 @@ func (suite *MediaCreateTestSuite) TestMediaCreateSuccessful() { | ||||||
| 	err = json.Unmarshal(b, attachmentReply) | 	err = json.Unmarshal(b, attachmentReply) | ||||||
| 	suite.NoError(err) | 	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.Equal("image", attachmentReply.Type) | ||||||
| 	suite.EqualValues(model.MediaMeta{ | 	suite.EqualValues(model.MediaMeta{ | ||||||
| 		Original: model.MediaDimensions{ | 		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 | 	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() { | func (suite *MediaCreateTestSuite) TestMediaCreateLongDescription() { | ||||||
| 	// set up the context for the request | 	// set up the context for the request | ||||||
| 	t := suite.testTokens["local_account_1"] | 	t := suite.testTokens["local_account_1"] | ||||||
|  | @ -238,9 +339,15 @@ func (suite *MediaCreateTestSuite) TestMediaCreateLongDescription() { | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		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("Content-Type", w.FormDataContentType()) | ||||||
| 	ctx.Request.Header.Set("accept", "application/json") | 	ctx.Request.Header.Set("accept", "application/json") | ||||||
|  | 	ctx.Params = gin.Params{ | ||||||
|  | 		gin.Param{ | ||||||
|  | 			Key:   mediamodule.APIVersionKey, | ||||||
|  | 			Value: "v1", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	// do the actual request | 	// do the actual request | ||||||
| 	suite.mediaModule.MediaCreatePOSTHandler(ctx) | 	suite.mediaModule.MediaCreatePOSTHandler(ctx) | ||||||
|  | @ -278,9 +385,15 @@ func (suite *MediaCreateTestSuite) TestMediaCreateTooShortDescription() { | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		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("Content-Type", w.FormDataContentType()) | ||||||
| 	ctx.Request.Header.Set("accept", "application/json") | 	ctx.Request.Header.Set("accept", "application/json") | ||||||
|  | 	ctx.Params = gin.Params{ | ||||||
|  | 		gin.Param{ | ||||||
|  | 			Key:   mediamodule.APIVersionKey, | ||||||
|  | 			Value: "v1", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	// do the actual request | 	// do the actual request | ||||||
| 	suite.mediaModule.MediaCreatePOSTHandler(ctx) | 	suite.mediaModule.MediaCreatePOSTHandler(ctx) | ||||||
|  |  | ||||||
|  | @ -145,7 +145,7 @@ func (suite *MediaUpdateTestSuite) TestUpdateImage() { | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		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("Content-Type", w.FormDataContentType()) | ||||||
| 	ctx.Request.Header.Set("accept", "application/json") | 	ctx.Request.Header.Set("accept", "application/json") | ||||||
| 	ctx.Params = gin.Params{ | 	ctx.Params = gin.Params{ | ||||||
|  | @ -172,7 +172,7 @@ func (suite *MediaUpdateTestSuite) TestUpdateImage() { | ||||||
| 	suite.NoError(err) | 	suite.NoError(err) | ||||||
| 
 | 
 | ||||||
| 	// the reply should contain the updated fields | 	// 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("gif", attachmentReply.Type) | ||||||
| 	suite.EqualValues(model.MediaMeta{ | 	suite.EqualValues(model.MediaMeta{ | ||||||
| 		Original: model.MediaDimensions{Width: 800, Height: 450, FrameRate: "", Duration: 0, Bitrate: 0, Size: "800x450", Aspect: 1.7777778}, | 		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) | 	}, attachmentReply.Meta) | ||||||
| 	suite.Equal(toUpdate.Blurhash, attachmentReply.Blurhash) | 	suite.Equal(toUpdate.Blurhash, attachmentReply.Blurhash) | ||||||
| 	suite.Equal(toUpdate.ID, attachmentReply.ID) | 	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) | 	suite.NotEmpty(toUpdate.Thumbnail.URL, attachmentReply.PreviewURL) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -210,7 +210,7 @@ func (suite *MediaUpdateTestSuite) TestUpdateImageShortDescription() { | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		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("Content-Type", w.FormDataContentType()) | ||||||
| 	ctx.Request.Header.Set("accept", "application/json") | 	ctx.Request.Header.Set("accept", "application/json") | ||||||
| 	ctx.Params = gin.Params{ | 	ctx.Params = gin.Params{ | ||||||
|  |  | ||||||
|  | @ -68,7 +68,7 @@ type Attachment struct { | ||||||
| 	Type string `json:"type"` | 	Type string `json:"type"` | ||||||
| 	// The location of the original full-size attachment. | 	// The location of the original full-size attachment. | ||||||
| 	// example: https://example.org/fileserver/some_id/attachments/some_id/original/attachment.jpeg | 	// example: https://example.org/fileserver/some_id/attachments/some_id/original/attachment.jpeg | ||||||
| 	URL string `json:"url"` | 	URL *string `json:"url"` | ||||||
| 	// A shorter URL for the attachment. | 	// A shorter URL for the attachment. | ||||||
| 	// In our case, we just give the URL again since we don't create smaller URLs. | 	// In our case, we just give the URL again since we don't create smaller URLs. | ||||||
| 	TextURL string `json:"text_url"` | 	TextURL string `json:"text_url"` | ||||||
|  | @ -78,16 +78,16 @@ type Attachment struct { | ||||||
| 	// The location of the full-size original attachment on the remote server. | 	// The location of the full-size original attachment on the remote server. | ||||||
| 	// Only defined for instances other than our own. | 	// Only defined for instances other than our own. | ||||||
| 	// example: https://some-other-server.org/attachments/original/ahhhhh.jpeg | 	// example: https://some-other-server.org/attachments/original/ahhhhh.jpeg | ||||||
| 	RemoteURL string `json:"remote_url"` | 	RemoteURL *string `json:"remote_url"` | ||||||
| 	// The location of a scaled-down preview of the attachment on the remote server. | 	// The location of a scaled-down preview of the attachment on the remote server. | ||||||
| 	// Only defined for instances other than our own. | 	// Only defined for instances other than our own. | ||||||
| 	// example: https://some-other-server.org/attachments/small/ahhhhh.jpeg | 	// example: https://some-other-server.org/attachments/small/ahhhhh.jpeg | ||||||
| 	PreviewRemoteURL string `json:"preview_remote_url"` | 	PreviewRemoteURL *string `json:"preview_remote_url"` | ||||||
| 	// Metadata for this attachment. | 	// Metadata for this attachment. | ||||||
| 	Meta MediaMeta `json:"meta,omitempty"` | 	Meta MediaMeta `json:"meta,omitempty"` | ||||||
| 	// Alt text that describes what is in the media attachment. | 	// Alt text that describes what is in the media attachment. | ||||||
| 	// example: This is a picture of a kitten. | 	// example: This is a picture of a kitten. | ||||||
| 	Description string `json:"description"` | 	Description *string `json:"description"` | ||||||
| 	// A hash computed by the BlurHash algorithm, for generating colorful preview thumbnails when media has not been downloaded yet. | 	// A hash computed by the BlurHash algorithm, for generating colorful preview thumbnails when media has not been downloaded yet. | ||||||
| 	// See https://github.com/woltapp/blurhash | 	// See https://github.com/woltapp/blurhash | ||||||
| 	Blurhash string `json:"blurhash,omitempty"` | 	Blurhash string `json:"blurhash,omitempty"` | ||||||
|  |  | ||||||
|  | @ -233,14 +233,11 @@ func (c *converter) AppToAPIAppPublic(ctx context.Context, a *gtsmodel.Applicati | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *converter) AttachmentToAPIAttachment(ctx context.Context, a *gtsmodel.MediaAttachment) (model.Attachment, error) { | func (c *converter) AttachmentToAPIAttachment(ctx context.Context, a *gtsmodel.MediaAttachment) (model.Attachment, error) { | ||||||
| 	return model.Attachment{ | 	apiAttachment := model.Attachment{ | ||||||
| 		ID:         a.ID, | 		ID:         a.ID, | ||||||
| 		Type:       strings.ToLower(string(a.Type)), | 		Type:       strings.ToLower(string(a.Type)), | ||||||
| 		URL:              a.URL, |  | ||||||
| 		TextURL:    a.URL, | 		TextURL:    a.URL, | ||||||
| 		PreviewURL: a.Thumbnail.URL, | 		PreviewURL: a.Thumbnail.URL, | ||||||
| 		RemoteURL:        a.RemoteURL, |  | ||||||
| 		PreviewRemoteURL: a.Thumbnail.RemoteURL, |  | ||||||
| 		Meta: model.MediaMeta{ | 		Meta: model.MediaMeta{ | ||||||
| 			Original: model.MediaDimensions{ | 			Original: model.MediaDimensions{ | ||||||
| 				Width:  a.FileMeta.Original.Width, | 				Width:  a.FileMeta.Original.Width, | ||||||
|  | @ -259,9 +256,31 @@ func (c *converter) AttachmentToAPIAttachment(ctx context.Context, a *gtsmodel.M | ||||||
| 				Y: a.FileMeta.Focus.Y, | 				Y: a.FileMeta.Focus.Y, | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		Description: a.Description, |  | ||||||
| 		Blurhash: a.Blurhash, | 		Blurhash: a.Blurhash, | ||||||
| 	}, nil | 	} | ||||||
|  | 
 | ||||||
|  | 	// nullable fields | ||||||
|  | 	if a.URL != "" { | ||||||
|  | 		i := a.URL | ||||||
|  | 		apiAttachment.URL = &i | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if a.RemoteURL != "" { | ||||||
|  | 		i := a.RemoteURL | ||||||
|  | 		apiAttachment.RemoteURL = &i | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if a.Thumbnail.RemoteURL != "" { | ||||||
|  | 		i := a.Thumbnail.RemoteURL | ||||||
|  | 		apiAttachment.PreviewRemoteURL = &i | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if a.Description != "" { | ||||||
|  | 		i := a.Description | ||||||
|  | 		apiAttachment.Description = &i | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return apiAttachment, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *converter) MentionToAPIMention(ctx context.Context, m *gtsmodel.Mention) (model.Mention, error) { | func (c *converter) MentionToAPIMention(ctx context.Context, m *gtsmodel.Mention) (model.Mention, error) { | ||||||
|  |  | ||||||
|  | @ -52,7 +52,7 @@ func (suite *InternalToFrontendTestSuite) TestStatusToFrontend() { | ||||||
| 	b, err := json.Marshal(apiStatus) | 	b, err := json.Marshal(apiStatus) | ||||||
| 	suite.NoError(err) | 	suite.NoError(err) | ||||||
| 
 | 
 | ||||||
| 	suite.Equal(`{"id":"01F8MH75CBF9JFX4ZAD54N0W0R","created_at":"2021-10-20T11:36:45.000Z","in_reply_to_id":"","in_reply_to_account_id":"","sensitive":false,"spoiler_text":"","visibility":"public","language":"en","uri":"http://localhost:8080/users/admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R","url":"http://localhost:8080/@admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R","replies_count":0,"reblogs_count":0,"favourites_count":1,"favourited":true,"reblogged":false,"muted":false,"bookmarked":false,"pinned":false,"content":"hello world! #welcome ! first post on the instance :rainbow: !","reblog":null,"application":{"name":"superseriousbusiness","website":"https://superserious.business"},"account":{"id":"01F8MH17FWEB39HZJ76B6VXSKF","username":"admin","acct":"admin","display_name":"","locked":false,"bot":false,"created_at":"2022-05-17T13:10:59.000Z","note":"","url":"http://localhost:8080/@admin","avatar":"","avatar_static":"","header":"","header_static":"","followers_count":1,"following_count":1,"statuses_count":4,"last_status_at":"2021-10-20T10:41:37.000Z","emojis":[],"fields":[]},"media_attachments":[{"id":"01F8MH6NEM8D7527KZAECTCR76","type":"image","url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpeg","text_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpeg","preview_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/small/01F8MH6NEM8D7527KZAECTCR76.jpeg","remote_url":"","preview_remote_url":"","meta":{"original":{"width":1200,"height":630,"size":"1200x630","aspect":1.9047619},"small":{"width":256,"height":134,"size":"256x134","aspect":1.9104477},"focus":{"x":0,"y":0}},"description":"Black and white image of some 50's style text saying: Welcome On Board","blurhash":"LNJRdVM{00Rj%Mayt7j[4nWBofRj"}],"mentions":[],"tags":[{"name":"welcome","url":"http://localhost:8080/tags/welcome"}],"emojis":[{"shortcode":"rainbow","url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png","static_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/emoji/static/01F8MH9H8E4VG3KDYJR9EGPXCQ.png","visible_in_picker":true}],"card":null,"poll":null,"text":""}`, string(b)) | 	suite.Equal(`{"id":"01F8MH75CBF9JFX4ZAD54N0W0R","created_at":"2021-10-20T11:36:45.000Z","in_reply_to_id":"","in_reply_to_account_id":"","sensitive":false,"spoiler_text":"","visibility":"public","language":"en","uri":"http://localhost:8080/users/admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R","url":"http://localhost:8080/@admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R","replies_count":0,"reblogs_count":0,"favourites_count":1,"favourited":true,"reblogged":false,"muted":false,"bookmarked":false,"pinned":false,"content":"hello world! #welcome ! first post on the instance :rainbow: !","reblog":null,"application":{"name":"superseriousbusiness","website":"https://superserious.business"},"account":{"id":"01F8MH17FWEB39HZJ76B6VXSKF","username":"admin","acct":"admin","display_name":"","locked":false,"bot":false,"created_at":"2022-05-17T13:10:59.000Z","note":"","url":"http://localhost:8080/@admin","avatar":"","avatar_static":"","header":"","header_static":"","followers_count":1,"following_count":1,"statuses_count":4,"last_status_at":"2021-10-20T10:41:37.000Z","emojis":[],"fields":[]},"media_attachments":[{"id":"01F8MH6NEM8D7527KZAECTCR76","type":"image","url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpeg","text_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpeg","preview_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/small/01F8MH6NEM8D7527KZAECTCR76.jpeg","remote_url":null,"preview_remote_url":null,"meta":{"original":{"width":1200,"height":630,"size":"1200x630","aspect":1.9047619},"small":{"width":256,"height":134,"size":"256x134","aspect":1.9104477},"focus":{"x":0,"y":0}},"description":"Black and white image of some 50's style text saying: Welcome On Board","blurhash":"LNJRdVM{00Rj%Mayt7j[4nWBofRj"}],"mentions":[],"tags":[{"name":"welcome","url":"http://localhost:8080/tags/welcome"}],"emojis":[{"shortcode":"rainbow","url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png","static_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/emoji/static/01F8MH9H8E4VG3KDYJR9EGPXCQ.png","visible_in_picker":true}],"card":null,"poll":null,"text":""}`, string(b)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *InternalToFrontendTestSuite) TestInstanceToFrontend() { | func (suite *InternalToFrontendTestSuite) TestInstanceToFrontend() { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue