mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-29 04:22:24 -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 | ||||
|   instance: | ||||
|     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: | ||||
|         description: New account registrations require admin approval. | ||||
|         type: boolean | ||||
|  | @ -1045,7 +1053,7 @@ definitions: | |||
|         x-go-name: Title | ||||
|       uri: | ||||
|         description: The URI of the instance. | ||||
|         example: https://example.org | ||||
|         example: https://gts.example.org | ||||
|         type: string | ||||
|         x-go-name: URI | ||||
|       urls: | ||||
|  | @ -2000,6 +2008,57 @@ paths: | |||
|       summary: Handles webfinger account lookup requests. | ||||
|       tags: | ||||
|       - 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: | ||||
|     post: | ||||
|       consumes: | ||||
|  | @ -3255,52 +3314,6 @@ paths: | |||
|           description: internal server error | ||||
|       tags: | ||||
|       - 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}: | ||||
|     get: | ||||
|       operationId: mediaGet | ||||
|  |  | |||
|  | @ -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 | ||||
| } | ||||
|  |  | |||
|  | @ -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) | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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{ | ||||
|  |  | |||
|  | @ -68,7 +68,7 @@ type Attachment struct { | |||
| 	Type string `json:"type"` | ||||
| 	// The location of the original full-size attachment. | ||||
| 	// 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. | ||||
| 	// In our case, we just give the URL again since we don't create smaller URLs. | ||||
| 	TextURL string `json:"text_url"` | ||||
|  | @ -78,16 +78,16 @@ type Attachment struct { | |||
| 	// The location of the full-size original attachment on the remote server. | ||||
| 	// Only defined for instances other than our own. | ||||
| 	// 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. | ||||
| 	// Only defined for instances other than our own. | ||||
| 	// 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. | ||||
| 	Meta MediaMeta `json:"meta,omitempty"` | ||||
| 	// Alt text that describes what is in the media attachment. | ||||
| 	// 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. | ||||
| 	// See https://github.com/woltapp/blurhash | ||||
| 	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) { | ||||
| 	return model.Attachment{ | ||||
| 		ID:               a.ID, | ||||
| 		Type:             strings.ToLower(string(a.Type)), | ||||
| 		URL:              a.URL, | ||||
| 		TextURL:          a.URL, | ||||
| 		PreviewURL:       a.Thumbnail.URL, | ||||
| 		RemoteURL:        a.RemoteURL, | ||||
| 		PreviewRemoteURL: a.Thumbnail.RemoteURL, | ||||
| 	apiAttachment := model.Attachment{ | ||||
| 		ID:         a.ID, | ||||
| 		Type:       strings.ToLower(string(a.Type)), | ||||
| 		TextURL:    a.URL, | ||||
| 		PreviewURL: a.Thumbnail.URL, | ||||
| 		Meta: model.MediaMeta{ | ||||
| 			Original: model.MediaDimensions{ | ||||
| 				Width:  a.FileMeta.Original.Width, | ||||
|  | @ -259,9 +256,31 @@ func (c *converter) AttachmentToAPIAttachment(ctx context.Context, a *gtsmodel.M | |||
| 				Y: a.FileMeta.Focus.Y, | ||||
| 			}, | ||||
| 		}, | ||||
| 		Description: a.Description, | ||||
| 		Blurhash:    a.Blurhash, | ||||
| 	}, nil | ||||
| 		Blurhash: a.Blurhash, | ||||
| 	} | ||||
| 
 | ||||
| 	// 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) { | ||||
|  |  | |||
|  | @ -52,7 +52,7 @@ func (suite *InternalToFrontendTestSuite) TestStatusToFrontend() { | |||
| 	b, err := json.Marshal(apiStatus) | ||||
| 	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() { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue