mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2025-06-05 21:59:39 +02:00
[feature] Federate pinned posts (aka featuredCollection
) in and out (#1560)
* start fiddling * the ol' fiddle + update * start working on fetching statuses * poopy doopy doo where r u uwu * further adventures in featuring statuses * finishing up * fmt * simply status unpin loop * move empty featured check back to caller function * remove unnecessary log.WithContext calls * remove unnecessary IsIRI() checks * add explanatory comment about status URIs * change log level to error * better test names
This commit is contained in:
@@ -43,7 +43,7 @@ func (m *Module) EmojiGetHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
resp, errWithCode := m.processor.Fedi().EmojiGet(apiutil.TransferSignatureContext(c), requestedEmojiID, c.Request.URL)
|
||||
resp, errWithCode := m.processor.Fedi().EmojiGet(apiutil.TransferSignatureContext(c), requestedEmojiID)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
|
@@ -18,12 +18,14 @@
|
||||
|
||||
package users
|
||||
|
||||
// SwaggerCollection represents an activitypub collection.
|
||||
// SwaggerCollection represents an ActivityPub Collection.
|
||||
// swagger:model swaggerCollection
|
||||
type SwaggerCollection struct {
|
||||
// ActivityStreams context.
|
||||
// ActivityStreams JSON-LD context.
|
||||
// A string or an array of strings, or more
|
||||
// complex nested items.
|
||||
// example: https://www.w3.org/ns/activitystreams
|
||||
Context string `json:"@context"`
|
||||
Context interface{} `json:"@context"`
|
||||
// ActivityStreams ID.
|
||||
// example: https://example.org/users/some_user/statuses/106717595988259568/replies
|
||||
ID string `json:"id"`
|
||||
@@ -55,3 +57,25 @@ type SwaggerCollectionPage struct {
|
||||
// example: ["https://example.org/users/some_other_user/statuses/086417595981111564", "https://another.example.com/users/another_user/statuses/01FCN8XDV3YG7B4R42QA6YQZ9R"]
|
||||
Items []string `json:"items"`
|
||||
}
|
||||
|
||||
// SwaggerFeaturedCollection represents an ActivityPub OrderedCollection.
|
||||
// swagger:model swaggerFeaturedCollection
|
||||
type SwaggerFeaturedCollection struct {
|
||||
// ActivityStreams JSON-LD context.
|
||||
// A string or an array of strings, or more
|
||||
// complex nested items.
|
||||
// example: https://www.w3.org/ns/activitystreams
|
||||
Context interface{} `json:"@context"`
|
||||
// ActivityStreams ID.
|
||||
// example: https://example.org/users/some_user/collections/featured
|
||||
ID string `json:"id"`
|
||||
// ActivityStreams type.
|
||||
// example: OrderedCollection
|
||||
Type string `json:"type"`
|
||||
// List of status URIs.
|
||||
// example: ['https://example.org/users/some_user/statuses/01GSZ0F7Q8SJKNRF777GJD271R', 'https://example.org/users/some_user/statuses/01GSZ0G012CBQ7TEKX689S3QRE']
|
||||
Items []string `json:"items"`
|
||||
// Number of items in this collection.
|
||||
// example: 2
|
||||
TotalItems int
|
||||
}
|
||||
|
97
internal/api/activitypub/users/featured.go
Normal file
97
internal/api/activitypub/users/featured.go
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
|
||||
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package users
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
)
|
||||
|
||||
// FeaturedCollectionGETHandler swagger:operation GET /users/{username}/collections/featured s2sFeaturedCollectionGet
|
||||
//
|
||||
// Get the featured collection (pinned posts) for a user.
|
||||
//
|
||||
// The response will contain an ordered collection of Note URIs in the `items` property.
|
||||
//
|
||||
// It is up to the caller to dereference the provided Note URIs (or not, if they already have them cached).
|
||||
//
|
||||
// HTTP signature is required on the request.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - s2s/federation
|
||||
//
|
||||
// produces:
|
||||
// - application/activity+json
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/swaggerFeaturedCollection"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '403':
|
||||
// description: forbidden
|
||||
// '404':
|
||||
// description: not found
|
||||
func (m *Module) FeaturedCollectionGETHandler(c *gin.Context) {
|
||||
// usernames on our instance are always lowercase
|
||||
requestedUsername := strings.ToLower(c.Param(UsernameKey))
|
||||
if requestedUsername == "" {
|
||||
err := errors.New("no username specified in request")
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
format, err := apiutil.NegotiateAccept(c, apiutil.HTMLOrActivityPubHeaders...)
|
||||
if err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
if format == string(apiutil.TextHTML) {
|
||||
// This isn't an ActivityPub request;
|
||||
// redirect to the user's profile.
|
||||
c.Redirect(http.StatusSeeOther, "/@"+requestedUsername)
|
||||
return
|
||||
}
|
||||
|
||||
resp, errWithCode := m.processor.Fedi().FeaturedCollectionGet(apiutil.TransferSignatureContext(c), requestedUsername)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
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)
|
||||
}
|
@@ -46,12 +46,13 @@ func (m *Module) FollowersGETHandler(c *gin.Context) {
|
||||
}
|
||||
|
||||
if format == string(apiutil.TextHTML) {
|
||||
// redirect to the user's profile
|
||||
// This isn't an ActivityPub request;
|
||||
// redirect to the user's profile.
|
||||
c.Redirect(http.StatusSeeOther, "/@"+requestedUsername)
|
||||
return
|
||||
}
|
||||
|
||||
resp, errWithCode := m.processor.Fedi().FollowersGet(apiutil.TransferSignatureContext(c), requestedUsername, c.Request.URL)
|
||||
resp, errWithCode := m.processor.Fedi().FollowersGet(apiutil.TransferSignatureContext(c), requestedUsername)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
|
@@ -46,12 +46,13 @@ func (m *Module) FollowingGETHandler(c *gin.Context) {
|
||||
}
|
||||
|
||||
if format == string(apiutil.TextHTML) {
|
||||
// redirect to the user's profile
|
||||
// This isn't an ActivityPub request;
|
||||
// redirect to the user's profile.
|
||||
c.Redirect(http.StatusSeeOther, "/@"+requestedUsername)
|
||||
return
|
||||
}
|
||||
|
||||
resp, errWithCode := m.processor.Fedi().FollowingGet(apiutil.TransferSignatureContext(c), requestedUsername, c.Request.URL)
|
||||
resp, errWithCode := m.processor.Fedi().FollowingGet(apiutil.TransferSignatureContext(c), requestedUsername)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
|
@@ -101,7 +101,8 @@ func (m *Module) OutboxGETHandler(c *gin.Context) {
|
||||
}
|
||||
|
||||
if format == string(apiutil.TextHTML) {
|
||||
// redirect to the user's profile
|
||||
// This isn't an ActivityPub request;
|
||||
// redirect to the user's profile.
|
||||
c.Redirect(http.StatusSeeOther, "/@"+requestedUsername)
|
||||
return
|
||||
}
|
||||
@@ -129,7 +130,7 @@ func (m *Module) OutboxGETHandler(c *gin.Context) {
|
||||
maxID = maxIDString
|
||||
}
|
||||
|
||||
resp, errWithCode := m.processor.Fedi().OutboxGet(apiutil.TransferSignatureContext(c), requestedUsername, page, maxID, minID, c.Request.URL)
|
||||
resp, errWithCode := m.processor.Fedi().OutboxGet(apiutil.TransferSignatureContext(c), requestedUsername, page, maxID, minID)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
|
@@ -150,7 +150,7 @@ func (m *Module) StatusRepliesGETHandler(c *gin.Context) {
|
||||
minID = minIDString
|
||||
}
|
||||
|
||||
resp, errWithCode := m.processor.Fedi().StatusRepliesGet(apiutil.TransferSignatureContext(c), requestedUsername, requestedStatusID, page, onlyOtherAccounts, minID, c.Request.URL)
|
||||
resp, errWithCode := m.processor.Fedi().StatusRepliesGet(apiutil.TransferSignatureContext(c), requestedUsername, requestedStatusID, page, onlyOtherAccounts, c.Query("only_other_accounts") != "", minID)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
|
@@ -59,7 +59,7 @@ func (m *Module) StatusGETHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
resp, errWithCode := m.processor.Fedi().StatusGet(apiutil.TransferSignatureContext(c), requestedUsername, requestedStatusID, c.Request.URL)
|
||||
resp, errWithCode := m.processor.Fedi().StatusGet(apiutil.TransferSignatureContext(c), requestedUsername, requestedStatusID)
|
||||
if errWithCode != nil {
|
||||
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
|
@@ -50,6 +50,8 @@ const (
|
||||
FollowersPath = BasePath + "/" + uris.FollowersPath
|
||||
// FollowingPath is for serving GET request's to a user's following list, with the given username key.
|
||||
FollowingPath = BasePath + "/" + uris.FollowingPath
|
||||
// FeaturedCollectionPath is for serving GET requests to a user's list of featured (pinned) statuses.
|
||||
FeaturedCollectionPath = BasePath + "/" + uris.CollectionsPath + "/" + uris.FeaturedPath
|
||||
// StatusPath is for serving GET requests to a particular status by a user, with the given username key and status ID
|
||||
StatusPath = BasePath + "/" + uris.StatusesPath + "/:" + StatusIDKey
|
||||
// StatusRepliesPath is for serving the replies collection of a status.
|
||||
@@ -71,6 +73,7 @@ func (m *Module) Route(attachHandler func(method string, path string, f ...gin.H
|
||||
attachHandler(http.MethodPost, InboxPath, m.InboxPOSTHandler)
|
||||
attachHandler(http.MethodGet, FollowersPath, m.FollowersGETHandler)
|
||||
attachHandler(http.MethodGet, FollowingPath, m.FollowingGETHandler)
|
||||
attachHandler(http.MethodGet, FeaturedCollectionPath, m.FeaturedCollectionGETHandler)
|
||||
attachHandler(http.MethodGet, StatusPath, m.StatusGETHandler)
|
||||
attachHandler(http.MethodGet, StatusRepliesPath, m.StatusRepliesGETHandler)
|
||||
attachHandler(http.MethodGet, OutboxPath, m.OutboxGETHandler)
|
||||
|
Reference in New Issue
Block a user