mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-12-24 19:16:14 -06:00
Merge branch 'superseriousbusiness:main' into rss-fixes1
This commit is contained in:
commit
ee3978e86c
689 changed files with 922580 additions and 199098 deletions
|
|
@ -27,6 +27,7 @@ import (
|
|||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/federation/dereferencing"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
||||
|
|
@ -34,14 +35,13 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/uris"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func (p *Processor) MoveSelf(
|
||||
ctx context.Context,
|
||||
authed *oauth.Auth,
|
||||
authed *apiutil.Auth,
|
||||
form *apimodel.AccountMoveRequest,
|
||||
) gtserror.WithCode {
|
||||
// Ensure valid MovedToURI.
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/suite"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
|
@ -56,7 +57,7 @@ func (suite *MoveTestSuite) TestMoveAccountOK() {
|
|||
// Trigger move from zork to admin.
|
||||
if err := suite.accountProcessor.MoveSelf(
|
||||
ctx,
|
||||
&oauth.Auth{
|
||||
&apiutil.Auth{
|
||||
Token: oauth.DBTokenToToken(suite.testTokens["local_account_1"]),
|
||||
Application: suite.testApplications["local_account_1"],
|
||||
User: suite.testUsers["local_account_1"],
|
||||
|
|
@ -120,7 +121,7 @@ func (suite *MoveTestSuite) TestMoveAccountNotAliased() {
|
|||
// not aliased back to zork.
|
||||
err := suite.accountProcessor.MoveSelf(
|
||||
ctx,
|
||||
&oauth.Auth{
|
||||
&apiutil.Auth{
|
||||
Token: oauth.DBTokenToToken(suite.testTokens["local_account_1"]),
|
||||
Application: suite.testApplications["local_account_1"],
|
||||
User: suite.testUsers["local_account_1"],
|
||||
|
|
@ -150,7 +151,7 @@ func (suite *MoveTestSuite) TestMoveAccountBadPassword() {
|
|||
// not aliased back to zork.
|
||||
err := suite.accountProcessor.MoveSelf(
|
||||
ctx,
|
||||
&oauth.Auth{
|
||||
&apiutil.Auth{
|
||||
Token: oauth.DBTokenToToken(suite.testTokens["local_account_1"]),
|
||||
Application: suite.testApplications["local_account_1"],
|
||||
User: suite.testUsers["local_account_1"],
|
||||
|
|
|
|||
|
|
@ -22,13 +22,13 @@ import (
|
|||
|
||||
"github.com/google/uuid"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
||||
func (p *Processor) AppCreate(ctx context.Context, authed *oauth.Auth, form *apimodel.ApplicationCreateRequest) (*apimodel.Application, gtserror.WithCode) {
|
||||
func (p *Processor) AppCreate(ctx context.Context, authed *apiutil.Auth, form *apimodel.ApplicationCreateRequest) (*apimodel.Application, gtserror.WithCode) {
|
||||
// set default 'read' for scopes if it's not set
|
||||
var scopes string
|
||||
if form.Scopes == "" {
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ func (p *Processor) PollVote(ctx context.Context, requester *gtsmodel.Account, p
|
|||
// Before enqueuing it, increment the poll
|
||||
// vote counts on the copy attached to the
|
||||
// PollVote (that we also later return).
|
||||
poll.IncrementVotes(choices)
|
||||
poll.IncrementVotes(choices, true)
|
||||
|
||||
// Enqueue worker task to handle side-effects of user poll vote(s).
|
||||
p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/admin"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/cleaner"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/email"
|
||||
|
|
@ -66,7 +67,7 @@ type ProcessingStandardTestSuite struct {
|
|||
testStatuses map[string]*gtsmodel.Status
|
||||
testTags map[string]*gtsmodel.Tag
|
||||
testMentions map[string]*gtsmodel.Mention
|
||||
testAutheds map[string]*oauth.Auth
|
||||
testAutheds map[string]*apiutil.Auth
|
||||
testBlocks map[string]*gtsmodel.Block
|
||||
testActivities map[string]testrig.ActivityWithSignature
|
||||
testLists map[string]*gtsmodel.List
|
||||
|
|
@ -85,7 +86,7 @@ func (suite *ProcessingStandardTestSuite) SetupSuite() {
|
|||
suite.testStatuses = testrig.NewTestStatuses()
|
||||
suite.testTags = testrig.NewTestTags()
|
||||
suite.testMentions = testrig.NewTestMentions()
|
||||
suite.testAutheds = map[string]*oauth.Auth{
|
||||
suite.testAutheds = map[string]*apiutil.Auth{
|
||||
"local_account_1": {
|
||||
Application: suite.testApplications["local_account_1"],
|
||||
User: suite.testUsers["local_account_1"],
|
||||
|
|
|
|||
|
|
@ -442,6 +442,33 @@ func (p *Processor) WebContextGet(
|
|||
_, parentHidden := hiddenStatuses[status.InReplyToID]
|
||||
v, err := p.visFilter.StatusVisible(ctx, nil, status)
|
||||
if err != nil || !v || parentHidden {
|
||||
// If this is the main status whose
|
||||
// context we're looking for, and it's
|
||||
// not visible for whatever reason, we
|
||||
// should just return a 404 here, as we
|
||||
// can't meaningfully render the thread.
|
||||
if status.ID == targetStatusID {
|
||||
var thisErr error
|
||||
switch {
|
||||
case err != nil:
|
||||
thisErr = gtserror.Newf("error checking visibility of target status: %w", err)
|
||||
|
||||
case !v:
|
||||
const errText = "target status not visible"
|
||||
thisErr = gtserror.New(errText)
|
||||
|
||||
case parentHidden:
|
||||
const errText = "target status parent is hidden"
|
||||
thisErr = gtserror.New(errText)
|
||||
}
|
||||
|
||||
return nil, gtserror.NewErrorNotFound(thisErr)
|
||||
}
|
||||
|
||||
// This isn't the main status whose
|
||||
// context we're looking for, just
|
||||
// your standard not-visible status,
|
||||
// so add it to the count + map.
|
||||
if !inReplies {
|
||||
// Main thread entry hidden.
|
||||
wCtx.ThreadHidden++
|
||||
|
|
|
|||
|
|
@ -19,10 +19,14 @@ package status
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
|
|
@ -92,11 +96,58 @@ func (p *Processor) Create(
|
|||
// Get current time.
|
||||
now := time.Now()
|
||||
|
||||
// Default to current
|
||||
// time as creation time.
|
||||
createdAt := now
|
||||
|
||||
// Handle backfilled/scheduled statuses.
|
||||
backfill := false
|
||||
if form.ScheduledAt != nil {
|
||||
scheduledAt := *form.ScheduledAt
|
||||
|
||||
// Statuses may only be scheduled
|
||||
// a minimum time into the future.
|
||||
if now.Before(scheduledAt) {
|
||||
const errText = "scheduled statuses are not yet supported"
|
||||
return nil, gtserror.NewErrorNotImplemented(gtserror.New(errText), errText)
|
||||
}
|
||||
|
||||
// If not scheduled into the future, this status is being backfilled.
|
||||
if !config.GetInstanceAllowBackdatingStatuses() {
|
||||
const errText = "backdating statuses has been disabled on this instance"
|
||||
return nil, gtserror.NewErrorForbidden(gtserror.New(errText), errText)
|
||||
}
|
||||
|
||||
// Statuses can't be backdated to or before the UNIX epoch
|
||||
// since this would prevent generating a ULID.
|
||||
// If backdated even further to the Go epoch,
|
||||
// this would also cause issues with time.Time.IsZero() checks
|
||||
// that normally signify an absent optional time,
|
||||
// but this check covers both cases.
|
||||
if scheduledAt.Compare(time.UnixMilli(0)) <= 0 {
|
||||
const errText = "statuses can't be backdated to or before the UNIX epoch"
|
||||
return nil, gtserror.NewErrorNotAcceptable(gtserror.New(errText), errText)
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
// This is a backfill.
|
||||
backfill = true
|
||||
|
||||
// Update to backfill date.
|
||||
createdAt = scheduledAt
|
||||
|
||||
// Generate an appropriate, (and unique!), ID for the creation time.
|
||||
if statusID, err = p.backfilledStatusID(ctx, createdAt); err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
}
|
||||
|
||||
status := >smodel.Status{
|
||||
ID: statusID,
|
||||
URI: accountURIs.StatusesURI + "/" + statusID,
|
||||
URL: accountURIs.StatusesURL + "/" + statusID,
|
||||
CreatedAt: now,
|
||||
CreatedAt: createdAt,
|
||||
Local: util.Ptr(true),
|
||||
Account: requester,
|
||||
AccountID: requester.ID,
|
||||
|
|
@ -134,11 +185,23 @@ func (p *Processor) Create(
|
|||
PendingApproval: util.Ptr(false),
|
||||
}
|
||||
|
||||
if backfill {
|
||||
// Ensure backfilled status contains no
|
||||
// mentions to anyone other than author.
|
||||
for _, mention := range status.Mentions {
|
||||
if mention.TargetAccountID != requester.ID {
|
||||
const errText = "statuses mentioning others can't be backfilled"
|
||||
return nil, gtserror.NewErrorForbidden(gtserror.New(errText), errText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check + attach in-reply-to status.
|
||||
if errWithCode := p.processInReplyTo(ctx,
|
||||
requester,
|
||||
status,
|
||||
form.InReplyToID,
|
||||
backfill,
|
||||
); errWithCode != nil {
|
||||
return nil, errWithCode
|
||||
}
|
||||
|
|
@ -165,11 +228,16 @@ func (p *Processor) Create(
|
|||
}
|
||||
|
||||
if form.Poll != nil {
|
||||
if backfill {
|
||||
const errText = "statuses with polls can't be backfilled"
|
||||
return nil, gtserror.NewErrorForbidden(gtserror.New(errText), errText)
|
||||
}
|
||||
|
||||
// Process poll, inserting into database.
|
||||
poll, errWithCode := p.processPoll(ctx,
|
||||
statusID,
|
||||
form.Poll,
|
||||
now,
|
||||
createdAt,
|
||||
)
|
||||
if errWithCode != nil {
|
||||
return nil, errWithCode
|
||||
|
|
@ -199,11 +267,18 @@ func (p *Processor) Create(
|
|||
}
|
||||
}
|
||||
|
||||
var model any = status
|
||||
if backfill {
|
||||
// We specifically wrap backfilled statuses in
|
||||
// a different type to signal to worker process.
|
||||
model = >smodel.BackfillStatus{Status: status}
|
||||
}
|
||||
|
||||
// Send it to the client API worker for async side-effects.
|
||||
p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: status,
|
||||
GTSModel: model,
|
||||
Origin: requester,
|
||||
})
|
||||
|
||||
|
|
@ -227,7 +302,49 @@ func (p *Processor) Create(
|
|||
return p.c.GetAPIStatus(ctx, requester, status)
|
||||
}
|
||||
|
||||
func (p *Processor) processInReplyTo(ctx context.Context, requester *gtsmodel.Account, status *gtsmodel.Status, inReplyToID string) gtserror.WithCode {
|
||||
// backfilledStatusID tries to find an unused ULID for a backfilled status.
|
||||
func (p *Processor) backfilledStatusID(ctx context.Context, createdAt time.Time) (string, error) {
|
||||
|
||||
// Any fetching of statuses here is
|
||||
// only to check availability of ID,
|
||||
// no need for any attached models.
|
||||
ctx = gtscontext.SetBarebones(ctx)
|
||||
|
||||
// backfilledStatusIDRetries should
|
||||
// be more than enough attempts.
|
||||
const backfilledStatusIDRetries = 100
|
||||
for try := 0; try < backfilledStatusIDRetries; try++ {
|
||||
var err error
|
||||
|
||||
// Generate a ULID based on the backfilled
|
||||
// status's original creation time.
|
||||
statusID := id.NewULIDFromTime(createdAt)
|
||||
|
||||
// Check for an existing status with that ID.
|
||||
status, err := p.state.DB.GetStatusByID(ctx, statusID)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return "", gtserror.Newf("DB error checking if a status ID was in use: %w", err)
|
||||
}
|
||||
|
||||
if status == nil {
|
||||
// We found a free ID!
|
||||
return statusID, nil
|
||||
}
|
||||
|
||||
// That status ID is
|
||||
// in use. Try again.
|
||||
}
|
||||
|
||||
return "", gtserror.Newf("failed to find an unused ID after %d tries", backfilledStatusIDRetries)
|
||||
}
|
||||
|
||||
func (p *Processor) processInReplyTo(
|
||||
ctx context.Context,
|
||||
requester *gtsmodel.Account,
|
||||
status *gtsmodel.Status,
|
||||
inReplyToID string,
|
||||
backfill bool,
|
||||
) gtserror.WithCode {
|
||||
if inReplyToID == "" {
|
||||
// Not a reply.
|
||||
// Nothing to do.
|
||||
|
|
@ -269,6 +386,13 @@ func (p *Processor) processInReplyTo(ctx context.Context, requester *gtsmodel.Ac
|
|||
return gtserror.NewErrorForbidden(err, errText)
|
||||
}
|
||||
|
||||
// When backfilling, only self-replies are allowed.
|
||||
if backfill && requester.ID != inReplyTo.AccountID {
|
||||
const errText = "replies to others can't be backfilled"
|
||||
err := gtserror.New(errText)
|
||||
return gtserror.NewErrorForbidden(err, errText)
|
||||
}
|
||||
|
||||
// Derive pendingApproval status.
|
||||
var pendingApproval bool
|
||||
switch {
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ func (suite *StatusCreateTestSuite) TestProcessContentWarningWithQuotationMarks(
|
|||
SpoilerText: "\"test\"", // these should not be html-escaped when the final text is rendered
|
||||
Visibility: apimodel.VisibilityPublic,
|
||||
LocalOnly: util.Ptr(false),
|
||||
ScheduledAt: "",
|
||||
ScheduledAt: nil,
|
||||
Language: "en",
|
||||
ContentType: apimodel.StatusContentTypePlain,
|
||||
}
|
||||
|
|
@ -75,7 +75,7 @@ func (suite *StatusCreateTestSuite) TestProcessContentWarningWithHTMLEscapedQuot
|
|||
SpoilerText: ""test"", // the html-escaped quotation marks should appear as normal quotation marks in the finished text
|
||||
Visibility: apimodel.VisibilityPublic,
|
||||
LocalOnly: util.Ptr(false),
|
||||
ScheduledAt: "",
|
||||
ScheduledAt: nil,
|
||||
Language: "en",
|
||||
ContentType: apimodel.StatusContentTypePlain,
|
||||
}
|
||||
|
|
@ -106,7 +106,7 @@ func (suite *StatusCreateTestSuite) TestProcessStatusMarkdownWithUnderscoreEmoji
|
|||
Sensitive: false,
|
||||
Visibility: apimodel.VisibilityPublic,
|
||||
LocalOnly: util.Ptr(false),
|
||||
ScheduledAt: "",
|
||||
ScheduledAt: nil,
|
||||
Language: "en",
|
||||
ContentType: apimodel.StatusContentTypeMarkdown,
|
||||
}
|
||||
|
|
@ -133,7 +133,7 @@ func (suite *StatusCreateTestSuite) TestProcessStatusMarkdownWithSpoilerTextEmoj
|
|||
Sensitive: false,
|
||||
Visibility: apimodel.VisibilityPublic,
|
||||
LocalOnly: util.Ptr(false),
|
||||
ScheduledAt: "",
|
||||
ScheduledAt: nil,
|
||||
Language: "en",
|
||||
ContentType: apimodel.StatusContentTypeMarkdown,
|
||||
}
|
||||
|
|
@ -164,7 +164,7 @@ func (suite *StatusCreateTestSuite) TestProcessMediaDescriptionTooShort() {
|
|||
SpoilerText: "",
|
||||
Visibility: apimodel.VisibilityPublic,
|
||||
LocalOnly: util.Ptr(false),
|
||||
ScheduledAt: "",
|
||||
ScheduledAt: nil,
|
||||
Language: "en",
|
||||
ContentType: apimodel.StatusContentTypePlain,
|
||||
}
|
||||
|
|
@ -189,7 +189,7 @@ func (suite *StatusCreateTestSuite) TestProcessLanguageWithScriptPart() {
|
|||
SpoilerText: "",
|
||||
Visibility: apimodel.VisibilityPublic,
|
||||
LocalOnly: util.Ptr(false),
|
||||
ScheduledAt: "",
|
||||
ScheduledAt: nil,
|
||||
Language: "zh-Hans",
|
||||
ContentType: apimodel.StatusContentTypePlain,
|
||||
}
|
||||
|
|
@ -219,7 +219,7 @@ func (suite *StatusCreateTestSuite) TestProcessReplyToUnthreadedRemoteStatus() {
|
|||
SpoilerText: "this is a reply",
|
||||
Visibility: apimodel.VisibilityPublic,
|
||||
LocalOnly: util.Ptr(false),
|
||||
ScheduledAt: "",
|
||||
ScheduledAt: nil,
|
||||
Language: "en",
|
||||
ContentType: apimodel.StatusContentTypePlain,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,12 @@ package stream
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
|
|
@ -58,5 +62,22 @@ func (p *Processor) Authorize(ctx context.Context, accessToken string) (*gtsmode
|
|||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
// Ensure read scope.
|
||||
//
|
||||
// TODO: make this more granular
|
||||
// depending on stream type.
|
||||
hasScopes := strings.Split(ti.GetScope(), " ")
|
||||
scopeOK := slices.ContainsFunc(
|
||||
hasScopes,
|
||||
func(hasScope string) bool {
|
||||
return apiutil.Scope(hasScope).Permits(apiutil.ScopeRead)
|
||||
},
|
||||
)
|
||||
|
||||
if !scopeOK {
|
||||
const errText = "token has insufficient scope permission"
|
||||
return nil, gtserror.NewErrorForbidden(errors.New(errText), errText)
|
||||
}
|
||||
|
||||
return acct, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,15 +23,15 @@ import (
|
|||
"fmt"
|
||||
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
statusfilter "github.com/superseriousbusiness/gotosocial/internal/filter/status"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
func (p *Processor) FavedTimelineGet(ctx context.Context, authed *oauth.Auth, maxID string, minID string, limit int) (*apimodel.PageableResponse, gtserror.WithCode) {
|
||||
func (p *Processor) FavedTimelineGet(ctx context.Context, authed *apiutil.Auth, maxID string, minID string, limit int) (*apimodel.PageableResponse, gtserror.WithCode) {
|
||||
statuses, nextMaxID, prevMinID, err := p.state.DB.GetFavedTimeline(ctx, authed.Account.ID, maxID, minID, limit)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
err = fmt.Errorf("FavedTimelineGet: db error getting statuses: %w", err)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"errors"
|
||||
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
statusfilter "github.com/superseriousbusiness/gotosocial/internal/filter/status"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/filter/usermute"
|
||||
|
|
@ -29,7 +30,6 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/state"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/timeline"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
|
|
@ -118,7 +118,7 @@ func HomeTimelineStatusPrepare(state *state.State, converter *typeutils.Converte
|
|||
}
|
||||
}
|
||||
|
||||
func (p *Processor) HomeTimelineGet(ctx context.Context, authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.PageableResponse, gtserror.WithCode) {
|
||||
func (p *Processor) HomeTimelineGet(ctx context.Context, authed *apiutil.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.PageableResponse, gtserror.WithCode) {
|
||||
statuses, err := p.state.Timelines.Home.GetTimeline(ctx, authed.Account.ID, maxID, sinceID, minID, limit, local)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
err = gtserror.Newf("error getting statuses: %w", err)
|
||||
|
|
|
|||
|
|
@ -23,10 +23,10 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/suite"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/filter/visibility"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
tlprocessor "github.com/superseriousbusiness/gotosocial/internal/processing/timeline"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/timeline"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
|
|
@ -64,7 +64,7 @@ func (suite *HomeTestSuite) TestHomeTimelineGetHideFiltered() {
|
|||
var (
|
||||
ctx = context.Background()
|
||||
requester = suite.testAccounts["local_account_1"]
|
||||
authed = &oauth.Auth{Account: requester}
|
||||
authed = &apiutil.Auth{Account: requester}
|
||||
maxID = ""
|
||||
sinceID = ""
|
||||
minID = "01F8MHAAY43M6RJ473VQFCVH36" // 1 before filteredStatus
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"errors"
|
||||
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
statusfilter "github.com/superseriousbusiness/gotosocial/internal/filter/status"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/filter/usermute"
|
||||
|
|
@ -29,7 +30,6 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/state"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/timeline"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
|
|
@ -130,7 +130,7 @@ func ListTimelineStatusPrepare(state *state.State, converter *typeutils.Converte
|
|||
}
|
||||
}
|
||||
|
||||
func (p *Processor) ListTimelineGet(ctx context.Context, authed *oauth.Auth, listID string, maxID string, sinceID string, minID string, limit int) (*apimodel.PageableResponse, gtserror.WithCode) {
|
||||
func (p *Processor) ListTimelineGet(ctx context.Context, authed *apiutil.Auth, listID string, maxID string, sinceID string, minID string, limit int) (*apimodel.PageableResponse, gtserror.WithCode) {
|
||||
// Ensure list exists + is owned by this account.
|
||||
list, err := p.state.DB.GetListByID(ctx, listID)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
"net/url"
|
||||
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/filter/status"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/filter/usermute"
|
||||
|
|
@ -31,14 +32,13 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/paging"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
func (p *Processor) NotificationsGet(
|
||||
ctx context.Context,
|
||||
authed *oauth.Auth,
|
||||
authed *apiutil.Auth,
|
||||
page *paging.Page,
|
||||
types []gtsmodel.NotificationType,
|
||||
excludeTypes []gtsmodel.NotificationType,
|
||||
|
|
@ -164,7 +164,7 @@ func (p *Processor) NotificationGet(ctx context.Context, account *gtsmodel.Accou
|
|||
return apiNotif, nil
|
||||
}
|
||||
|
||||
func (p *Processor) NotificationsClear(ctx context.Context, authed *oauth.Auth) gtserror.WithCode {
|
||||
func (p *Processor) NotificationsClear(ctx context.Context, authed *apiutil.Auth) gtserror.WithCode {
|
||||
// Delete all notifications of all types that target the authorized account.
|
||||
if err := p.state.DB.DeleteNotifications(ctx, nil, authed.Account.ID, ""); err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return gtserror.NewErrorInternalError(err)
|
||||
|
|
|
|||
|
|
@ -44,34 +44,43 @@ func (p *Processor) Create(
|
|||
app *gtsmodel.Application,
|
||||
form *apimodel.AccountCreateRequest,
|
||||
) (*gtsmodel.User, gtserror.WithCode) {
|
||||
const (
|
||||
usersPerDay = 10
|
||||
regBacklog = 20
|
||||
var (
|
||||
usersPerDay = config.GetAccountsRegistrationDailyLimit()
|
||||
regBacklog = config.GetAccountsRegistrationBacklogLimit()
|
||||
)
|
||||
|
||||
// Ensure no more than usersPerDay
|
||||
// If usersPerDay limit is in place,
|
||||
// ensure no more than usersPerDay
|
||||
// have registered in the last 24h.
|
||||
newUsersCount, err := p.state.DB.CountApprovedSignupsSince(ctx, time.Now().Add(-24*time.Hour))
|
||||
if err != nil {
|
||||
err := fmt.Errorf("db error counting new users: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
if usersPerDay > 0 {
|
||||
newUsersCount, err := p.state.DB.CountApprovedSignupsSince(ctx, time.Now().Add(-24*time.Hour))
|
||||
if err != nil {
|
||||
err := fmt.Errorf("db error counting new users: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
if newUsersCount >= usersPerDay {
|
||||
err := fmt.Errorf("this instance has hit its limit of new sign-ups for today (%d); you can try again tomorrow", usersPerDay)
|
||||
return nil, gtserror.NewErrorUnprocessableEntity(err, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if newUsersCount >= usersPerDay {
|
||||
err := fmt.Errorf("this instance has hit its limit of new sign-ups for today; you can try again tomorrow")
|
||||
return nil, gtserror.NewErrorUnprocessableEntity(err, err.Error())
|
||||
}
|
||||
// If registration backlog limit is
|
||||
// in place, ensure backlog isn't full.
|
||||
if regBacklog > 0 {
|
||||
backlogLen, err := p.state.DB.CountUnhandledSignups(ctx)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("db error counting registration backlog length: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
// Ensure the new users backlog isn't full.
|
||||
backlogLen, err := p.state.DB.CountUnhandledSignups(ctx)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("db error counting registration backlog length: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
if backlogLen >= regBacklog {
|
||||
err := fmt.Errorf("this instance's sign-up backlog is currently full; you must wait until pending sign-ups are handled by the admin(s)")
|
||||
return nil, gtserror.NewErrorUnprocessableEntity(err, err.Error())
|
||||
if backlogLen >= regBacklog {
|
||||
err := fmt.Errorf(
|
||||
"this instance's sign-up backlog is currently full (%d sign-ups pending approval); "+
|
||||
"you must wait until some pending sign-ups are handled by the admin(s)", regBacklog,
|
||||
)
|
||||
return nil, gtserror.NewErrorUnprocessableEntity(err, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
emailAvailable, err := p.state.DB.IsEmailAvailable(ctx, form.Email)
|
||||
|
|
|
|||
74
internal/processing/user/create_test.go
Normal file
74
internal/processing/user/create_test.go
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package user_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
)
|
||||
|
||||
type CreateTestSuite struct {
|
||||
UserStandardTestSuite
|
||||
}
|
||||
|
||||
func (suite *CreateTestSuite) TestCreateOK() {
|
||||
var (
|
||||
ctx = context.Background()
|
||||
app = suite.testApps["application_1"]
|
||||
appToken = suite.testTokens["local_account_1_client_application_token"]
|
||||
form = &apimodel.AccountCreateRequest{
|
||||
Reason: "a long enough explanation of why I am doing api calls",
|
||||
Username: "someone_new",
|
||||
Email: "someone_new@example.org",
|
||||
Password: "a long enough password for this endpoint",
|
||||
Agreement: true,
|
||||
Locale: "en-us",
|
||||
IP: net.ParseIP("192.0.2.128"),
|
||||
}
|
||||
)
|
||||
|
||||
// Create user via the API endpoint.
|
||||
user, errWithCode := suite.user.Create(ctx, app, form)
|
||||
if errWithCode != nil {
|
||||
suite.FailNow(errWithCode.Error())
|
||||
}
|
||||
|
||||
// Load the app-level access token that was just used.
|
||||
appAccessToken, err := suite.oauthServer.LoadAccessToken(ctx, appToken.Access)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
// Create a user-level access token for the new user.
|
||||
userAccessToken, err := suite.user.TokenForNewUser(ctx, appAccessToken, app, user)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
// Check returned user-level access token.
|
||||
suite.NotEmpty(userAccessToken.AccessToken)
|
||||
suite.Equal("Bearer", userAccessToken.TokenType)
|
||||
}
|
||||
|
||||
func TestCreateTestSuite(t *testing.T) {
|
||||
suite.Run(t, &CreateTestSuite{})
|
||||
}
|
||||
|
|
@ -41,6 +41,7 @@ func New(
|
|||
return Processor{
|
||||
state: state,
|
||||
converter: converter,
|
||||
oauthServer: oauthServer,
|
||||
emailSender: emailSender,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/email"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing/user"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/state"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||
|
|
@ -34,9 +35,11 @@ type UserStandardTestSuite struct {
|
|||
emailSender email.Sender
|
||||
db db.DB
|
||||
state state.State
|
||||
oauthServer oauth.Server
|
||||
|
||||
testUsers map[string]*gtsmodel.User
|
||||
|
||||
testApps map[string]*gtsmodel.Application
|
||||
testTokens map[string]*gtsmodel.Token
|
||||
testUsers map[string]*gtsmodel.User
|
||||
sentEmails map[string]string
|
||||
|
||||
user user.Processor
|
||||
|
|
@ -51,9 +54,12 @@ func (suite *UserStandardTestSuite) SetupTest() {
|
|||
suite.db = testrig.NewTestDB(&suite.state)
|
||||
suite.state.DB = suite.db
|
||||
suite.state.AdminActions = admin.New(suite.state.DB, &suite.state.Workers)
|
||||
suite.oauthServer = testrig.NewTestOauthServer(suite.state.DB)
|
||||
|
||||
suite.sentEmails = make(map[string]string)
|
||||
suite.emailSender = testrig.NewEmailSender("../../../web/template/", suite.sentEmails)
|
||||
suite.testApps = testrig.NewTestApplications()
|
||||
suite.testTokens = testrig.NewTestTokens()
|
||||
suite.testUsers = testrig.NewTestUsers()
|
||||
|
||||
suite.user = user.New(&suite.state, typeutils.NewConverter(&suite.state), testrig.NewTestOauthServer(suite.db), suite.emailSender)
|
||||
|
|
|
|||
|
|
@ -260,9 +260,18 @@ func (p *clientAPI) CreateUser(ctx context.Context, cMsg *messages.FromClientAPI
|
|||
}
|
||||
|
||||
func (p *clientAPI) CreateStatus(ctx context.Context, cMsg *messages.FromClientAPI) error {
|
||||
status, ok := cMsg.GTSModel.(*gtsmodel.Status)
|
||||
if !ok {
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.Status", cMsg.GTSModel)
|
||||
var status *gtsmodel.Status
|
||||
var backfill bool
|
||||
|
||||
// Check passed client message model type.
|
||||
switch model := cMsg.GTSModel.(type) {
|
||||
case *gtsmodel.Status:
|
||||
status = model
|
||||
case *gtsmodel.BackfillStatus:
|
||||
status = model.Status
|
||||
backfill = true
|
||||
default:
|
||||
return gtserror.Newf("%T not parseable as *gtsmodel.Status or *gtsmodel.BackfillStatus", cMsg.GTSModel)
|
||||
}
|
||||
|
||||
// If pending approval is true then status must
|
||||
|
|
@ -344,12 +353,19 @@ func (p *clientAPI) CreateStatus(ctx context.Context, cMsg *messages.FromClientA
|
|||
log.Errorf(ctx, "error updating account stats: %v", err)
|
||||
}
|
||||
|
||||
if err := p.surface.timelineAndNotifyStatus(ctx, status); err != nil {
|
||||
log.Errorf(ctx, "error timelining and notifying status: %v", err)
|
||||
}
|
||||
// We specifically do not timeline
|
||||
// or notify for backfilled statuses,
|
||||
// as these are more for archival than
|
||||
// newly posted content for user feeds.
|
||||
if !backfill {
|
||||
|
||||
if err := p.federate.CreateStatus(ctx, status); err != nil {
|
||||
log.Errorf(ctx, "error federating status: %v", err)
|
||||
if err := p.surface.timelineAndNotifyStatus(ctx, status); err != nil {
|
||||
log.Errorf(ctx, "error timelining and notifying status: %v", err)
|
||||
}
|
||||
|
||||
if err := p.federate.CreateStatus(ctx, status); err != nil {
|
||||
log.Errorf(ctx, "error federating status: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if status.InReplyToID != "" {
|
||||
|
|
|
|||
|
|
@ -368,6 +368,162 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusWithNotification() {
|
|||
suite.checkWebPushed(testStructs.WebPushSender, receivingAccount.ID, gtsmodel.NotificationStatus)
|
||||
}
|
||||
|
||||
// Even with notifications on for a user, backfilling a status should not notify or timeline it.
|
||||
func (suite *FromClientAPITestSuite) TestProcessCreateBackfilledStatusWithNotification() {
|
||||
testStructs := testrig.SetupTestStructs(rMediaPath, rTemplatePath)
|
||||
defer testrig.TearDownTestStructs(testStructs)
|
||||
|
||||
var (
|
||||
ctx = context.Background()
|
||||
postingAccount = suite.testAccounts["admin_account"]
|
||||
receivingAccount = suite.testAccounts["local_account_1"]
|
||||
testList = suite.testLists["local_account_1_list_1"]
|
||||
streams = suite.openStreams(ctx,
|
||||
testStructs.Processor,
|
||||
receivingAccount,
|
||||
[]string{testList.ID},
|
||||
)
|
||||
homeStream = streams[stream.TimelineHome]
|
||||
listStream = streams[stream.TimelineList+":"+testList.ID]
|
||||
notifStream = streams[stream.TimelineNotifications]
|
||||
|
||||
// Admin account posts a new top-level status.
|
||||
status = suite.newStatus(
|
||||
ctx,
|
||||
testStructs.State,
|
||||
postingAccount,
|
||||
gtsmodel.VisibilityPublic,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
false,
|
||||
nil,
|
||||
)
|
||||
)
|
||||
|
||||
// Update the follow from receiving account -> posting account so
|
||||
// that receiving account wants notifs when posting account posts.
|
||||
follow := new(gtsmodel.Follow)
|
||||
*follow = *suite.testFollows["local_account_1_admin_account"]
|
||||
|
||||
follow.Notify = util.Ptr(true)
|
||||
if err := testStructs.State.DB.UpdateFollow(ctx, follow); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
// Process the new status as a backfill.
|
||||
if err := testStructs.Processor.Workers().ProcessFromClientAPI(
|
||||
ctx,
|
||||
&messages.FromClientAPI{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: >smodel.BackfillStatus{Status: status},
|
||||
Origin: postingAccount,
|
||||
},
|
||||
); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
// There should be no message in the home stream.
|
||||
suite.checkStreamed(
|
||||
homeStream,
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
)
|
||||
|
||||
// There should be no message in the list stream.
|
||||
suite.checkStreamed(
|
||||
listStream,
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
)
|
||||
|
||||
// No notification should appear for the status.
|
||||
if testrig.WaitFor(func() bool {
|
||||
var err error
|
||||
_, err = testStructs.State.DB.GetNotification(
|
||||
ctx,
|
||||
gtsmodel.NotificationStatus,
|
||||
receivingAccount.ID,
|
||||
postingAccount.ID,
|
||||
status.ID,
|
||||
)
|
||||
return err == nil
|
||||
}) {
|
||||
suite.FailNow("a status notification was created, but should not have been")
|
||||
}
|
||||
|
||||
// There should be no message in the notification stream.
|
||||
suite.checkStreamed(
|
||||
notifStream,
|
||||
false,
|
||||
"",
|
||||
"",
|
||||
)
|
||||
|
||||
// There should be no Web Push status notification.
|
||||
suite.checkNotWebPushed(testStructs.WebPushSender, receivingAccount.ID)
|
||||
}
|
||||
|
||||
// Backfilled statuses should not federate when created.
|
||||
func (suite *FromClientAPITestSuite) TestProcessCreateBackfilledStatusWithRemoteFollower() {
|
||||
testStructs := testrig.SetupTestStructs(rMediaPath, rTemplatePath)
|
||||
defer testrig.TearDownTestStructs(testStructs)
|
||||
|
||||
var (
|
||||
ctx = context.Background()
|
||||
postingAccount = suite.testAccounts["local_account_1"]
|
||||
receivingAccount = suite.testAccounts["remote_account_1"]
|
||||
|
||||
// Local account posts a new top-level status.
|
||||
status = suite.newStatus(
|
||||
ctx,
|
||||
testStructs.State,
|
||||
postingAccount,
|
||||
gtsmodel.VisibilityPublic,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
false,
|
||||
nil,
|
||||
)
|
||||
)
|
||||
|
||||
// Follow the local account from the remote account.
|
||||
follow := >smodel.Follow{
|
||||
ID: "01JJHW9RW28SC1NEPZ0WBJQ4ZK",
|
||||
CreatedAt: testrig.TimeMustParse("2022-05-14T13:21:09+02:00"),
|
||||
UpdatedAt: testrig.TimeMustParse("2022-05-14T13:21:09+02:00"),
|
||||
AccountID: receivingAccount.ID,
|
||||
TargetAccountID: postingAccount.ID,
|
||||
ShowReblogs: util.Ptr(true),
|
||||
URI: "http://fossbros-anonymous.io/users/foss_satan/follow/01JJHWEVC7F8W2JDW1136K431K",
|
||||
Notify: util.Ptr(false),
|
||||
}
|
||||
|
||||
if err := testStructs.State.DB.PutFollow(ctx, follow); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
// Process the new status as a backfill.
|
||||
if err := testStructs.Processor.Workers().ProcessFromClientAPI(
|
||||
ctx,
|
||||
&messages.FromClientAPI{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
GTSModel: >smodel.BackfillStatus{Status: status},
|
||||
Origin: postingAccount,
|
||||
},
|
||||
); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
// No deliveries should be queued.
|
||||
suite.Zero(testStructs.State.Workers.Delivery.Queue.Len())
|
||||
}
|
||||
|
||||
func (suite *FromClientAPITestSuite) TestProcessCreateStatusReply() {
|
||||
testStructs := testrig.SetupTestStructs(rMediaPath, rTemplatePath)
|
||||
defer testrig.TearDownTestStructs(testStructs)
|
||||
|
|
|
|||
|
|
@ -124,6 +124,10 @@ func (p *Processor) ProcessFromFediAPI(ctx context.Context, fMsg *messages.FromF
|
|||
// UPDATE ACCOUNT
|
||||
case ap.ActorPerson:
|
||||
return p.fediAPI.UpdateAccount(ctx, fMsg)
|
||||
|
||||
// UPDATE QUESTION
|
||||
case ap.ActivityQuestion:
|
||||
return p.fediAPI.UpdatePollVote(ctx, fMsg)
|
||||
}
|
||||
|
||||
// ACCEPT SOMETHING
|
||||
|
|
@ -355,7 +359,8 @@ func (p *fediAPI) CreatePollVote(ctx context.Context, fMsg *messages.FromFediAPI
|
|||
return gtserror.Newf("cannot cast %T -> *gtsmodel.PollVote", fMsg.GTSModel)
|
||||
}
|
||||
|
||||
// Insert the new poll vote in the database.
|
||||
// Insert the new poll vote in the database, note this
|
||||
// will handle updating votes on the poll model itself.
|
||||
if err := p.state.DB.PutPollVote(ctx, vote); err != nil {
|
||||
return gtserror.Newf("error inserting poll vote in db: %w", err)
|
||||
}
|
||||
|
|
@ -376,9 +381,9 @@ func (p *fediAPI) CreatePollVote(ctx context.Context, fMsg *messages.FromFediAPI
|
|||
status.Poll = vote.Poll
|
||||
|
||||
if *status.Local {
|
||||
// Before federating it, increment the
|
||||
// poll vote counts on our local copy.
|
||||
status.Poll.IncrementVotes(vote.Choices)
|
||||
// Before federating it, increment the poll vote
|
||||
// and voter counts, *only on our local copy*.
|
||||
status.Poll.IncrementVotes(vote.Choices, true)
|
||||
|
||||
// These were poll votes in a local status, we need to
|
||||
// federate the updated status model with latest vote counts.
|
||||
|
|
@ -387,8 +392,43 @@ func (p *fediAPI) CreatePollVote(ctx context.Context, fMsg *messages.FromFediAPI
|
|||
}
|
||||
}
|
||||
|
||||
// Interaction counts changed on the source status, uncache from timelines.
|
||||
p.surface.invalidateStatusFromTimelines(ctx, vote.Poll.StatusID)
|
||||
// Interaction counts changed, uncache from timelines.
|
||||
p.surface.invalidateStatusFromTimelines(ctx, status.ID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *fediAPI) UpdatePollVote(ctx context.Context, fMsg *messages.FromFediAPI) error {
|
||||
// Cast poll vote type from the worker message.
|
||||
vote, ok := fMsg.GTSModel.(*gtsmodel.PollVote)
|
||||
if !ok {
|
||||
return gtserror.Newf("cannot cast %T -> *gtsmodel.PollVote", fMsg.GTSModel)
|
||||
}
|
||||
|
||||
// Update poll vote model (specifically only choices) in the database.
|
||||
if err := p.state.DB.UpdatePollVote(ctx, vote, "choices"); err != nil {
|
||||
return gtserror.Newf("error updating poll vote in db: %w", err)
|
||||
}
|
||||
|
||||
// Update the vote counts on the poll model itself. These will have
|
||||
// been updated by message pusher as we can't know which were new.
|
||||
if err := p.state.DB.UpdatePoll(ctx, vote.Poll, "votes"); err != nil {
|
||||
return gtserror.Newf("error updating poll in db: %w", err)
|
||||
}
|
||||
|
||||
// Get the origin status.
|
||||
status := vote.Poll.Status
|
||||
|
||||
if *status.Local {
|
||||
// These were poll votes in a local status, we need to
|
||||
// federate the updated status model with latest vote counts.
|
||||
if err := p.federate.UpdateStatus(ctx, status); err != nil {
|
||||
log.Errorf(ctx, "error federating status update: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Interaction counts changed, uncache from timelines.
|
||||
p.surface.invalidateStatusFromTimelines(ctx, status.ID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -844,16 +884,16 @@ func (p *fediAPI) AcceptRemoteStatus(ctx context.Context, fMsg *messages.FromFed
|
|||
return gtserror.Newf("%T not parseable as *url.URL", fMsg.APObject)
|
||||
}
|
||||
|
||||
acceptIRI := fMsg.APIRI
|
||||
if acceptIRI == nil {
|
||||
return gtserror.New("acceptIRI was nil")
|
||||
approvedByURI := fMsg.APIRI
|
||||
if approvedByURI == nil {
|
||||
return gtserror.New("approvedByURI was nil")
|
||||
}
|
||||
|
||||
// Assume we're accepting a status; create a
|
||||
// barebones status for dereferencing purposes.
|
||||
bareStatus := >smodel.Status{
|
||||
URI: objectIRI.String(),
|
||||
ApprovedByURI: acceptIRI.String(),
|
||||
ApprovedByURI: approvedByURI.String(),
|
||||
}
|
||||
|
||||
// Call RefreshStatus() to process the provided
|
||||
|
|
@ -872,7 +912,7 @@ func (p *fediAPI) AcceptRemoteStatus(ctx context.Context, fMsg *messages.FromFed
|
|||
}
|
||||
|
||||
// No error means it was indeed a remote status, and the
|
||||
// given acceptIRI permitted it. Timeline and notify it.
|
||||
// given approvedByURI permitted it. Timeline and notify it.
|
||||
if err := p.surface.timelineAndNotifyStatus(ctx, status); err != nil {
|
||||
log.Errorf(ctx, "error timelining and notifying status: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ import (
|
|||
"context"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/processing"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/stream"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
|
|
@ -48,7 +48,7 @@ type WorkersTestSuite struct {
|
|||
testStatuses map[string]*gtsmodel.Status
|
||||
testTags map[string]*gtsmodel.Tag
|
||||
testMentions map[string]*gtsmodel.Mention
|
||||
testAutheds map[string]*oauth.Auth
|
||||
testAutheds map[string]*apiutil.Auth
|
||||
testBlocks map[string]*gtsmodel.Block
|
||||
testActivities map[string]testrig.ActivityWithSignature
|
||||
testLists map[string]*gtsmodel.List
|
||||
|
|
@ -66,7 +66,7 @@ func (suite *WorkersTestSuite) SetupSuite() {
|
|||
suite.testStatuses = testrig.NewTestStatuses()
|
||||
suite.testTags = testrig.NewTestTags()
|
||||
suite.testMentions = testrig.NewTestMentions()
|
||||
suite.testAutheds = map[string]*oauth.Auth{
|
||||
suite.testAutheds = map[string]*apiutil.Auth{
|
||||
"local_account_1": {
|
||||
Application: suite.testApplications["local_account_1"],
|
||||
User: suite.testUsers["local_account_1"],
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue