diff --git a/internal/api/activitypub/emoji/emojiget.go b/internal/api/activitypub/emoji/emojiget.go index c291a500a..5a9f0db72 100644 --- a/internal/api/activitypub/emoji/emojiget.go +++ b/internal/api/activitypub/emoji/emojiget.go @@ -18,7 +18,6 @@ package emoji import ( - "encoding/json" "errors" "net/http" "strings" @@ -36,7 +35,7 @@ func (m *Module) EmojiGetHandler(c *gin.Context) { return } - format, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubHeaders...) + contentType, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubHeaders...) if err != nil { apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) return @@ -48,11 +47,12 @@ func (m *Module) EmojiGetHandler(c *gin.Context) { return } - b, err := json.Marshal(resp) - if err != nil { - apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1) - return - } - - c.Data(http.StatusOK, format, b) + // Encode JSON HTTP response. + apiutil.EncodeJSONResponse( + c.Writer, + c.Request, + http.StatusOK, + contentType, + resp, + ) } diff --git a/internal/api/activitypub/publickey/publickeyget.go b/internal/api/activitypub/publickey/publickeyget.go index a7de4efad..083a31961 100644 --- a/internal/api/activitypub/publickey/publickeyget.go +++ b/internal/api/activitypub/publickey/publickeyget.go @@ -18,7 +18,6 @@ package publickey import ( - "encoding/json" "errors" "net/http" "strings" @@ -42,13 +41,13 @@ func (m *Module) PublicKeyGETHandler(c *gin.Context) { return } - format, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...) + contentType, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...) if err != nil { apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) return } - if format == string(apiutil.TextHTML) { + if contentType == string(apiutil.TextHTML) { // redirect to the user's profile c.Redirect(http.StatusSeeOther, "/@"+requestedUsername) return @@ -60,11 +59,12 @@ func (m *Module) PublicKeyGETHandler(c *gin.Context) { return } - b, err := json.Marshal(resp) - if err != nil { - apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1) - return - } - - c.Data(http.StatusOK, format, b) + // Encode JSON HTTP response. + apiutil.EncodeJSONResponse( + c.Writer, + c.Request, + http.StatusOK, + contentType, + resp, + ) } diff --git a/internal/api/activitypub/users/featured.go b/internal/api/activitypub/users/featured.go index 7a2b73a6f..f256c1e75 100644 --- a/internal/api/activitypub/users/featured.go +++ b/internal/api/activitypub/users/featured.go @@ -18,7 +18,6 @@ package users import ( - "encoding/json" "errors" "net/http" "strings" @@ -67,13 +66,13 @@ func (m *Module) FeaturedCollectionGETHandler(c *gin.Context) { return } - format, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...) + contentType, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...) if err != nil { apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) return } - if format == string(apiutil.TextHTML) { + if contentType == string(apiutil.TextHTML) { // This isn't an ActivityPub request; // redirect to the user's profile. c.Redirect(http.StatusSeeOther, "/@"+requestedUsername) @@ -86,11 +85,5 @@ func (m *Module) FeaturedCollectionGETHandler(c *gin.Context) { return } - b, err := json.Marshal(resp) - if err != nil { - apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1) - return - } - - c.Data(http.StatusOK, format, b) + apiutil.JSONType(c, http.StatusOK, contentType, resp) } diff --git a/internal/api/activitypub/users/followers.go b/internal/api/activitypub/users/followers.go index e93ef8d4d..956cdc71e 100644 --- a/internal/api/activitypub/users/followers.go +++ b/internal/api/activitypub/users/followers.go @@ -18,7 +18,6 @@ package users import ( - "encoding/json" "errors" "net/http" "strings" @@ -39,13 +38,13 @@ func (m *Module) FollowersGETHandler(c *gin.Context) { return } - format, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...) + contentType, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...) if err != nil { apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) return } - if format == string(apiutil.TextHTML) { + if contentType == string(apiutil.TextHTML) { // This isn't an ActivityPub request; // redirect to the user's profile. c.Redirect(http.StatusSeeOther, "/@"+requestedUsername) @@ -68,11 +67,5 @@ func (m *Module) FollowersGETHandler(c *gin.Context) { return } - b, err := json.Marshal(resp) - if err != nil { - apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1) - return - } - - c.Data(http.StatusOK, format, b) + apiutil.JSONType(c, http.StatusOK, contentType, resp) } diff --git a/internal/api/activitypub/users/following.go b/internal/api/activitypub/users/following.go index 54fb3b676..d01b55b57 100644 --- a/internal/api/activitypub/users/following.go +++ b/internal/api/activitypub/users/following.go @@ -18,7 +18,6 @@ package users import ( - "encoding/json" "errors" "net/http" "strings" @@ -39,13 +38,13 @@ func (m *Module) FollowingGETHandler(c *gin.Context) { return } - format, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...) + contentType, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...) if err != nil { apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) return } - if format == string(apiutil.TextHTML) { + if contentType == string(apiutil.TextHTML) { // This isn't an ActivityPub request; // redirect to the user's profile. c.Redirect(http.StatusSeeOther, "/@"+requestedUsername) @@ -68,11 +67,5 @@ func (m *Module) FollowingGETHandler(c *gin.Context) { return } - b, err := json.Marshal(resp) - if err != nil { - apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1) - return - } - - c.Data(http.StatusOK, format, b) + apiutil.JSONType(c, http.StatusOK, contentType, resp) } diff --git a/internal/api/activitypub/users/inboxpost.go b/internal/api/activitypub/users/inboxpost.go index c2d3d79c4..03ba5c5a6 100644 --- a/internal/api/activitypub/users/inboxpost.go +++ b/internal/api/activitypub/users/inboxpost.go @@ -47,6 +47,5 @@ func (m *Module) InboxPOSTHandler(c *gin.Context) { return } - // Inbox POST body was Accepted for processing. - c.JSON(http.StatusAccepted, gin.H{"status": http.StatusText(http.StatusAccepted)}) + apiutil.Data(c, http.StatusAccepted, apiutil.AppJSON, apiutil.StatusAcceptedJSON) } diff --git a/internal/api/activitypub/users/outboxget.go b/internal/api/activitypub/users/outboxget.go index e4617ba90..7dcc354ac 100644 --- a/internal/api/activitypub/users/outboxget.go +++ b/internal/api/activitypub/users/outboxget.go @@ -18,7 +18,6 @@ package users import ( - "encoding/json" "errors" "fmt" "net/http" @@ -93,13 +92,13 @@ func (m *Module) OutboxGETHandler(c *gin.Context) { return } - format, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...) + contentType, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...) if err != nil { apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) return } - if format == string(apiutil.TextHTML) { + if contentType == string(apiutil.TextHTML) { // This isn't an ActivityPub request; // redirect to the user's profile. c.Redirect(http.StatusSeeOther, "/@"+requestedUsername) @@ -135,11 +134,5 @@ func (m *Module) OutboxGETHandler(c *gin.Context) { return } - b, err := json.Marshal(resp) - if err != nil { - apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1) - return - } - - c.Data(http.StatusOK, format, b) + apiutil.JSONType(c, http.StatusOK, contentType, resp) } diff --git a/internal/api/activitypub/users/outboxget_test.go b/internal/api/activitypub/users/outboxget_test.go index 4829a8946..31b7e8e9b 100644 --- a/internal/api/activitypub/users/outboxget_test.go +++ b/internal/api/activitypub/users/outboxget_test.go @@ -209,7 +209,7 @@ func (suite *OutboxGetTestSuite) TestGetOutboxNextPage() { suite.NoError(err) suite.Equal(`{ "@context": "https://www.w3.org/ns/activitystreams", - "id": "http://localhost:8080/users/the_mighty_zork/outbox?page=true\u0026maxID=01F8MHAMCHF6Y650WCRSCP4WMY", + "id": "http://localhost:8080/users/the_mighty_zork/outbox?page=true&maxID=01F8MHAMCHF6Y650WCRSCP4WMY", "orderedItems": [], "partOf": "http://localhost:8080/users/the_mighty_zork/outbox", "type": "OrderedCollectionPage" diff --git a/internal/api/activitypub/users/repliesget.go b/internal/api/activitypub/users/repliesget.go index 3ac4ccbbb..2d3472f35 100644 --- a/internal/api/activitypub/users/repliesget.go +++ b/internal/api/activitypub/users/repliesget.go @@ -18,7 +18,6 @@ package users import ( - "encoding/json" "errors" "net/http" "strings" @@ -107,13 +106,13 @@ func (m *Module) StatusRepliesGETHandler(c *gin.Context) { return } - format, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...) + contentType, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...) if err != nil { apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) return } - if format == string(apiutil.TextHTML) { + if contentType == string(apiutil.TextHTML) { // redirect to the status c.Redirect(http.StatusSeeOther, "/@"+requestedUsername+"/statuses/"+requestedStatusID) return @@ -161,12 +160,5 @@ func (m *Module) StatusRepliesGETHandler(c *gin.Context) { return } - b, err := json.Marshal(resp) - if err != nil { - errWithCode := gtserror.NewErrorInternalError(err) - apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) - return - } - - c.Data(http.StatusOK, format, b) + apiutil.JSONType(c, http.StatusOK, contentType, resp) } diff --git a/internal/api/activitypub/users/repliesget_test.go b/internal/api/activitypub/users/repliesget_test.go index ac25f3617..d20d8c6c0 100644 --- a/internal/api/activitypub/users/repliesget_test.go +++ b/internal/api/activitypub/users/repliesget_test.go @@ -266,11 +266,16 @@ func toJSON(a any) string { } a = m } - b, err := json.MarshalIndent(a, "", " ") + var dst bytes.Buffer + enc := json.NewEncoder(&dst) + enc.SetIndent("", " ") + enc.SetEscapeHTML(false) + err := enc.Encode(a) if err != nil { panic(err) } - return string(b) + dst.Truncate(dst.Len() - 1) // drop new-line + return dst.String() } // indentJSON will return indented JSON from raw provided JSON. diff --git a/internal/api/activitypub/users/statusget.go b/internal/api/activitypub/users/statusget.go index f7b5ccfd1..27af9c6b4 100644 --- a/internal/api/activitypub/users/statusget.go +++ b/internal/api/activitypub/users/statusget.go @@ -18,7 +18,6 @@ package users import ( - "encoding/json" "errors" "net/http" "strings" @@ -46,13 +45,13 @@ func (m *Module) StatusGETHandler(c *gin.Context) { return } - format, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...) + contentType, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...) if err != nil { apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) return } - if format == string(apiutil.TextHTML) { + if contentType == string(apiutil.TextHTML) { // redirect to the status c.Redirect(http.StatusSeeOther, "/@"+requestedUsername+"/statuses/"+requestedStatusID) return @@ -64,11 +63,5 @@ func (m *Module) StatusGETHandler(c *gin.Context) { return } - b, err := json.Marshal(resp) - if err != nil { - apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1) - return - } - - c.Data(http.StatusOK, format, b) + apiutil.JSONType(c, http.StatusOK, contentType, resp) } diff --git a/internal/api/activitypub/users/userget.go b/internal/api/activitypub/users/userget.go index 61be69836..2deca0fa4 100644 --- a/internal/api/activitypub/users/userget.go +++ b/internal/api/activitypub/users/userget.go @@ -18,7 +18,6 @@ package users import ( - "encoding/json" "errors" "net/http" "strings" @@ -46,13 +45,13 @@ func (m *Module) UsersGETHandler(c *gin.Context) { return } - format, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...) + contentType, err := apiutil.NegotiateAccept(c, apiutil.ActivityPubOrHTMLHeaders...) if err != nil { apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) return } - if format == string(apiutil.TextHTML) { + if contentType == string(apiutil.TextHTML) { // redirect to the user's profile c.Redirect(http.StatusSeeOther, "/@"+requestedUsername) return @@ -64,11 +63,5 @@ func (m *Module) UsersGETHandler(c *gin.Context) { return } - b, err := json.Marshal(resp) - if err != nil { - apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1) - return - } - - c.Data(http.StatusOK, format, b) + apiutil.JSONType(c, http.StatusOK, contentType, resp) } diff --git a/internal/api/auth/token.go b/internal/api/auth/token.go index 9787e9efc..cab9352fa 100644 --- a/internal/api/auth/token.go +++ b/internal/api/auth/token.go @@ -110,5 +110,11 @@ func (m *Module) TokenPOSTHandler(c *gin.Context) { c.Header("Cache-Control", "no-store") c.Header("Pragma", "no-cache") - c.JSON(http.StatusOK, token) + apiutil.EncodeJSONResponse( + c.Writer, + c.Request, + http.StatusOK, + apiutil.AppJSON, + token, + ) } diff --git a/internal/api/client/accounts/accountcreate.go b/internal/api/client/accounts/accountcreate.go index 473000f6d..061c66b57 100644 --- a/internal/api/client/accounts/accountcreate.go +++ b/internal/api/client/accounts/accountcreate.go @@ -107,7 +107,7 @@ func (m *Module) AccountCreatePOSTHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, ti) + apiutil.JSON(c, http.StatusOK, ti) } // validateNormalizeCreateAccount checks through all the necessary prerequisites for creating a new account, diff --git a/internal/api/client/accounts/accountdelete.go b/internal/api/client/accounts/accountdelete.go index 242902cab..947634f70 100644 --- a/internal/api/client/accounts/accountdelete.go +++ b/internal/api/client/accounts/accountdelete.go @@ -96,5 +96,7 @@ func (m *Module) AccountDeletePOSTHandler(c *gin.Context) { return } - c.JSON(http.StatusAccepted, gin.H{"message": "accepted"}) + apiutil.JSON(c, http.StatusAccepted, map[string]string{ + "message": "accepted", + }) } diff --git a/internal/api/client/accounts/accountget.go b/internal/api/client/accounts/accountget.go index 300efe46e..4c1b66a20 100644 --- a/internal/api/client/accounts/accountget.go +++ b/internal/api/client/accounts/accountget.go @@ -90,5 +90,5 @@ func (m *Module) AccountGETHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, acctInfo) + apiutil.JSON(c, http.StatusOK, acctInfo) } diff --git a/internal/api/client/accounts/accountupdate.go b/internal/api/client/accounts/accountupdate.go index 9c51f5924..ab731bd7e 100644 --- a/internal/api/client/accounts/accountupdate.go +++ b/internal/api/client/accounts/accountupdate.go @@ -170,7 +170,7 @@ func (m *Module) AccountUpdateCredentialsPATCHHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, acctSensitive) + apiutil.JSON(c, http.StatusOK, acctSensitive) } // fieldsAttributesFormBinding satisfies gin's binding.Binding interface. diff --git a/internal/api/client/accounts/accountverify.go b/internal/api/client/accounts/accountverify.go index 97a21e0d8..1799089ab 100644 --- a/internal/api/client/accounts/accountverify.go +++ b/internal/api/client/accounts/accountverify.go @@ -73,5 +73,5 @@ func (m *Module) AccountVerifyGETHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, acctSensitive) + apiutil.JSON(c, http.StatusOK, acctSensitive) } diff --git a/internal/api/client/accounts/block.go b/internal/api/client/accounts/block.go index ccf781849..24ff099a7 100644 --- a/internal/api/client/accounts/block.go +++ b/internal/api/client/accounts/block.go @@ -90,5 +90,5 @@ func (m *Module) AccountBlockPOSTHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, relationship) + apiutil.JSON(c, http.StatusOK, relationship) } diff --git a/internal/api/client/accounts/follow.go b/internal/api/client/accounts/follow.go index 260f647cc..2e6e79964 100644 --- a/internal/api/client/accounts/follow.go +++ b/internal/api/client/accounts/follow.go @@ -122,5 +122,5 @@ func (m *Module) AccountFollowPOSTHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, relationship) + apiutil.JSON(c, http.StatusOK, relationship) } diff --git a/internal/api/client/accounts/followers.go b/internal/api/client/accounts/followers.go index 2448bc50a..bedbcef24 100644 --- a/internal/api/client/accounts/followers.go +++ b/internal/api/client/accounts/followers.go @@ -151,5 +151,5 @@ func (m *Module) AccountFollowersGETHandler(c *gin.Context) { c.Header("Link", resp.LinkHeader) } - c.JSON(http.StatusOK, resp.Items) + apiutil.JSON(c, http.StatusOK, resp.Items) } diff --git a/internal/api/client/accounts/following.go b/internal/api/client/accounts/following.go index d106d6ea6..9a8e488b2 100644 --- a/internal/api/client/accounts/following.go +++ b/internal/api/client/accounts/following.go @@ -151,5 +151,5 @@ func (m *Module) AccountFollowingGETHandler(c *gin.Context) { c.Header("Link", resp.LinkHeader) } - c.JSON(http.StatusOK, resp.Items) + apiutil.JSON(c, http.StatusOK, resp.Items) } diff --git a/internal/api/client/accounts/lists.go b/internal/api/client/accounts/lists.go index 4ce1bf729..d42fdd3f9 100644 --- a/internal/api/client/accounts/lists.go +++ b/internal/api/client/accounts/lists.go @@ -93,5 +93,5 @@ func (m *Module) AccountListsGETHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, lists) + apiutil.JSON(c, http.StatusOK, lists) } diff --git a/internal/api/client/accounts/lookup.go b/internal/api/client/accounts/lookup.go index 4b31ea6cc..f6bd97657 100644 --- a/internal/api/client/accounts/lookup.go +++ b/internal/api/client/accounts/lookup.go @@ -89,5 +89,5 @@ func (m *Module) AccountLookupGETHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, account) + apiutil.JSON(c, http.StatusOK, account) } diff --git a/internal/api/client/accounts/note.go b/internal/api/client/accounts/note.go index 9a0667875..29ea01c9a 100644 --- a/internal/api/client/accounts/note.go +++ b/internal/api/client/accounts/note.go @@ -104,5 +104,5 @@ func (m *Module) AccountNotePOSTHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, relationship) + apiutil.JSON(c, http.StatusOK, relationship) } diff --git a/internal/api/client/accounts/relationships.go b/internal/api/client/accounts/relationships.go index 591ae7684..dfe8c1721 100644 --- a/internal/api/client/accounts/relationships.go +++ b/internal/api/client/accounts/relationships.go @@ -106,5 +106,5 @@ func (m *Module) AccountRelationshipsGETHandler(c *gin.Context) { relationships = append(relationships, *r) } - c.JSON(http.StatusOK, relationships) + apiutil.JSON(c, http.StatusOK, relationships) } diff --git a/internal/api/client/accounts/search.go b/internal/api/client/accounts/search.go index c10fb2960..183fc1347 100644 --- a/internal/api/client/accounts/search.go +++ b/internal/api/client/accounts/search.go @@ -162,5 +162,5 @@ func (m *Module) AccountSearchGETHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, results) + apiutil.JSON(c, http.StatusOK, results) } diff --git a/internal/api/client/accounts/statuses.go b/internal/api/client/accounts/statuses.go index 867788501..870a96891 100644 --- a/internal/api/client/accounts/statuses.go +++ b/internal/api/client/accounts/statuses.go @@ -241,5 +241,6 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) { if resp.LinkHeader != "" { c.Header("Link", resp.LinkHeader) } - c.JSON(http.StatusOK, resp.Items) + + apiutil.JSON(c, http.StatusOK, resp.Items) } diff --git a/internal/api/client/accounts/unblock.go b/internal/api/client/accounts/unblock.go index 882a3b2f1..e8144711e 100644 --- a/internal/api/client/accounts/unblock.go +++ b/internal/api/client/accounts/unblock.go @@ -91,5 +91,6 @@ func (m *Module) AccountUnblockPOSTHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, relationship) + apiutil.JSON(c, http.StatusOK, relationship) + } diff --git a/internal/api/client/accounts/unfollow.go b/internal/api/client/accounts/unfollow.go index 4edc9ccea..9eb66aed3 100644 --- a/internal/api/client/accounts/unfollow.go +++ b/internal/api/client/accounts/unfollow.go @@ -91,5 +91,5 @@ func (m *Module) AccountUnfollowPOSTHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, relationship) + apiutil.JSON(c, http.StatusOK, relationship) } diff --git a/internal/api/client/admin/accountaction.go b/internal/api/client/admin/accountaction.go index 91186ae73..89bcf644e 100644 --- a/internal/api/client/admin/accountaction.go +++ b/internal/api/client/admin/accountaction.go @@ -124,5 +124,7 @@ func (m *Module) AccountActionPOSTHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, gin.H{"message": "OK"}) + apiutil.JSON(c, http.StatusOK, map[string]string{ + "message": "OK", + }) } diff --git a/internal/api/client/admin/domainkeysexpire.go b/internal/api/client/admin/domainkeysexpire.go index 73a811dd4..10a7597a4 100644 --- a/internal/api/client/admin/domainkeysexpire.go +++ b/internal/api/client/admin/domainkeysexpire.go @@ -132,7 +132,9 @@ func (m *Module) DomainKeysExpirePOSTHandler(c *gin.Context) { return } - c.JSON(http.StatusAccepted, &apimodel.AdminActionResponse{ActionID: actionID}) + apiutil.JSON(c, http.StatusOK, &apimodel.AdminActionResponse{ + ActionID: actionID, + }) } func validateDomainKeysExpire(form *apimodel.DomainKeysExpireRequest) error { diff --git a/internal/api/client/admin/domainpermission.go b/internal/api/client/admin/domainpermission.go index 203eddc8b..05319086f 100644 --- a/internal/api/client/admin/domainpermission.go +++ b/internal/api/client/admin/domainpermission.go @@ -122,7 +122,7 @@ func (m *Module) createDomainPermissions( return } - c.JSON(http.StatusOK, domainBlock) + apiutil.JSON(c, http.StatusOK, domainBlock) return } @@ -158,7 +158,7 @@ func (m *Module) createDomainPermissions( domainPerms = append(domainPerms, entry.Resource) } - c.JSON(http.StatusOK, domainPerms) + apiutil.JSON(c, http.StatusOK, domainPerms) } // deleteDomainPermission deletes a single domain permission (block or allow). @@ -200,7 +200,7 @@ func (m *Module) deleteDomainPermission( return } - c.JSON(http.StatusOK, domainPerm) + apiutil.JSON(c, http.StatusOK, domainPerm) } // getDomainPermission gets a single domain permission (block or allow). @@ -248,7 +248,7 @@ func (m *Module) getDomainPermission( return } - c.JSON(http.StatusOK, domainPerm) + apiutil.JSON(c, http.StatusOK, domainPerm) } // getDomainPermissions gets all domain permissions of the given type (block, allow). @@ -290,5 +290,5 @@ func (m *Module) getDomainPermissions( return } - c.JSON(http.StatusOK, domainPerm) + apiutil.JSON(c, http.StatusOK, domainPerm) } diff --git a/internal/api/client/admin/emailtest.go b/internal/api/client/admin/emailtest.go index 5c5330679..8f274e226 100644 --- a/internal/api/client/admin/emailtest.go +++ b/internal/api/client/admin/emailtest.go @@ -116,5 +116,7 @@ func (m *Module) EmailTestPOSTHandler(c *gin.Context) { return } - c.JSON(http.StatusAccepted, gin.H{"status": "test email sent"}) + apiutil.JSON(c, http.StatusAccepted, map[string]string{ + "status": "test email sent", + }) } diff --git a/internal/api/client/admin/emojicategoriesget.go b/internal/api/client/admin/emojicategoriesget.go index 9597f789c..2c097c6df 100644 --- a/internal/api/client/admin/emojicategoriesget.go +++ b/internal/api/client/admin/emojicategoriesget.go @@ -89,5 +89,5 @@ func (m *Module) EmojiCategoriesGETHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, categories) + apiutil.JSON(c, http.StatusOK, categories) } diff --git a/internal/api/client/admin/emojicreate.go b/internal/api/client/admin/emojicreate.go index e98ef754e..d916a76c1 100644 --- a/internal/api/client/admin/emojicreate.go +++ b/internal/api/client/admin/emojicreate.go @@ -131,7 +131,7 @@ func (m *Module) EmojiCreatePOSTHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, apiEmoji) + apiutil.JSON(c, http.StatusOK, apiEmoji) } func validateCreateEmoji(form *apimodel.EmojiCreateRequest) error { diff --git a/internal/api/client/admin/emojidelete.go b/internal/api/client/admin/emojidelete.go index 8d388a409..b5cf72daf 100644 --- a/internal/api/client/admin/emojidelete.go +++ b/internal/api/client/admin/emojidelete.go @@ -105,5 +105,5 @@ func (m *Module) EmojiDELETEHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, emoji) + apiutil.JSON(c, http.StatusOK, emoji) } diff --git a/internal/api/client/admin/emojiget.go b/internal/api/client/admin/emojiget.go index 349747b6b..710094551 100644 --- a/internal/api/client/admin/emojiget.go +++ b/internal/api/client/admin/emojiget.go @@ -95,5 +95,5 @@ func (m *Module) EmojiGETHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, emoji) + apiutil.JSON(c, http.StatusOK, emoji) } diff --git a/internal/api/client/admin/emojisget.go b/internal/api/client/admin/emojisget.go index c0ae90004..212401117 100644 --- a/internal/api/client/admin/emojisget.go +++ b/internal/api/client/admin/emojisget.go @@ -206,5 +206,6 @@ func (m *Module) EmojisGETHandler(c *gin.Context) { if resp.LinkHeader != "" { c.Header("Link", resp.LinkHeader) } - c.JSON(http.StatusOK, resp.Items) + + apiutil.JSON(c, http.StatusOK, resp.Items) } diff --git a/internal/api/client/admin/emojiupdate.go b/internal/api/client/admin/emojiupdate.go index 49f8c3414..f531f36f9 100644 --- a/internal/api/client/admin/emojiupdate.go +++ b/internal/api/client/admin/emojiupdate.go @@ -161,7 +161,7 @@ func (m *Module) EmojiPATCHHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, emoji) + apiutil.JSON(c, http.StatusOK, emoji) } // do a first pass on the form here diff --git a/internal/api/client/admin/mediacleanup.go b/internal/api/client/admin/mediacleanup.go index 3ef3c1441..7a0ee4bd6 100644 --- a/internal/api/client/admin/mediacleanup.go +++ b/internal/api/client/admin/mediacleanup.go @@ -102,5 +102,5 @@ func (m *Module) MediaCleanupPOSTHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, remoteCacheDays) + apiutil.JSON(c, http.StatusOK, remoteCacheDays) } diff --git a/internal/api/client/admin/mediarefetch.go b/internal/api/client/admin/mediarefetch.go index 2c57d3b95..1c0da6dea 100644 --- a/internal/api/client/admin/mediarefetch.go +++ b/internal/api/client/admin/mediarefetch.go @@ -88,5 +88,5 @@ func (m *Module) MediaRefetchPOSTHandler(c *gin.Context) { return } - c.Status(http.StatusAccepted) + apiutil.Data(c, http.StatusOK, apiutil.AppJSON, apiutil.StatusAcceptedJSON) } diff --git a/internal/api/client/admin/reportget.go b/internal/api/client/admin/reportget.go index 55425f53a..f70ae8b54 100644 --- a/internal/api/client/admin/reportget.go +++ b/internal/api/client/admin/reportget.go @@ -98,5 +98,5 @@ func (m *Module) ReportGETHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, report) + apiutil.JSON(c, http.StatusOK, report) } diff --git a/internal/api/client/admin/reportresolve.go b/internal/api/client/admin/reportresolve.go index fdd4f8449..9c1c32afe 100644 --- a/internal/api/client/admin/reportresolve.go +++ b/internal/api/client/admin/reportresolve.go @@ -120,5 +120,5 @@ func (m *Module) ReportResolvePOSTHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, report) + apiutil.JSON(c, http.StatusOK, report) } diff --git a/internal/api/client/admin/reportsget.go b/internal/api/client/admin/reportsget.go index cffa578f4..394962f6b 100644 --- a/internal/api/client/admin/reportsget.go +++ b/internal/api/client/admin/reportsget.go @@ -176,5 +176,6 @@ func (m *Module) ReportsGETHandler(c *gin.Context) { if resp.LinkHeader != "" { c.Header("Link", resp.LinkHeader) } - c.JSON(http.StatusOK, resp.Items) + + apiutil.JSON(c, http.StatusOK, resp.Items) } diff --git a/internal/api/client/admin/rulecreate.go b/internal/api/client/admin/rulecreate.go index 7792233f6..e838bff1e 100644 --- a/internal/api/client/admin/rulecreate.go +++ b/internal/api/client/admin/rulecreate.go @@ -108,7 +108,7 @@ func (m *Module) RulePOSTHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, apiRule) + apiutil.JSON(c, http.StatusOK, apiRule) } func validateCreateRule(form *apimodel.InstanceRuleCreateRequest) error { diff --git a/internal/api/client/admin/ruledelete.go b/internal/api/client/admin/ruledelete.go index 7281ed62e..dfa84615f 100644 --- a/internal/api/client/admin/ruledelete.go +++ b/internal/api/client/admin/ruledelete.go @@ -103,5 +103,5 @@ func (m *Module) RuleDELETEHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, apiRule) + apiutil.JSON(c, http.StatusOK, apiRule) } diff --git a/internal/api/client/admin/ruleget.go b/internal/api/client/admin/ruleget.go index 444820a3f..8281092fb 100644 --- a/internal/api/client/admin/ruleget.go +++ b/internal/api/client/admin/ruleget.go @@ -98,5 +98,5 @@ func (m *Module) RuleGETHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, rule) + apiutil.JSON(c, http.StatusOK, rule) } diff --git a/internal/api/client/admin/rulesget.go b/internal/api/client/admin/rulesget.go index 56f83866f..2cc9e0158 100644 --- a/internal/api/client/admin/rulesget.go +++ b/internal/api/client/admin/rulesget.go @@ -87,5 +87,5 @@ func (m *Module) RulesGETHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, resp) + apiutil.JSON(c, http.StatusOK, resp) } diff --git a/internal/api/client/admin/ruleupdate.go b/internal/api/client/admin/ruleupdate.go index 82ed41190..eafa3af34 100644 --- a/internal/api/client/admin/ruleupdate.go +++ b/internal/api/client/admin/ruleupdate.go @@ -123,5 +123,5 @@ func (m *Module) RulePATCHHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, apiRule) + apiutil.JSON(c, http.StatusOK, apiRule) } diff --git a/internal/api/client/apps/appcreate.go b/internal/api/client/apps/appcreate.go index ebbc462cd..8aa87c3b3 100644 --- a/internal/api/client/apps/appcreate.go +++ b/internal/api/client/apps/appcreate.go @@ -121,5 +121,5 @@ func (m *Module) AppsPOSTHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, apiApp) + apiutil.JSON(c, http.StatusOK, apiApp) } diff --git a/internal/api/client/blocks/blocksget.go b/internal/api/client/blocks/blocksget.go index 0761160bc..fe5104c61 100644 --- a/internal/api/client/blocks/blocksget.go +++ b/internal/api/client/blocks/blocksget.go @@ -142,5 +142,5 @@ func (m *Module) BlocksGETHandler(c *gin.Context) { c.Header("Link", resp.LinkHeader) } - c.JSON(http.StatusOK, resp.Items) + apiutil.JSON(c, http.StatusOK, resp.Items) } diff --git a/internal/api/client/bookmarks/bookmarksget.go b/internal/api/client/bookmarks/bookmarksget.go index 06aaaf578..17808642c 100644 --- a/internal/api/client/bookmarks/bookmarksget.go +++ b/internal/api/client/bookmarks/bookmarksget.go @@ -120,5 +120,6 @@ func (m *Module) BookmarksGETHandler(c *gin.Context) { if resp.LinkHeader != "" { c.Header("Link", resp.LinkHeader) } - c.JSON(http.StatusOK, resp.Items) + + apiutil.JSON(c, http.StatusOK, resp.Items) } diff --git a/internal/api/client/customemojis/customemojisget.go b/internal/api/client/customemojis/customemojisget.go index f9a529114..be595afd7 100644 --- a/internal/api/client/customemojis/customemojisget.go +++ b/internal/api/client/customemojis/customemojisget.go @@ -71,5 +71,5 @@ func (m *Module) CustomEmojisGETHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, emojis) + apiutil.JSON(c, http.StatusOK, emojis) } diff --git a/internal/api/client/favourites/favouritesget.go b/internal/api/client/favourites/favouritesget.go index 112bbd856..3ba2f9fcf 100644 --- a/internal/api/client/favourites/favouritesget.go +++ b/internal/api/client/favourites/favouritesget.go @@ -137,5 +137,6 @@ func (m *Module) FavouritesGETHandler(c *gin.Context) { if resp.LinkHeader != "" { c.Header("Link", resp.LinkHeader) } - c.JSON(http.StatusOK, resp.Items) + + apiutil.JSON(c, http.StatusOK, resp.Items) } diff --git a/internal/api/client/featuredtags/get.go b/internal/api/client/featuredtags/get.go index f4ba00a39..c1ee7ca2c 100644 --- a/internal/api/client/featuredtags/get.go +++ b/internal/api/client/featuredtags/get.go @@ -71,5 +71,5 @@ func (m *Module) FeaturedTagsGETHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, []interface{}{}) + apiutil.Data(c, http.StatusOK, apiutil.AppJSON, apiutil.EmptyJSONArray) } diff --git a/internal/api/client/filters/filtersget.go b/internal/api/client/filters/filtersget.go index 58ae23c32..38dd330a7 100644 --- a/internal/api/client/filters/filtersget.go +++ b/internal/api/client/filters/filtersget.go @@ -38,5 +38,5 @@ func (m *Module) FiltersGETHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, []string{}) + apiutil.Data(c, http.StatusOK, apiutil.AppJSON, apiutil.EmptyJSONArray) } diff --git a/internal/api/client/followrequests/authorize.go b/internal/api/client/followrequests/authorize.go index 707d3db26..406b54179 100644 --- a/internal/api/client/followrequests/authorize.go +++ b/internal/api/client/followrequests/authorize.go @@ -93,5 +93,5 @@ func (m *Module) FollowRequestAuthorizePOSTHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, relationship) + apiutil.JSON(c, http.StatusOK, relationship) } diff --git a/internal/api/client/followrequests/get.go b/internal/api/client/followrequests/get.go index af2f3741c..40cdceaea 100644 --- a/internal/api/client/followrequests/get.go +++ b/internal/api/client/followrequests/get.go @@ -139,5 +139,5 @@ func (m *Module) FollowRequestGETHandler(c *gin.Context) { c.Header("Link", resp.LinkHeader) } - c.JSON(http.StatusOK, resp.Items) + apiutil.JSON(c, http.StatusOK, resp.Items) } diff --git a/internal/api/client/followrequests/reject.go b/internal/api/client/followrequests/reject.go index 6514a615e..a8189b78a 100644 --- a/internal/api/client/followrequests/reject.go +++ b/internal/api/client/followrequests/reject.go @@ -91,5 +91,5 @@ func (m *Module) FollowRequestRejectPOSTHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, relationship) + apiutil.JSON(c, http.StatusOK, relationship) } diff --git a/internal/api/client/instance/instanceget.go b/internal/api/client/instance/instanceget.go index 57d47b902..6690e7e98 100644 --- a/internal/api/client/instance/instanceget.go +++ b/internal/api/client/instance/instanceget.go @@ -58,7 +58,7 @@ func (m *Module) InstanceInformationGETHandlerV1(c *gin.Context) { return } - c.JSON(http.StatusOK, instance) + apiutil.JSON(c, http.StatusOK, instance) } // InstanceInformationGETHandlerV2 swagger:operation GET /api/v2/instance instanceGetV2 @@ -93,5 +93,5 @@ func (m *Module) InstanceInformationGETHandlerV2(c *gin.Context) { return } - c.JSON(http.StatusOK, instance) + apiutil.JSON(c, http.StatusOK, instance) } diff --git a/internal/api/client/instance/instancepatch.go b/internal/api/client/instance/instancepatch.go index 885ad19c8..484579cf2 100644 --- a/internal/api/client/instance/instancepatch.go +++ b/internal/api/client/instance/instancepatch.go @@ -161,7 +161,7 @@ func (m *Module) InstanceUpdatePATCHHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, i) + apiutil.JSON(c, http.StatusOK, i) } func validateInstanceUpdate(form *apimodel.InstanceSettingsUpdateRequest) error { diff --git a/internal/api/client/instance/instancepatch_test.go b/internal/api/client/instance/instancepatch_test.go index 1f8b691be..0c270de21 100644 --- a/internal/api/client/instance/instancepatch_test.go +++ b/internal/api/client/instance/instancepatch_test.go @@ -78,8 +78,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch1() { "uri": "http://localhost:8080", "account_domain": "localhost:8080", "title": "Example Instance", - "description": "\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e", - "short_description": "\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e", + "description": "

This is the GoToSocial testrig. It doesn't federate or anything.

When the testrig is shut down, all data on it will be deleted.

Don't use this in production!

", + "short_description": "

This is the GoToSocial testrig. It doesn't federate or anything.

When the testrig is shut down, all data on it will be deleted.

Don't use this in production!

", "email": "someone@example.org", "version": "0.0.0-testrig", "languages": [ @@ -195,8 +195,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch2() { "uri": "http://localhost:8080", "account_domain": "localhost:8080", "title": "Geoff's Instance", - "description": "\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e", - "short_description": "\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e", + "description": "

This is the GoToSocial testrig. It doesn't federate or anything.

When the testrig is shut down, all data on it will be deleted.

Don't use this in production!

", + "short_description": "

This is the GoToSocial testrig. It doesn't federate or anything.

When the testrig is shut down, all data on it will be deleted.

Don't use this in production!

", "email": "admin@example.org", "version": "0.0.0-testrig", "languages": [ @@ -312,8 +312,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch3() { "uri": "http://localhost:8080", "account_domain": "localhost:8080", "title": "GoToSocial Testrig Instance", - "description": "\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e", - "short_description": "\u003cp\u003eThis is some html, which is \u003cem\u003eallowed\u003c/em\u003e in short descriptions.\u003c/p\u003e", + "description": "

This is the GoToSocial testrig. It doesn't federate or anything.

When the testrig is shut down, all data on it will be deleted.

Don't use this in production!

", + "short_description": "

This is some html, which is allowed in short descriptions.

", "email": "admin@example.org", "version": "0.0.0-testrig", "languages": [ @@ -480,8 +480,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch6() { "uri": "http://localhost:8080", "account_domain": "localhost:8080", "title": "GoToSocial Testrig Instance", - "description": "\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e", - "short_description": "\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e", + "description": "

This is the GoToSocial testrig. It doesn't federate or anything.

When the testrig is shut down, all data on it will be deleted.

Don't use this in production!

", + "short_description": "

This is the GoToSocial testrig. It doesn't federate or anything.

When the testrig is shut down, all data on it will be deleted.

Don't use this in production!

", "email": "", "version": "0.0.0-testrig", "languages": [ @@ -619,8 +619,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch8() { "uri": "http://localhost:8080", "account_domain": "localhost:8080", "title": "GoToSocial Testrig Instance", - "description": "\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e", - "short_description": "\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e", + "description": "

This is the GoToSocial testrig. It doesn't federate or anything.

When the testrig is shut down, all data on it will be deleted.

Don't use this in production!

", + "short_description": "

This is the GoToSocial testrig. It doesn't federate or anything.

When the testrig is shut down, all data on it will be deleted.

Don't use this in production!

", "email": "admin@example.org", "version": "0.0.0-testrig", "languages": [ @@ -773,8 +773,8 @@ func (suite *InstancePatchTestSuite) TestInstancePatch9() { "uri": "http://localhost:8080", "account_domain": "localhost:8080", "title": "GoToSocial Testrig Instance", - "description": "\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e", - "short_description": "\u003cp\u003eThis is the GoToSocial testrig. It doesn't federate or anything.\u003c/p\u003e\u003cp\u003eWhen the testrig is shut down, all data on it will be deleted.\u003c/p\u003e\u003cp\u003eDon't use this in production!\u003c/p\u003e", + "description": "

This is the GoToSocial testrig. It doesn't federate or anything.

When the testrig is shut down, all data on it will be deleted.

Don't use this in production!

", + "short_description": "

This is the GoToSocial testrig. It doesn't federate or anything.

When the testrig is shut down, all data on it will be deleted.

Don't use this in production!

", "email": "admin@example.org", "version": "0.0.0-testrig", "languages": [ diff --git a/internal/api/client/instance/instancepeersget.go b/internal/api/client/instance/instancepeersget.go index 05085bc0f..c278c0674 100644 --- a/internal/api/client/instance/instancepeersget.go +++ b/internal/api/client/instance/instancepeersget.go @@ -156,5 +156,5 @@ func (m *Module) InstancePeersGETHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, data) + apiutil.JSON(c, http.StatusOK, data) } diff --git a/internal/api/client/instance/instancerulesget.go b/internal/api/client/instance/instancerulesget.go index 5cc99ba41..9df1b8fbe 100644 --- a/internal/api/client/instance/instancerulesget.go +++ b/internal/api/client/instance/instancerulesget.go @@ -67,5 +67,5 @@ func (m *Module) InstanceRulesGETHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, resp) + apiutil.JSON(c, http.StatusOK, resp) } diff --git a/internal/api/client/lists/listaccounts.go b/internal/api/client/lists/listaccounts.go index 6feffb1e8..e1d340ebb 100644 --- a/internal/api/client/lists/listaccounts.go +++ b/internal/api/client/lists/listaccounts.go @@ -174,5 +174,6 @@ func (m *Module) ListAccountsGETHandler(c *gin.Context) { if resp.LinkHeader != "" { c.Header("Link", resp.LinkHeader) } - c.JSON(http.StatusOK, resp.Items) + + apiutil.JSON(c, http.StatusOK, resp.Items) } diff --git a/internal/api/client/lists/listaccountsadd.go b/internal/api/client/lists/listaccountsadd.go index a2a74e475..6fb5eab3c 100644 --- a/internal/api/client/lists/listaccountsadd.go +++ b/internal/api/client/lists/listaccountsadd.go @@ -116,5 +116,5 @@ func (m *Module) ListAccountsPOSTHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, gin.H{}) + apiutil.Data(c, http.StatusOK, apiutil.AppJSON, apiutil.EmptyJSONObject) } diff --git a/internal/api/client/lists/listaccountsremove.go b/internal/api/client/lists/listaccountsremove.go index 2a89cb960..50e53a3a3 100644 --- a/internal/api/client/lists/listaccountsremove.go +++ b/internal/api/client/lists/listaccountsremove.go @@ -126,5 +126,5 @@ func (m *Module) ListAccountsDELETEHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, gin.H{}) + apiutil.Data(c, http.StatusOK, apiutil.AppJSON, apiutil.EmptyJSONObject) } diff --git a/internal/api/client/lists/listcreate.go b/internal/api/client/lists/listcreate.go index 1405aedd2..4228e5fff 100644 --- a/internal/api/client/lists/listcreate.go +++ b/internal/api/client/lists/listcreate.go @@ -102,5 +102,5 @@ func (m *Module) ListCreatePOSTHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, apiList) + apiutil.JSON(c, http.StatusOK, apiList) } diff --git a/internal/api/client/lists/listdelete.go b/internal/api/client/lists/listdelete.go index e0139b574..b03f21e5a 100644 --- a/internal/api/client/lists/listdelete.go +++ b/internal/api/client/lists/listdelete.go @@ -87,5 +87,5 @@ func (m *Module) ListDELETEHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, gin.H{}) + apiutil.Data(c, http.StatusOK, apiutil.AppJSON, apiutil.EmptyJSONObject) } diff --git a/internal/api/client/lists/listget.go b/internal/api/client/lists/listget.go index f8dc54eb1..34b21d28b 100644 --- a/internal/api/client/lists/listget.go +++ b/internal/api/client/lists/listget.go @@ -91,5 +91,5 @@ func (m *Module) ListGETHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, resp) + apiutil.JSON(c, http.StatusOK, resp) } diff --git a/internal/api/client/lists/listsget.go b/internal/api/client/lists/listsget.go index f16152a9d..6bfc3c883 100644 --- a/internal/api/client/lists/listsget.go +++ b/internal/api/client/lists/listsget.go @@ -77,5 +77,5 @@ func (m *Module) ListsGETHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, lists) + apiutil.JSON(c, http.StatusOK, lists) } diff --git a/internal/api/client/lists/listupdate.go b/internal/api/client/lists/listupdate.go index 97c0cc636..58a4cf1c4 100644 --- a/internal/api/client/lists/listupdate.go +++ b/internal/api/client/lists/listupdate.go @@ -148,5 +148,5 @@ func (m *Module) ListUpdatePUTHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, apiList) + apiutil.JSON(c, http.StatusOK, apiList) } diff --git a/internal/api/client/markers/markersget.go b/internal/api/client/markers/markersget.go index eb403dcc6..9f4fc4270 100644 --- a/internal/api/client/markers/markersget.go +++ b/internal/api/client/markers/markersget.go @@ -84,7 +84,7 @@ func (m *Module) MarkersGETHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, marker) + apiutil.JSON(c, http.StatusOK, marker) } // parseMarkerNames turns a list of strings into a set of valid marker timeline names, or returns an error. diff --git a/internal/api/client/markers/markerspost.go b/internal/api/client/markers/markerspost.go index 3167becac..8fe40c798 100644 --- a/internal/api/client/markers/markerspost.go +++ b/internal/api/client/markers/markerspost.go @@ -106,5 +106,5 @@ func (m *Module) MarkersPOSTHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, marker) + apiutil.JSON(c, http.StatusOK, marker) } diff --git a/internal/api/client/media/mediacreate.go b/internal/api/client/media/mediacreate.go index d2264bb0d..daa2e5bb7 100644 --- a/internal/api/client/media/mediacreate.go +++ b/internal/api/client/media/mediacreate.go @@ -139,7 +139,7 @@ func (m *Module) MediaCreatePOSTHandler(c *gin.Context) { apiAttachment.URL = nil } - c.JSON(http.StatusOK, apiAttachment) + apiutil.JSON(c, http.StatusOK, apiAttachment) } func validateCreateMedia(form *apimodel.AttachmentRequest) error { diff --git a/internal/api/client/media/mediaget.go b/internal/api/client/media/mediaget.go index 431f73d65..8456f85d8 100644 --- a/internal/api/client/media/mediaget.go +++ b/internal/api/client/media/mediaget.go @@ -98,5 +98,5 @@ func (m *Module) MediaGETHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, attachment) + apiutil.JSON(c, http.StatusOK, attachment) } diff --git a/internal/api/client/media/mediaupdate.go b/internal/api/client/media/mediaupdate.go index 032cfd705..8378502e8 100644 --- a/internal/api/client/media/mediaupdate.go +++ b/internal/api/client/media/mediaupdate.go @@ -141,7 +141,7 @@ func (m *Module) MediaPUTHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, attachment) + apiutil.JSON(c, http.StatusOK, attachment) } func validateUpdateMedia(form *apimodel.AttachmentUpdateRequest) error { diff --git a/internal/api/client/notifications/notificationget.go b/internal/api/client/notifications/notificationget.go index 98e32498b..551eeca39 100644 --- a/internal/api/client/notifications/notificationget.go +++ b/internal/api/client/notifications/notificationget.go @@ -83,5 +83,5 @@ func (m *Module) NotificationGETHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, resp) + apiutil.JSON(c, http.StatusOK, resp) } diff --git a/internal/api/client/notifications/notificationsclear.go b/internal/api/client/notifications/notificationsclear.go index 4b63db283..2d7da3c6b 100644 --- a/internal/api/client/notifications/notificationsclear.go +++ b/internal/api/client/notifications/notificationsclear.go @@ -75,5 +75,5 @@ func (m *Module) NotificationsClearPOSTHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, struct{}{}) + apiutil.Data(c, http.StatusOK, apiutil.AppJSON, apiutil.EmptyJSONObject) } diff --git a/internal/api/client/notifications/notificationsget.go b/internal/api/client/notifications/notificationsget.go index fd175a115..da43cffec 100644 --- a/internal/api/client/notifications/notificationsget.go +++ b/internal/api/client/notifications/notificationsget.go @@ -155,5 +155,6 @@ func (m *Module) NotificationsGETHandler(c *gin.Context) { if resp.LinkHeader != "" { c.Header("Link", resp.LinkHeader) } - c.JSON(http.StatusOK, resp.Items) + + apiutil.JSON(c, http.StatusOK, resp.Items) } diff --git a/internal/api/client/polls/polls_get.go b/internal/api/client/polls/polls_get.go index 0b15c0ed1..fc89255e9 100644 --- a/internal/api/client/polls/polls_get.go +++ b/internal/api/client/polls/polls_get.go @@ -96,5 +96,5 @@ func (m *Module) PollGETHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, poll) + apiutil.JSON(c, http.StatusOK, poll) } diff --git a/internal/api/client/polls/polls_vote.go b/internal/api/client/polls/polls_vote.go index e5281b3fc..0ab5ac20c 100644 --- a/internal/api/client/polls/polls_vote.go +++ b/internal/api/client/polls/polls_vote.go @@ -117,7 +117,7 @@ func (m *Module) PollVotePOSTHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, poll) + apiutil.JSON(c, http.StatusOK, poll) } func bindChoices(c *gin.Context) ([]int, error) { diff --git a/internal/api/client/preferences/preferencesget.go b/internal/api/client/preferences/preferencesget.go index 2834134de..4a6cb4b55 100644 --- a/internal/api/client/preferences/preferencesget.go +++ b/internal/api/client/preferences/preferencesget.go @@ -87,5 +87,6 @@ func (m *Module) PreferencesGETHandler(c *gin.Context) { apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) return } - c.JSON(http.StatusOK, resp) + + apiutil.JSON(c, http.StatusOK, resp) } diff --git a/internal/api/client/reports/reportcreate.go b/internal/api/client/reports/reportcreate.go index a4fa01148..a34b8d52e 100644 --- a/internal/api/client/reports/reportcreate.go +++ b/internal/api/client/reports/reportcreate.go @@ -107,5 +107,5 @@ func (m *Module) ReportPOSTHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, apiReport) + apiutil.JSON(c, http.StatusOK, apiReport) } diff --git a/internal/api/client/reports/reportget.go b/internal/api/client/reports/reportget.go index 0d5d16da2..4a9b06664 100644 --- a/internal/api/client/reports/reportget.go +++ b/internal/api/client/reports/reportget.go @@ -90,5 +90,5 @@ func (m *Module) ReportGETHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, report) + apiutil.JSON(c, http.StatusOK, report) } diff --git a/internal/api/client/reports/reportsget.go b/internal/api/client/reports/reportsget.go index e290608eb..ba47f1b8b 100644 --- a/internal/api/client/reports/reportsget.go +++ b/internal/api/client/reports/reportsget.go @@ -168,5 +168,6 @@ func (m *Module) ReportsGETHandler(c *gin.Context) { if resp.LinkHeader != "" { c.Header("Link", resp.LinkHeader) } - c.JSON(http.StatusOK, resp.Items) + + apiutil.JSON(c, http.StatusOK, resp.Items) } diff --git a/internal/api/client/search/searchget.go b/internal/api/client/search/searchget.go index 2759feb5b..909c14f24 100644 --- a/internal/api/client/search/searchget.go +++ b/internal/api/client/search/searchget.go @@ -235,5 +235,5 @@ func (m *Module) SearchGETHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, results) + apiutil.JSON(c, http.StatusOK, results) } diff --git a/internal/api/client/timelines/home.go b/internal/api/client/timelines/home.go index 963096f59..a7e7717da 100644 --- a/internal/api/client/timelines/home.go +++ b/internal/api/client/timelines/home.go @@ -147,5 +147,6 @@ func (m *Module) HomeTimelineGETHandler(c *gin.Context) { if resp.LinkHeader != "" { c.Header("Link", resp.LinkHeader) } - c.JSON(http.StatusOK, resp.Items) + + apiutil.JSON(c, http.StatusOK, resp.Items) } diff --git a/internal/api/client/timelines/list.go b/internal/api/client/timelines/list.go index 2e13e32cd..dc5f21424 100644 --- a/internal/api/client/timelines/list.go +++ b/internal/api/client/timelines/list.go @@ -145,5 +145,6 @@ func (m *Module) ListTimelineGETHandler(c *gin.Context) { if resp.LinkHeader != "" { c.Header("Link", resp.LinkHeader) } - c.JSON(http.StatusOK, resp.Items) + + apiutil.JSON(c, http.StatusOK, resp.Items) } diff --git a/internal/api/client/timelines/public.go b/internal/api/client/timelines/public.go index 7b8acf1ca..8eb34edc7 100644 --- a/internal/api/client/timelines/public.go +++ b/internal/api/client/timelines/public.go @@ -158,5 +158,6 @@ func (m *Module) PublicTimelineGETHandler(c *gin.Context) { if resp.LinkHeader != "" { c.Header("Link", resp.LinkHeader) } - c.JSON(http.StatusOK, resp.Items) + + apiutil.JSON(c, http.StatusOK, resp.Items) } diff --git a/internal/api/client/timelines/tag.go b/internal/api/client/timelines/tag.go index 58754705b..0d95a6c58 100644 --- a/internal/api/client/timelines/tag.go +++ b/internal/api/client/timelines/tag.go @@ -142,5 +142,6 @@ func (m *Module) TagTimelineGETHandler(c *gin.Context) { if resp.LinkHeader != "" { c.Header("Link", resp.LinkHeader) } - c.JSON(http.StatusOK, resp.Items) + + apiutil.JSON(c, http.StatusOK, resp.Items) } diff --git a/internal/api/client/user/passwordchange.go b/internal/api/client/user/passwordchange.go index 8a1487ac0..c2928e9e5 100644 --- a/internal/api/client/user/passwordchange.go +++ b/internal/api/client/user/passwordchange.go @@ -99,5 +99,5 @@ func (m *Module) PasswordChangePOSTHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, gin.H{"status": "OK"}) + apiutil.Data(c, http.StatusOK, apiutil.AppJSON, apiutil.StatusOKJSON) } diff --git a/internal/api/fileserver/servefile.go b/internal/api/fileserver/servefile.go index 12d24ca76..5fe79f1d7 100644 --- a/internal/api/fileserver/servefile.go +++ b/internal/api/fileserver/servefile.go @@ -91,9 +91,8 @@ func (m *Module) ServeFile(c *gin.Context) { } if content.URL != nil { - // This is a non-local, non-proxied S3 file we're redirecting to. - // Derive the max-age value from how long the link has left until - // it expires. + // This is a non-local, non-proxied S3 file we're redirecting to. Derive + // the max-age value from how long the link has left until it expires. maxAge := int(time.Until(content.URL.Expiry).Seconds()) c.Header("Cache-Control", "private, max-age="+strconv.Itoa(maxAge)+", immutable") c.Redirect(http.StatusFound, content.URL.String()) @@ -110,7 +109,7 @@ func (m *Module) ServeFile(c *gin.Context) { // TODO: if the requester only accepts text/html we should try to serve them *something*. // This is mostly needed because when sharing a link to a gts-hosted file on something like mastodon, the masto servers will // attempt to look up the content to provide a preview of the link, and they ask for text/html. - format, err := apiutil.NegotiateAccept(c, apiutil.MIME(content.ContentType)) + contentType, err := apiutil.NegotiateAccept(c, content.ContentType) if err != nil { apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) return @@ -118,7 +117,7 @@ func (m *Module) ServeFile(c *gin.Context) { // if this is a head request, just return info + throw the reader away if c.Request.Method == http.MethodHead { - c.Header("Content-Type", format) + c.Header("Content-Type", contentType) c.Header("Content-Length", strconv.FormatInt(content.ContentLength, 10)) c.Status(http.StatusOK) return @@ -128,12 +127,12 @@ func (m *Module) ServeFile(c *gin.Context) { rng := c.GetHeader("Range") if rng == "" { // This is a simple query for the whole file, so do a read from whole reader. - c.DataFromReader(http.StatusOK, content.ContentLength, format, content.Content, nil) + c.DataFromReader(http.StatusOK, content.ContentLength, contentType, content.Content, nil) return } // Set known content-type and serve range. - c.Header("Content-Type", format) + c.Header("Content-Type", contentType) serveFileRange( c.Writer, c.Request, diff --git a/internal/api/nodeinfo/nodeinfoget.go b/internal/api/nodeinfo/nodeinfoget.go index 3158eeb9a..368a5503d 100644 --- a/internal/api/nodeinfo/nodeinfoget.go +++ b/internal/api/nodeinfo/nodeinfoget.go @@ -18,7 +18,6 @@ package nodeinfo import ( - "encoding/json" "net/http" "github.com/gin-gonic/gin" @@ -55,11 +54,12 @@ func (m *Module) NodeInfo2GETHandler(c *gin.Context) { return } - b, err := json.Marshal(nodeInfo) - if err != nil { - apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1) - return - } - - c.Data(http.StatusOK, NodeInfo2ContentType, b) + // Encode JSON HTTP response. + apiutil.EncodeJSONResponse( + c.Writer, + c.Request, + http.StatusOK, + NodeInfo2ContentType, + nodeInfo, + ) } diff --git a/internal/api/util/errorhandling.go b/internal/api/util/errorhandling.go index 4fa544ffd..8bb251040 100644 --- a/internal/api/util/errorhandling.go +++ b/internal/api/util/errorhandling.go @@ -55,7 +55,9 @@ func NotFoundHandler(c *gin.Context, instanceGet func(ctx context.Context) (*api "requestID": gtscontext.RequestID(ctx), }) default: - c.JSON(http.StatusNotFound, gin.H{"error": errWithCode.Safe()}) + JSON(c, http.StatusNotFound, map[string]string{ + "error": errWithCode.Safe(), + }) } } @@ -78,7 +80,9 @@ func genericErrorHandler(c *gin.Context, instanceGet func(ctx context.Context) ( "requestID": gtscontext.RequestID(ctx), }) default: - c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) + JSON(c, errWithCode.Code(), map[string]string{ + "error": errWithCode.Safe(), + }) } } @@ -102,7 +106,7 @@ func ErrorHandler( c *gin.Context, errWithCode gtserror.WithCode, instanceGet func(ctx context.Context) (*apimodel.InstanceV1, gtserror.WithCode), - offers ...MIME, + offers ...string, ) { if ctxErr := c.Request.Context().Err(); ctxErr != nil { // Context error means either client has left already, @@ -175,7 +179,7 @@ func OAuthErrorHandler(c *gin.Context, errWithCode gtserror.WithCode) { l.Debug("handling OAuth error") } - c.JSON(statusCode, gin.H{ + JSON(c, statusCode, map[string]string{ "error": errWithCode.Error(), "error_description": errWithCode.Safe(), }) diff --git a/internal/api/util/mime.go b/internal/api/util/mime.go index edd0dcecf..ad1b405cd 100644 --- a/internal/api/util/mime.go +++ b/internal/api/util/mime.go @@ -17,20 +17,18 @@ package util -// MIME represents a mime-type. -type MIME string - const ( - AppJSON MIME = `application/json` - AppXML MIME = `application/xml` - AppXMLXRD MIME = `application/xrd+xml` - AppRSSXML MIME = `application/rss+xml` - AppActivityJSON MIME = `application/activity+json` - AppActivityLDJSON MIME = `application/ld+json; profile="https://www.w3.org/ns/activitystreams"` - AppJRDJSON MIME = `application/jrd+json` // https://www.rfc-editor.org/rfc/rfc7033#section-10.2 - AppForm MIME = `application/x-www-form-urlencoded` - MultipartForm MIME = `multipart/form-data` - TextXML MIME = `text/xml` - TextHTML MIME = `text/html` - TextCSS MIME = `text/css` + // Possible GoToSocial mimetypes. + AppJSON = `application/json` + AppXML = `application/xml` + AppXMLXRD = `application/xrd+xml` + AppRSSXML = `application/rss+xml` + AppActivityJSON = `application/activity+json` + AppActivityLDJSON = `application/ld+json; profile="https://www.w3.org/ns/activitystreams"` + AppJRDJSON = `application/jrd+json` // https://www.rfc-editor.org/rfc/rfc7033#section-10.2 + AppForm = `application/x-www-form-urlencoded` + MultipartForm = `multipart/form-data` + TextXML = `text/xml` + TextHTML = `text/html` + TextCSS = `text/css` ) diff --git a/internal/api/util/negotiate.go b/internal/api/util/negotiate.go index f4268511b..5b4f54bc6 100644 --- a/internal/api/util/negotiate.go +++ b/internal/api/util/negotiate.go @@ -26,7 +26,7 @@ import ( ) // JSONAcceptHeaders is a slice of offers that just contains application/json types. -var JSONAcceptHeaders = []MIME{ +var JSONAcceptHeaders = []string{ AppJSON, } @@ -34,7 +34,7 @@ var JSONAcceptHeaders = []MIME{ // jrd+json content type, but will be chill and fall back to app/json. // This is to be used specifically for webfinger responses. // See https://www.rfc-editor.org/rfc/rfc7033#section-10.2 -var WebfingerJSONAcceptHeaders = []MIME{ +var WebfingerJSONAcceptHeaders = []string{ AppJRDJSON, AppJSON, } @@ -42,13 +42,13 @@ var WebfingerJSONAcceptHeaders = []MIME{ // JSONOrHTMLAcceptHeaders is a slice of offers that prefers AppJSON and will // fall back to HTML if necessary. This is useful for error handling, since it can // be used to serve a nice HTML page if the caller accepts that, or just JSON if not. -var JSONOrHTMLAcceptHeaders = []MIME{ +var JSONOrHTMLAcceptHeaders = []string{ AppJSON, TextHTML, } // HTMLAcceptHeaders is a slice of offers that just contains text/html types. -var HTMLAcceptHeaders = []MIME{ +var HTMLAcceptHeaders = []string{ TextHTML, } @@ -57,7 +57,7 @@ var HTMLAcceptHeaders = []MIME{ // but which should also be able to serve ActivityPub as a fallback. // // https://www.w3.org/TR/activitypub/#retrieving-objects -var HTMLOrActivityPubHeaders = []MIME{ +var HTMLOrActivityPubHeaders = []string{ TextHTML, AppActivityLDJSON, AppActivityJSON, @@ -68,7 +68,7 @@ var HTMLOrActivityPubHeaders = []MIME{ // which a user might also go to in their browser sometimes. // // https://www.w3.org/TR/activitypub/#retrieving-objects -var ActivityPubOrHTMLHeaders = []MIME{ +var ActivityPubOrHTMLHeaders = []string{ AppActivityLDJSON, AppActivityJSON, TextHTML, @@ -78,12 +78,12 @@ var ActivityPubOrHTMLHeaders = []MIME{ // This is useful for URLs should only serve ActivityPub. // // https://www.w3.org/TR/activitypub/#retrieving-objects -var ActivityPubHeaders = []MIME{ +var ActivityPubHeaders = []string{ AppActivityLDJSON, AppActivityJSON, } -var HostMetaHeaders = []MIME{ +var HostMetaHeaders = []string{ AppXMLXRD, AppXML, } @@ -109,7 +109,7 @@ var HostMetaHeaders = []MIME{ // often-used Accept types. // // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation#server-driven_content_negotiation -func NegotiateAccept(c *gin.Context, offers ...MIME) (string, error) { +func NegotiateAccept(c *gin.Context, offers ...string) (string, error) { if len(offers) == 0 { return "", errors.New("no format offered") } diff --git a/internal/api/util/negotiate_test.go b/internal/api/util/negotiate_test.go index a8b28b55f..d1b08695f 100644 --- a/internal/api/util/negotiate_test.go +++ b/internal/api/util/negotiate_test.go @@ -9,7 +9,7 @@ import ( "github.com/gin-gonic/gin" ) -type testMIMES []MIME +type testMIMES []string func (tm testMIMES) String(t *testing.T) string { t.Helper() diff --git a/internal/api/util/response.go b/internal/api/util/response.go new file mode 100644 index 000000000..e22bac545 --- /dev/null +++ b/internal/api/util/response.go @@ -0,0 +1,282 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package util + +import ( + "encoding/json" + "encoding/xml" + "io" + "net/http" + "strconv" + "sync" + + "codeberg.org/gruf/go-byteutil" + "codeberg.org/gruf/go-fastcopy" + "github.com/gin-gonic/gin" + "github.com/superseriousbusiness/gotosocial/internal/log" +) + +var ( + // Pre-preared response body data. + StatusOKJSON = mustJSON(map[string]string{ + "status": http.StatusText(http.StatusOK), + }) + StatusAcceptedJSON = mustJSON(map[string]string{ + "status": http.StatusText(http.StatusAccepted), + }) + StatusInternalServerErrorJSON = mustJSON(map[string]string{ + "status": http.StatusText(http.StatusInternalServerError), + }) + EmptyJSONObject = mustJSON("{}") + EmptyJSONArray = mustJSON("[]") + + // write buffer pool. + bufPool sync.Pool +) + +// JSON calls EncodeJSONResponse() using gin.Context{}, with content-type = AppJSON, +// This function handles the case of JSON unmarshal errors and pools read buffers. +func JSON(c *gin.Context, code int, data any) { + EncodeJSONResponse(c.Writer, c.Request, code, AppJSON, data) +} + +// JSON calls EncodeJSONResponse() using gin.Context{}, with given content-type. +// This function handles the case of JSON unmarshal errors and pools read buffers. +func JSONType(c *gin.Context, code int, contentType string, data any) { + EncodeJSONResponse(c.Writer, c.Request, code, contentType, data) +} + +// Data calls WriteResponseBytes() using gin.Context{}, with given content-type. +func Data(c *gin.Context, code int, contentType string, data []byte) { + WriteResponseBytes(c.Writer, c.Request, code, contentType, data) +} + +// WriteResponse buffered streams 'data' as HTTP response +// to ResponseWriter with given status code content-type. +func WriteResponse( + rw http.ResponseWriter, + r *http.Request, + statusCode int, + contentType string, + data io.Reader, + length int64, +) { + if length < 0 { + // The worst-case scenario, length is not known so we need to + // read the entire thing into memory to know length & respond. + writeResponseUnknownLength(rw, r, statusCode, contentType, data) + return + } + + // The best-case scenario, stream content of known length. + rw.Header().Set("Content-Type", contentType) + rw.Header().Set("Content-Length", strconv.FormatInt(length, 10)) + rw.WriteHeader(statusCode) + if _, err := fastcopy.Copy(rw, data); err != nil { + log.Errorf(r.Context(), "error streaming: %v", err) + } +} + +// WriteResponseBytes is functionally similar to +// WriteResponse except that it takes prepared bytes. +func WriteResponseBytes( + rw http.ResponseWriter, + r *http.Request, + statusCode int, + contentType string, + data []byte, +) { + rw.Header().Set("Content-Type", contentType) + rw.Header().Set("Content-Length", strconv.Itoa(len(data))) + rw.WriteHeader(statusCode) + if _, err := rw.Write(data); err != nil && err != io.EOF { + log.Errorf(r.Context(), "error writing: %v", err) + } +} + +// EncodeJSONResponse encodes 'data' as JSON HTTP response +// to ResponseWriter with given status code, content-type. +func EncodeJSONResponse( + rw http.ResponseWriter, + r *http.Request, + statusCode int, + contentType string, + data any, +) { + // Acquire buffer. + buf := getBuf() + + // Wrap buffer in JSON encoder. + enc := json.NewEncoder(buf) + enc.SetEscapeHTML(false) + + // Encode JSON data into byte buffer. + if err := enc.Encode(data); err == nil { + + // Drop new-line added by encoder. + if buf.B[len(buf.B)-1] == '\n' { + buf.B = buf.B[:len(buf.B)-1] + } + + // Respond with the now-known + // size byte slice within buf. + WriteResponseBytes(rw, r, + statusCode, + contentType, + buf.B, + ) + } else { + // This will always be a JSON error, we + // can't really add any more useful context. + log.Error(r.Context(), err) + + // Any error returned here is unrecoverable, + // set Internal Server Error JSON response. + WriteResponseBytes(rw, r, + http.StatusInternalServerError, + AppJSON, + StatusInternalServerErrorJSON, + ) + } + + // Release. + putBuf(buf) +} + +// EncodeJSONResponse encodes 'data' as XML HTTP response +// to ResponseWriter with given status code, content-type. +func EncodeXMLResponse( + rw http.ResponseWriter, + r *http.Request, + statusCode int, + contentType string, + data any, +) { + // Acquire buffer. + buf := getBuf() + + // Write XML header string to buf. + buf.B = append(buf.B, xml.Header...) + + // Wrap buffer in XML encoder. + enc := xml.NewEncoder(buf) + + // Encode JSON data into byte buffer. + if err := enc.Encode(data); err == nil { + + // Respond with the now-known + // size byte slice within buf. + WriteResponseBytes(rw, r, + statusCode, + contentType, + buf.B, + ) + } else { + // This will always be an XML error, we + // can't really add any more useful context. + log.Error(r.Context(), err) + + // Any error returned here is unrecoverable, + // set Internal Server Error JSON response. + WriteResponseBytes(rw, r, + http.StatusInternalServerError, + AppJSON, + StatusInternalServerErrorJSON, + ) + } + + // Release. + putBuf(buf) +} + +// writeResponseUnknownLength handles reading data of unknown legnth +// efficiently into memory, and passing on to WriteResponseBytes(). +func writeResponseUnknownLength( + rw http.ResponseWriter, + r *http.Request, + statusCode int, + contentType string, + data io.Reader, +) { + // Acquire buffer. + buf := getBuf() + + // Read content into buffer. + _, err := buf.ReadFrom(data) + + if err == nil { + + // Respond with the now-known + // size byte slice within buf. + WriteResponseBytes(rw, r, + statusCode, + contentType, + buf.B, + ) + } else { + // This will always be a reader error (non EOF), + // but that doesn't mean the writer is closed yet! + log.Errorf(r.Context(), "error reading: %v", err) + + // Any error returned here is unrecoverable, + // set Internal Server Error JSON response. + WriteResponseBytes(rw, r, + http.StatusInternalServerError, + AppJSON, + StatusInternalServerErrorJSON, + ) + } + + // Release. + putBuf(buf) +} + +func getBuf() *byteutil.Buffer { + // acquire buffer from pool. + buf, _ := bufPool.Get().(*byteutil.Buffer) + + if buf == nil { + // alloc new buf if needed. + buf = new(byteutil.Buffer) + buf.B = make([]byte, 0, 4096) + } + + return buf +} + +func putBuf(buf *byteutil.Buffer) { + if cap(buf.B) >= int(^uint16(0)) { + // drop buffers of large size. + return + } + + // ensure empty. + buf.Reset() + + // release to pool. + bufPool.Put(buf) +} + +// mustJSON converts data to JSON, else panicking. +func mustJSON(data any) []byte { + b, err := json.Marshal(data) + if err != nil { + panic(err) + } + return b +} diff --git a/internal/api/wellknown/hostmeta/hostmetaget.go b/internal/api/wellknown/hostmeta/hostmetaget.go index c74a2e246..131e2ac58 100644 --- a/internal/api/wellknown/hostmeta/hostmetaget.go +++ b/internal/api/wellknown/hostmeta/hostmetaget.go @@ -18,8 +18,6 @@ package hostmeta import ( - "bytes" - "encoding/xml" "net/http" "github.com/gin-gonic/gin" @@ -52,21 +50,12 @@ func (m *Module) HostMetaGETHandler(c *gin.Context) { hostMeta := m.processor.Fedi().HostMetaGet() - // this setup with a separate buffer we encode into is used because - // xml.Marshal does not emit xml.Header by itself - var buf bytes.Buffer - - // Preallocate buffer of reasonable length. - buf.Grow(len(xml.Header) + 64) - - // No need to check for error on write to buffer. - _, _ = buf.WriteString(xml.Header) - - // Encode host-meta as XML to in-memory buffer. - if err := xml.NewEncoder(&buf).Encode(hostMeta); err != nil { - apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1) - return - } - - c.Data(http.StatusOK, HostMetaContentType, buf.Bytes()) + // Encode XML HTTP response. + apiutil.EncodeXMLResponse( + c.Writer, + c.Request, + http.StatusOK, + HostMetaContentType, + hostMeta, + ) } diff --git a/internal/api/wellknown/nodeinfo/nodeinfoget.go b/internal/api/wellknown/nodeinfo/nodeinfoget.go index 3cbb29650..c458f131e 100644 --- a/internal/api/wellknown/nodeinfo/nodeinfoget.go +++ b/internal/api/wellknown/nodeinfo/nodeinfoget.go @@ -55,5 +55,12 @@ func (m *Module) NodeInfoWellKnownGETHandler(c *gin.Context) { return } - c.JSON(http.StatusOK, resp) + // Encode JSON HTTP response. + apiutil.EncodeJSONResponse( + c.Writer, + c.Request, + http.StatusOK, + apiutil.AppJSON, + resp, + ) } diff --git a/internal/api/wellknown/webfinger/webfingerget.go b/internal/api/wellknown/webfinger/webfingerget.go index 02f366ea6..74cb8fefe 100644 --- a/internal/api/wellknown/webfinger/webfingerget.go +++ b/internal/api/wellknown/webfinger/webfingerget.go @@ -18,7 +18,6 @@ package webfinger import ( - "encoding/json" "errors" "fmt" "net/http" @@ -87,13 +86,12 @@ func (m *Module) WebfingerGETRequest(c *gin.Context) { return } - b, err := json.Marshal(resp) - if err != nil { - apiutil.ErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1) - return - } - - // Always return "application/jrd+json" regardless of negotiated - // format. See https://www.rfc-editor.org/rfc/rfc7033#section-10.2 - c.Data(http.StatusOK, string(apiutil.AppJRDJSON), b) + // Encode JSON HTTP response. + apiutil.EncodeJSONResponse( + c.Writer, + c.Request, + http.StatusOK, + apiutil.AppJRDJSON, + resp, + ) } diff --git a/internal/web/tag.go b/internal/web/tag.go index d52de81d8..69591f114 100644 --- a/internal/web/tag.go +++ b/internal/web/tag.go @@ -45,7 +45,7 @@ func (m *Module) tagGETHandler(c *gin.Context) { } // We only serve text/html at this endpoint. - if _, err := apiutil.NegotiateAccept(c, []apiutil.MIME{apiutil.TextHTML}...); err != nil { + if _, err := apiutil.NegotiateAccept(c, apiutil.TextHTML); err != nil { apiutil.WebErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), instanceGet) return }