mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-11-28 04:33:32 -06:00
Serve outbox for Actor (#289)
* add statusesvisible convenience function * add minID + onlyPublic to account statuses get * move swagger collection stuff to common * start working on Outbox GETting * move functions into federationProcessor * outboxToASCollection * add statusesvisible convenience function * add minID + onlyPublic to account statuses get * move swagger collection stuff to common * start working on Outbox GETting * move functions into federationProcessor * outboxToASCollection * bit more work on outbox paging * wrapNoteInCreate function * test + hook up the processor functions * don't do prev + next links on empty reply * test get outbox through api * don't fail on no status entries * add outbox implementation doc * typo
This commit is contained in:
parent
26a95ad27d
commit
4b1d9d3780
38 changed files with 1851 additions and 470 deletions
|
|
@ -39,8 +39,12 @@ const (
|
|||
PinnedKey = "pinned"
|
||||
// MaxIDKey is for specifying the maximum ID of the status to retrieve.
|
||||
MaxIDKey = "max_id"
|
||||
// MediaOnlyKey is for specifying that only statuses with media should be returned in a list of returned statuses by an account.
|
||||
MediaOnlyKey = "only_media"
|
||||
// MinIDKey is for specifying the minimum ID of the status to retrieve.
|
||||
MinIDKey = "min_id"
|
||||
// OnlyMediaKey is for specifying that only statuses with media should be returned in a list of returned statuses by an account.
|
||||
OnlyMediaKey = "only_media"
|
||||
// OnlyPublicKey is for specifying that only statuses with visibility public should be returned in a list of returned statuses by account.
|
||||
OnlyPublicKey = "only_public"
|
||||
|
||||
// IDKey is the key to use for retrieving account ID in requests
|
||||
IDKey = "id"
|
||||
|
|
|
|||
|
|
@ -64,6 +64,12 @@ import (
|
|||
// Return only statuses *OLDER* than the given max status ID.
|
||||
// The status with the specified ID will not be included in the response.
|
||||
// in: query
|
||||
// - name: min_id
|
||||
// type: string
|
||||
// description: |-
|
||||
// Return only statuses *NEWER* than the given min status ID.
|
||||
// The status with the specified ID will not be included in the response.
|
||||
// in: query
|
||||
// required: false
|
||||
// - name: pinned_only
|
||||
// type: boolean
|
||||
|
|
@ -71,12 +77,18 @@ import (
|
|||
// default: false
|
||||
// in: query
|
||||
// required: false
|
||||
// - name: media_only
|
||||
// - name: only_media
|
||||
// type: boolean
|
||||
// description: Show only statuses with media attachments.
|
||||
// default: false
|
||||
// in: query
|
||||
// required: false
|
||||
// - name: only_public
|
||||
// type: boolean
|
||||
// description: Show only statuses with a privacy setting of 'public'.
|
||||
// default: false
|
||||
// in: query
|
||||
// required: false
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
|
|
@ -143,6 +155,12 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) {
|
|||
maxID = maxIDString
|
||||
}
|
||||
|
||||
minID := ""
|
||||
minIDString := c.Query(MinIDKey)
|
||||
if minIDString != "" {
|
||||
minID = minIDString
|
||||
}
|
||||
|
||||
pinnedOnly := false
|
||||
pinnedString := c.Query(PinnedKey)
|
||||
if pinnedString != "" {
|
||||
|
|
@ -156,7 +174,7 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
mediaOnly := false
|
||||
mediaOnlyString := c.Query(MediaOnlyKey)
|
||||
mediaOnlyString := c.Query(OnlyMediaKey)
|
||||
if mediaOnlyString != "" {
|
||||
i, err := strconv.ParseBool(mediaOnlyString)
|
||||
if err != nil {
|
||||
|
|
@ -167,7 +185,19 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) {
|
|||
mediaOnly = i
|
||||
}
|
||||
|
||||
statuses, errWithCode := m.processor.AccountStatusesGet(c.Request.Context(), authed, targetAcctID, limit, excludeReplies, maxID, pinnedOnly, mediaOnly)
|
||||
publicOnly := false
|
||||
publicOnlyString := c.Query(OnlyPublicKey)
|
||||
if mediaOnlyString != "" {
|
||||
i, err := strconv.ParseBool(publicOnlyString)
|
||||
if err != nil {
|
||||
l.Debugf("error parsing public only string: %s", err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse public only query param"})
|
||||
return
|
||||
}
|
||||
mediaOnly = i
|
||||
}
|
||||
|
||||
statuses, errWithCode := m.processor.AccountStatusesGet(c.Request.Context(), authed, targetAcctID, limit, excludeReplies, maxID, minID, pinnedOnly, mediaOnly, publicOnly)
|
||||
if errWithCode != nil {
|
||||
l.Debugf("error from processor account statuses get: %s", errWithCode)
|
||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
||||
|
|
|
|||
|
|
@ -57,3 +57,41 @@ func negotiateFormat(c *gin.Context) (string, error) {
|
|||
}
|
||||
return format, nil
|
||||
}
|
||||
|
||||
// SwaggerCollection represents an activitypub collection.
|
||||
// swagger:model swaggerCollection
|
||||
type SwaggerCollection struct {
|
||||
// ActivityStreams context.
|
||||
// example: https://www.w3.org/ns/activitystreams
|
||||
Context string `json:"@context"`
|
||||
// ActivityStreams ID.
|
||||
// example: https://example.org/users/some_user/statuses/106717595988259568/replies
|
||||
ID string `json:"id"`
|
||||
// ActivityStreams type.
|
||||
// example: Collection
|
||||
Type string `json:"type"`
|
||||
// ActivityStreams first property.
|
||||
First SwaggerCollectionPage `json:"first"`
|
||||
// ActivityStreams last property.
|
||||
Last SwaggerCollectionPage `json:"last,omitempty"`
|
||||
}
|
||||
|
||||
// SwaggerCollectionPage represents one page of a collection.
|
||||
// swagger:model swaggerCollectionPage
|
||||
type SwaggerCollectionPage struct {
|
||||
// ActivityStreams ID.
|
||||
// example: https://example.org/users/some_user/statuses/106717595988259568/replies?page=true
|
||||
ID string `json:"id"`
|
||||
// ActivityStreams type.
|
||||
// example: CollectionPage
|
||||
Type string `json:"type"`
|
||||
// Link to the next page.
|
||||
// example: https://example.org/users/some_user/statuses/106717595988259568/replies?only_other_accounts=true&page=true
|
||||
Next string `json:"next"`
|
||||
// Collection this page belongs to.
|
||||
// example: https://example.org/users/some_user/statuses/106717595988259568/replies
|
||||
PartOf string `json:"partOf"`
|
||||
// Items on this page.
|
||||
// example: ["https://example.org/users/some_other_user/statuses/086417595981111564", "https://another.example.com/users/another_user/statuses/01FCN8XDV3YG7B4R42QA6YQZ9R"]
|
||||
Items []string `json:"items"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -436,7 +436,7 @@ func (suite *InboxPostTestSuite) TestPostDelete() {
|
|||
suite.ErrorIs(err, db.ErrNoEntries)
|
||||
|
||||
// no statuses from foss satan should be left in the database
|
||||
dbStatuses, err := suite.db.GetAccountStatuses(ctx, deletedAccount.ID, 0, false, "", false, false)
|
||||
dbStatuses, err := suite.db.GetAccountStatuses(ctx, deletedAccount.ID, 0, false, "", "", false, false, false)
|
||||
suite.ErrorIs(err, db.ErrNoEntries)
|
||||
suite.Empty(dbStatuses)
|
||||
|
||||
|
|
|
|||
142
internal/api/s2s/user/outboxget.go
Normal file
142
internal/api/s2s/user/outboxget.go
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021 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 user
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// OutboxGETHandler swagger:operation GET /users/{username}/outbox s2sOutboxGet
|
||||
//
|
||||
// Get the public outbox collection for an actor.
|
||||
//
|
||||
// Note that the response will be a Collection with a page as `first`, as shown below, if `page` is `false`.
|
||||
//
|
||||
// If `page` is `true`, then the response will be a single `CollectionPage` without the wrapping `Collection`.
|
||||
//
|
||||
// HTTP signature is required on the request.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - s2s/federation
|
||||
//
|
||||
// produces:
|
||||
// - application/activity+json
|
||||
//
|
||||
// parameters:
|
||||
// - name: username
|
||||
// type: string
|
||||
// description: Username of the account.
|
||||
// in: path
|
||||
// required: true
|
||||
// - name: page
|
||||
// type: boolean
|
||||
// description: Return response as a CollectionPage.
|
||||
// in: query
|
||||
// default: false
|
||||
// - name: min_id
|
||||
// type: string
|
||||
// description: Minimum ID of the next status, used for paging.
|
||||
// in: query
|
||||
// - name: max_id
|
||||
// type: string
|
||||
// description: Maximum ID of the next status, used for paging.
|
||||
// in: query
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/swaggerCollection"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '403':
|
||||
// description: forbidden
|
||||
// '404':
|
||||
// description: not found
|
||||
func (m *Module) OutboxGETHandler(c *gin.Context) {
|
||||
l := logrus.WithFields(logrus.Fields{
|
||||
"func": "OutboxGETHandler",
|
||||
"url": c.Request.RequestURI,
|
||||
})
|
||||
|
||||
requestedUsername := c.Param(UsernameKey)
|
||||
if requestedUsername == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no username specified in request"})
|
||||
return
|
||||
}
|
||||
|
||||
page := false
|
||||
pageString := c.Query(PageKey)
|
||||
if pageString != "" {
|
||||
i, err := strconv.ParseBool(pageString)
|
||||
if err != nil {
|
||||
l.Debugf("error parsing page string: %s", err)
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse page query param"})
|
||||
return
|
||||
}
|
||||
page = i
|
||||
}
|
||||
|
||||
minID := ""
|
||||
minIDString := c.Query(MinIDKey)
|
||||
if minIDString != "" {
|
||||
minID = minIDString
|
||||
}
|
||||
|
||||
maxID := ""
|
||||
maxIDString := c.Query(MaxIDKey)
|
||||
if maxIDString != "" {
|
||||
maxID = maxIDString
|
||||
}
|
||||
|
||||
format, err := negotiateFormat(c)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotAcceptable, gin.H{"error": fmt.Sprintf("could not negotiate format with given Accept header(s): %s", err)})
|
||||
return
|
||||
}
|
||||
l.Tracef("negotiated format: %s", format)
|
||||
|
||||
ctx := transferContext(c)
|
||||
|
||||
outbox, errWithCode := m.processor.GetFediOutbox(ctx, requestedUsername, page, maxID, minID, c.Request.URL)
|
||||
if errWithCode != nil {
|
||||
l.Info(errWithCode.Error())
|
||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
||||
return
|
||||
}
|
||||
|
||||
b, mErr := json.Marshal(outbox)
|
||||
if mErr != nil {
|
||||
err := fmt.Errorf("could not marshal json: %s", mErr)
|
||||
l.Error(err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.Data(http.StatusOK, format, b)
|
||||
}
|
||||
206
internal/api/s2s/user/outboxget_test.go
Normal file
206
internal/api/s2s/user/outboxget_test.go
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021 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 user_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-fed/activity/streams"
|
||||
"github.com/go-fed/activity/streams/vocab"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/s2s/user"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
type OutboxGetTestSuite struct {
|
||||
UserStandardTestSuite
|
||||
}
|
||||
|
||||
func (suite *OutboxGetTestSuite) TestGetOutbox() {
|
||||
// the dereference we're gonna use
|
||||
derefRequests := testrig.NewTestDereferenceRequests(suite.testAccounts)
|
||||
signedRequest := derefRequests["foss_satan_dereference_zork_outbox"]
|
||||
targetAccount := suite.testAccounts["local_account_1"]
|
||||
|
||||
tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db)
|
||||
federator := testrig.NewTestFederator(suite.db, tc, suite.storage)
|
||||
processor := testrig.NewTestProcessor(suite.db, suite.storage, federator)
|
||||
userModule := user.New(suite.config, processor).(*user.Module)
|
||||
|
||||
// setup request
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := gin.CreateTestContext(recorder)
|
||||
ctx.Request = httptest.NewRequest(http.MethodGet, targetAccount.OutboxURI, nil) // the endpoint we're hitting
|
||||
ctx.Request.Header.Set("Signature", signedRequest.SignatureHeader)
|
||||
ctx.Request.Header.Set("Date", signedRequest.DateHeader)
|
||||
|
||||
// we need to pass the context through signature check first to set appropriate values on it
|
||||
suite.securityModule.SignatureCheck(ctx)
|
||||
|
||||
// normally the router would populate these params from the path values,
|
||||
// but because we're calling the function directly, we need to set them manually.
|
||||
ctx.Params = gin.Params{
|
||||
gin.Param{
|
||||
Key: user.UsernameKey,
|
||||
Value: targetAccount.Username,
|
||||
},
|
||||
}
|
||||
|
||||
// trigger the function being tested
|
||||
userModule.OutboxGETHandler(ctx)
|
||||
|
||||
// check response
|
||||
suite.EqualValues(http.StatusOK, recorder.Code)
|
||||
|
||||
result := recorder.Result()
|
||||
defer result.Body.Close()
|
||||
b, err := ioutil.ReadAll(result.Body)
|
||||
suite.NoError(err)
|
||||
suite.Equal(`{"@context":"https://www.w3.org/ns/activitystreams","first":"http://localhost:8080/users/the_mighty_zork/outbox?page=true","id":"http://localhost:8080/users/the_mighty_zork/outbox","type":"OrderedCollection"}`, string(b))
|
||||
|
||||
m := make(map[string]interface{})
|
||||
err = json.Unmarshal(b, &m)
|
||||
suite.NoError(err)
|
||||
|
||||
t, err := streams.ToType(context.Background(), m)
|
||||
suite.NoError(err)
|
||||
|
||||
_, ok := t.(vocab.ActivityStreamsOrderedCollection)
|
||||
suite.True(ok)
|
||||
}
|
||||
|
||||
func (suite *OutboxGetTestSuite) TestGetOutboxFirstPage() {
|
||||
// the dereference we're gonna use
|
||||
derefRequests := testrig.NewTestDereferenceRequests(suite.testAccounts)
|
||||
signedRequest := derefRequests["foss_satan_dereference_zork_outbox_first"]
|
||||
targetAccount := suite.testAccounts["local_account_1"]
|
||||
|
||||
tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db)
|
||||
federator := testrig.NewTestFederator(suite.db, tc, suite.storage)
|
||||
processor := testrig.NewTestProcessor(suite.db, suite.storage, federator)
|
||||
userModule := user.New(suite.config, processor).(*user.Module)
|
||||
|
||||
// setup request
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := gin.CreateTestContext(recorder)
|
||||
ctx.Request = httptest.NewRequest(http.MethodGet, targetAccount.OutboxURI+"?page=true", nil) // the endpoint we're hitting
|
||||
ctx.Request.Header.Set("Signature", signedRequest.SignatureHeader)
|
||||
ctx.Request.Header.Set("Date", signedRequest.DateHeader)
|
||||
|
||||
// we need to pass the context through signature check first to set appropriate values on it
|
||||
suite.securityModule.SignatureCheck(ctx)
|
||||
|
||||
// normally the router would populate these params from the path values,
|
||||
// but because we're calling the function directly, we need to set them manually.
|
||||
ctx.Params = gin.Params{
|
||||
gin.Param{
|
||||
Key: user.UsernameKey,
|
||||
Value: targetAccount.Username,
|
||||
},
|
||||
}
|
||||
|
||||
// trigger the function being tested
|
||||
userModule.OutboxGETHandler(ctx)
|
||||
|
||||
// check response
|
||||
suite.EqualValues(http.StatusOK, recorder.Code)
|
||||
|
||||
result := recorder.Result()
|
||||
defer result.Body.Close()
|
||||
b, err := ioutil.ReadAll(result.Body)
|
||||
suite.NoError(err)
|
||||
suite.Equal(`{"@context":"https://www.w3.org/ns/activitystreams","id":"http://localhost:8080/users/the_mighty_zork/outbox?page=true","next":"http://localhost:8080/users/the_mighty_zork/outbox?page=true\u0026max_id=01F8MHAMCHF6Y650WCRSCP4WMY","orderedItems":{"actor":"http://localhost:8080/users/the_mighty_zork","cc":"http://localhost:8080/users/the_mighty_zork/followers","id":"http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY/activity","object":"http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY","published":"2021-10-20T10:40:37Z","to":"https://www.w3.org/ns/activitystreams#Public","type":"Create"},"partOf":"http://localhost:8080/users/the_mighty_zork/outbox","prev":"http://localhost:8080/users/the_mighty_zork/outbox?page=true\u0026min_id=01F8MHAMCHF6Y650WCRSCP4WMY","type":"OrderedCollectionPage"}`, string(b))
|
||||
|
||||
m := make(map[string]interface{})
|
||||
err = json.Unmarshal(b, &m)
|
||||
suite.NoError(err)
|
||||
|
||||
t, err := streams.ToType(context.Background(), m)
|
||||
suite.NoError(err)
|
||||
|
||||
_, ok := t.(vocab.ActivityStreamsOrderedCollectionPage)
|
||||
suite.True(ok)
|
||||
}
|
||||
|
||||
func (suite *OutboxGetTestSuite) TestGetOutboxNextPage() {
|
||||
// the dereference we're gonna use
|
||||
derefRequests := testrig.NewTestDereferenceRequests(suite.testAccounts)
|
||||
signedRequest := derefRequests["foss_satan_dereference_zork_outbox_next"]
|
||||
targetAccount := suite.testAccounts["local_account_1"]
|
||||
|
||||
tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil), suite.db)
|
||||
federator := testrig.NewTestFederator(suite.db, tc, suite.storage)
|
||||
processor := testrig.NewTestProcessor(suite.db, suite.storage, federator)
|
||||
userModule := user.New(suite.config, processor).(*user.Module)
|
||||
|
||||
// setup request
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := gin.CreateTestContext(recorder)
|
||||
ctx.Request = httptest.NewRequest(http.MethodGet, targetAccount.OutboxURI+"?page=true&max_id=01F8MHAMCHF6Y650WCRSCP4WMY", nil) // the endpoint we're hitting
|
||||
ctx.Request.Header.Set("Signature", signedRequest.SignatureHeader)
|
||||
ctx.Request.Header.Set("Date", signedRequest.DateHeader)
|
||||
|
||||
// we need to pass the context through signature check first to set appropriate values on it
|
||||
suite.securityModule.SignatureCheck(ctx)
|
||||
|
||||
// normally the router would populate these params from the path values,
|
||||
// but because we're calling the function directly, we need to set them manually.
|
||||
ctx.Params = gin.Params{
|
||||
gin.Param{
|
||||
Key: user.UsernameKey,
|
||||
Value: targetAccount.Username,
|
||||
},
|
||||
gin.Param{
|
||||
Key: user.MaxIDKey,
|
||||
Value: "01F8MHAMCHF6Y650WCRSCP4WMY",
|
||||
},
|
||||
}
|
||||
|
||||
// trigger the function being tested
|
||||
userModule.OutboxGETHandler(ctx)
|
||||
|
||||
// check response
|
||||
suite.EqualValues(http.StatusOK, recorder.Code)
|
||||
|
||||
result := recorder.Result()
|
||||
defer result.Body.Close()
|
||||
b, err := ioutil.ReadAll(result.Body)
|
||||
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","orderedItems":[],"partOf":"http://localhost:8080/users/the_mighty_zork/outbox","type":"OrderedCollectionPage"}`, string(b))
|
||||
|
||||
m := make(map[string]interface{})
|
||||
err = json.Unmarshal(b, &m)
|
||||
suite.NoError(err)
|
||||
|
||||
t, err := streams.ToType(context.Background(), m)
|
||||
suite.NoError(err)
|
||||
|
||||
_, ok := t.(vocab.ActivityStreamsOrderedCollectionPage)
|
||||
suite.True(ok)
|
||||
}
|
||||
|
||||
func TestOutboxGetTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(OutboxGetTestSuite))
|
||||
}
|
||||
|
|
@ -75,7 +75,7 @@ import (
|
|||
// '200':
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/swaggerStatusRepliesCollection"
|
||||
// "$ref": "#/definitions/swaggerCollection"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
|
|
@ -158,39 +158,3 @@ func (m *Module) StatusRepliesGETHandler(c *gin.Context) {
|
|||
|
||||
c.Data(http.StatusOK, format, b)
|
||||
}
|
||||
|
||||
// SwaggerStatusRepliesCollection represents a response to GET /users/{username}/statuses/{status}/replies.
|
||||
// swagger:model swaggerStatusRepliesCollection
|
||||
type SwaggerStatusRepliesCollection struct {
|
||||
// ActivityStreams context.
|
||||
// example: https://www.w3.org/ns/activitystreams
|
||||
Context string `json:"@context"`
|
||||
// ActivityStreams ID.
|
||||
// example: https://example.org/users/some_user/statuses/106717595988259568/replies
|
||||
ID string `json:"id"`
|
||||
// ActivityStreams type.
|
||||
// example: Collection
|
||||
Type string `json:"type"`
|
||||
// ActivityStreams first property.
|
||||
First SwaggerStatusRepliesCollectionPage `json:"first"`
|
||||
}
|
||||
|
||||
// SwaggerStatusRepliesCollectionPage represents one page of a collection.
|
||||
// swagger:model swaggerStatusRepliesCollectionPage
|
||||
type SwaggerStatusRepliesCollectionPage struct {
|
||||
// ActivityStreams ID.
|
||||
// example: https://example.org/users/some_user/statuses/106717595988259568/replies?page=true
|
||||
ID string `json:"id"`
|
||||
// ActivityStreams type.
|
||||
// example: CollectionPage
|
||||
Type string `json:"type"`
|
||||
// Link to the next page.
|
||||
// example: https://example.org/users/some_user/statuses/106717595988259568/replies?only_other_accounts=true&page=true
|
||||
Next string `json:"next"`
|
||||
// Collection this page belongs to.
|
||||
// example: https://example.org/users/some_user/statuses/106717595988259568/replies
|
||||
PartOf string `json:"partOf"`
|
||||
// Items on this page.
|
||||
// example: ["https://example.org/users/some_other_user/statuses/086417595981111564", "https://another.example.com/users/another_user/statuses/01FCN8XDV3YG7B4R42QA6YQZ9R"]
|
||||
Items []string `json:"items"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ const (
|
|||
OnlyOtherAccountsKey = "only_other_accounts"
|
||||
// MinIDKey is for filtering status responses.
|
||||
MinIDKey = "min_id"
|
||||
// MaxIDKey is for filtering status responses.
|
||||
MaxIDKey = "max_id"
|
||||
// PageKey is for filtering status responses.
|
||||
PageKey = "page"
|
||||
|
||||
|
|
@ -50,6 +52,8 @@ const (
|
|||
UsersPublicKeyPath = UsersBasePathWithUsername + "/" + util.PublicKeyPath
|
||||
// UsersInboxPath is for serving POST requests to a user's inbox with the given username key.
|
||||
UsersInboxPath = UsersBasePathWithUsername + "/" + util.InboxPath
|
||||
// UsersOutboxPath is for serving GET requests to a user's outbox with the given username key.
|
||||
UsersOutboxPath = UsersBasePathWithUsername + "/" + util.OutboxPath
|
||||
// UsersFollowersPath is for serving GET request's to a user's followers list, with the given username key.
|
||||
UsersFollowersPath = UsersBasePathWithUsername + "/" + util.FollowersPath
|
||||
// UsersFollowingPath is for serving GET request's to a user's following list, with the given username key.
|
||||
|
|
@ -83,5 +87,6 @@ func (m *Module) Route(s router.Router) error {
|
|||
s.AttachHandler(http.MethodGet, UsersStatusPath, m.StatusGETHandler)
|
||||
s.AttachHandler(http.MethodGet, UsersPublicKeyPath, m.PublicKeyGETHandler)
|
||||
s.AttachHandler(http.MethodGet, UsersStatusRepliesPath, m.StatusRepliesGETHandler)
|
||||
s.AttachHandler(http.MethodGet, UsersOutboxPath, m.OutboxGETHandler)
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue