[feature] Federate reports to remote instance as Flag (if desired) (#1386)
* reports federate out, we did it lxds * fix optional line start (should be optional slash)
This commit is contained in:
parent
c59ec6f2a4
commit
3283900b0d
|
@ -621,7 +621,7 @@ func ExtractActor(i WithActor) (*url.URL, error) {
|
||||||
return nil, errors.New("no iri found for actor prop")
|
return nil, errors.New("no iri found for actor prop")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractObject extracts a URL object from a WithObject interface.
|
// ExtractObject extracts the first URL object from a WithObject interface.
|
||||||
func ExtractObject(i WithObject) (*url.URL, error) {
|
func ExtractObject(i WithObject) (*url.URL, error) {
|
||||||
objectProp := i.GetActivityStreamsObject()
|
objectProp := i.GetActivityStreamsObject()
|
||||||
if objectProp == nil {
|
if objectProp == nil {
|
||||||
|
|
|
@ -20,7 +20,7 @@ package federatingdb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"codeberg.org/gruf/go-kv"
|
"codeberg.org/gruf/go-kv"
|
||||||
|
@ -33,34 +33,27 @@ import (
|
||||||
//
|
//
|
||||||
// The library makes this call only after acquiring a lock first.
|
// The library makes this call only after acquiring a lock first.
|
||||||
func (f *federatingDB) Get(ctx context.Context, id *url.URL) (value vocab.Type, err error) {
|
func (f *federatingDB) Get(ctx context.Context, id *url.URL) (value vocab.Type, err error) {
|
||||||
l := log.WithFields(kv.Fields{
|
l := log.WithFields(kv.Fields{{"id", id}}...)
|
||||||
{"id", id},
|
|
||||||
}...)
|
|
||||||
l.Debug("entering Get")
|
l.Debug("entering Get")
|
||||||
|
|
||||||
if uris.IsUserPath(id) {
|
switch {
|
||||||
|
case uris.IsUserPath(id):
|
||||||
acct, err := f.db.GetAccountByURI(ctx, id.String())
|
acct, err := f.db.GetAccountByURI(ctx, id.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return f.typeConverter.AccountToAS(ctx, acct)
|
return f.typeConverter.AccountToAS(ctx, acct)
|
||||||
}
|
case uris.IsStatusesPath(id):
|
||||||
|
|
||||||
if uris.IsStatusesPath(id) {
|
|
||||||
status, err := f.db.GetStatusByURI(ctx, id.String())
|
status, err := f.db.GetStatusByURI(ctx, id.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return f.typeConverter.StatusToAS(ctx, status)
|
return f.typeConverter.StatusToAS(ctx, status)
|
||||||
}
|
case uris.IsFollowersPath(id):
|
||||||
|
|
||||||
if uris.IsFollowersPath(id) {
|
|
||||||
return f.Followers(ctx, id)
|
return f.Followers(ctx, id)
|
||||||
}
|
case uris.IsFollowingPath(id):
|
||||||
|
|
||||||
if uris.IsFollowingPath(id) {
|
|
||||||
return f.Following(ctx, id)
|
return f.Following(ctx, id)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("federatingDB: could not Get %s", id.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New("could not get")
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -229,60 +229,56 @@ func (f *federatingDB) ActorForInbox(ctx context.Context, inboxIRI *url.URL) (ac
|
||||||
}
|
}
|
||||||
|
|
||||||
// getAccountForIRI returns the account that corresponds to or owns the given IRI.
|
// getAccountForIRI returns the account that corresponds to or owns the given IRI.
|
||||||
func (f *federatingDB) getAccountForIRI(ctx context.Context, iri *url.URL) (account *gtsmodel.Account, err error) {
|
func (f *federatingDB) getAccountForIRI(ctx context.Context, iri *url.URL) (*gtsmodel.Account, error) {
|
||||||
acct := >smodel.Account{}
|
var (
|
||||||
|
acct = >smodel.Account{}
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
if uris.IsInboxPath(iri) {
|
switch {
|
||||||
if err := f.db.GetWhere(ctx, []db.Where{{Key: "inbox_uri", Value: iri.String()}}, acct); err != nil {
|
case uris.IsUserPath(iri):
|
||||||
if err == db.ErrNoEntries {
|
if acct, err = f.db.GetAccountByURI(ctx, iri.String()); err != nil {
|
||||||
return nil, fmt.Errorf("no actor found that corresponds to inbox %s", iri.String())
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("db error searching for actor with inbox %s", iri.String())
|
|
||||||
}
|
|
||||||
return acct, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if uris.IsOutboxPath(iri) {
|
|
||||||
if err := f.db.GetWhere(ctx, []db.Where{{Key: "outbox_uri", Value: iri.String()}}, acct); err != nil {
|
|
||||||
if err == db.ErrNoEntries {
|
|
||||||
return nil, fmt.Errorf("no actor found that corresponds to outbox %s", iri.String())
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("db error searching for actor with outbox %s", iri.String())
|
|
||||||
}
|
|
||||||
return acct, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if uris.IsUserPath(iri) {
|
|
||||||
if err := f.db.GetWhere(ctx, []db.Where{{Key: "uri", Value: iri.String()}}, acct); err != nil {
|
|
||||||
if err == db.ErrNoEntries {
|
if err == db.ErrNoEntries {
|
||||||
return nil, fmt.Errorf("no actor found that corresponds to uri %s", iri.String())
|
return nil, fmt.Errorf("no actor found that corresponds to uri %s", iri.String())
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("db error searching for actor with uri %s", iri.String())
|
return nil, fmt.Errorf("db error searching for actor with uri %s", iri.String())
|
||||||
}
|
}
|
||||||
return acct, nil
|
return acct, nil
|
||||||
}
|
case uris.IsInboxPath(iri):
|
||||||
|
if err = f.db.GetWhere(ctx, []db.Where{{Key: "inbox_uri", Value: iri.String()}}, acct); err != nil {
|
||||||
if uris.IsFollowersPath(iri) {
|
if err == db.ErrNoEntries {
|
||||||
if err := f.db.GetWhere(ctx, []db.Where{{Key: "followers_uri", Value: iri.String()}}, acct); err != nil {
|
return nil, fmt.Errorf("no actor found that corresponds to inbox %s", iri.String())
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("db error searching for actor with inbox %s", iri.String())
|
||||||
|
}
|
||||||
|
return acct, nil
|
||||||
|
case uris.IsOutboxPath(iri):
|
||||||
|
if err = f.db.GetWhere(ctx, []db.Where{{Key: "outbox_uri", Value: iri.String()}}, acct); err != nil {
|
||||||
|
if err == db.ErrNoEntries {
|
||||||
|
return nil, fmt.Errorf("no actor found that corresponds to outbox %s", iri.String())
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("db error searching for actor with outbox %s", iri.String())
|
||||||
|
}
|
||||||
|
return acct, nil
|
||||||
|
case uris.IsFollowersPath(iri):
|
||||||
|
if err = f.db.GetWhere(ctx, []db.Where{{Key: "followers_uri", Value: iri.String()}}, acct); err != nil {
|
||||||
if err == db.ErrNoEntries {
|
if err == db.ErrNoEntries {
|
||||||
return nil, fmt.Errorf("no actor found that corresponds to followers_uri %s", iri.String())
|
return nil, fmt.Errorf("no actor found that corresponds to followers_uri %s", iri.String())
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("db error searching for actor with followers_uri %s", iri.String())
|
return nil, fmt.Errorf("db error searching for actor with followers_uri %s", iri.String())
|
||||||
}
|
}
|
||||||
return acct, nil
|
return acct, nil
|
||||||
}
|
case uris.IsFollowingPath(iri):
|
||||||
|
if err = f.db.GetWhere(ctx, []db.Where{{Key: "following_uri", Value: iri.String()}}, acct); err != nil {
|
||||||
if uris.IsFollowingPath(iri) {
|
|
||||||
if err := f.db.GetWhere(ctx, []db.Where{{Key: "following_uri", Value: iri.String()}}, acct); err != nil {
|
|
||||||
if err == db.ErrNoEntries {
|
if err == db.ErrNoEntries {
|
||||||
return nil, fmt.Errorf("no actor found that corresponds to following_uri %s", iri.String())
|
return nil, fmt.Errorf("no actor found that corresponds to following_uri %s", iri.String())
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("db error searching for actor with following_uri %s", iri.String())
|
return nil, fmt.Errorf("db error searching for actor with following_uri %s", iri.String())
|
||||||
}
|
}
|
||||||
return acct, nil
|
return acct, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("getActorForIRI: iri %s not recognised", iri)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("getActorForIRI: iri %s not recognised", iri)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// collectFollows takes a slice of iris and converts them into ActivityStreamsCollection of IRIs.
|
// collectFollows takes a slice of iris and converts them into ActivityStreamsCollection of IRIs.
|
||||||
|
|
|
@ -345,10 +345,19 @@ func (p *processor) processDeleteAccountFromClientAPI(ctx context.Context, clien
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *processor) processReportAccountFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error {
|
func (p *processor) processReportAccountFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error {
|
||||||
// TODO: in a separate PR, handle side effects of flag/report
|
report, ok := clientMsg.GTSModel.(*gtsmodel.Report)
|
||||||
// 1. email admin(s)
|
if !ok {
|
||||||
// 2. federate report if necessary
|
return errors.New("report was not parseable as *gtsmodel.Report")
|
||||||
return nil
|
}
|
||||||
|
|
||||||
|
// TODO: in a separate PR, also email admin(s)
|
||||||
|
|
||||||
|
if !*report.Forwarded {
|
||||||
|
// nothing to do, don't federate the report
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.federateReport(ctx, report)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: move all the below functions into federation.Federator
|
// TODO: move all the below functions into federation.Federator
|
||||||
|
@ -922,3 +931,51 @@ func (p *processor) federateUnblock(ctx context.Context, block *gtsmodel.Block)
|
||||||
_, err = p.federator.FederatingActor().Send(ctx, outboxIRI, undo)
|
_, err = p.federator.FederatingActor().Send(ctx, outboxIRI, undo)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *processor) federateReport(ctx context.Context, report *gtsmodel.Report) error {
|
||||||
|
if report.TargetAccount == nil {
|
||||||
|
reportTargetAccount, err := p.db.GetAccountByID(ctx, report.TargetAccountID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("federateReport: error getting report target account from database: %w", err)
|
||||||
|
}
|
||||||
|
report.TargetAccount = reportTargetAccount
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(report.StatusIDs) > 0 && len(report.Statuses) == 0 {
|
||||||
|
statuses, err := p.db.GetStatuses(ctx, report.StatusIDs)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("federateReport: error getting report statuses from database: %w", err)
|
||||||
|
}
|
||||||
|
report.Statuses = statuses
|
||||||
|
}
|
||||||
|
|
||||||
|
flag, err := p.tc.ReportToASFlag(ctx, report)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("federateReport: error converting report to AS flag: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add bto so that our federating actor knows where to
|
||||||
|
// send the Flag; it'll still use a shared inbox if possible
|
||||||
|
reportTargetURI, err := url.Parse(report.TargetAccount.URI)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("federateReport: error parsing outboxURI %s: %w", report.TargetAccount.URI, err)
|
||||||
|
}
|
||||||
|
bTo := streams.NewActivityStreamsBtoProperty()
|
||||||
|
bTo.AppendIRI(reportTargetURI)
|
||||||
|
flag.SetActivityStreamsBto(bTo)
|
||||||
|
|
||||||
|
// deliver the flag using the outbox of the
|
||||||
|
// instance account to anonymize the report
|
||||||
|
instanceAccount, err := p.db.GetInstanceAccount(ctx, "")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("federateReport: error getting instance account: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
outboxIRI, err := url.Parse(instanceAccount.OutboxURI)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("federateReport: error parsing outboxURI %s: %w", instanceAccount.OutboxURI, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = p.federator.FederatingActor().Send(ctx, outboxIRI, flag)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
|
@ -91,6 +91,7 @@ func (p *processor) Create(ctx context.Context, account *gtsmodel.Account, form
|
||||||
APActivityType: ap.ActivityFlag,
|
APActivityType: ap.ActivityFlag,
|
||||||
GTSModel: report,
|
GTSModel: report,
|
||||||
OriginAccount: account,
|
OriginAccount: account,
|
||||||
|
TargetAccount: targetAccount,
|
||||||
})
|
})
|
||||||
|
|
||||||
apiReport, err := p.tc.ReportToAPIReport(ctx, report)
|
apiReport, err := p.tc.ReportToAPIReport(ctx, report)
|
||||||
|
|
|
@ -77,40 +77,45 @@ var (
|
||||||
// EmojiFinder extracts emoji strings from a piece of text.
|
// EmojiFinder extracts emoji strings from a piece of text.
|
||||||
EmojiFinder = regexp.MustCompile(emojiFinderString)
|
EmojiFinder = regexp.MustCompile(emojiFinderString)
|
||||||
|
|
||||||
// usernameString defines an acceptable username on this instance
|
// usernameString defines an acceptable username for a new account on this instance
|
||||||
usernameString = fmt.Sprintf(`[a-z0-9_]{2,%d}`, maximumUsernameLength)
|
usernameString = fmt.Sprintf(`[a-z0-9_]{2,%d}`, maximumUsernameLength)
|
||||||
// Username can be used to validate usernames of new signups
|
// Username can be used to validate usernames of new signups
|
||||||
Username = regexp.MustCompile(fmt.Sprintf(`^%s$`, usernameString))
|
Username = regexp.MustCompile(fmt.Sprintf(`^%s$`, usernameString))
|
||||||
|
|
||||||
userPathString = fmt.Sprintf(`^?/%s/(%s)$`, users, usernameString)
|
// usernameStringRelaxed is like usernameString, but also allows the '.' character,
|
||||||
|
// so it can also be used to match the instance account, which will have a username
|
||||||
|
// like 'example.org', and it has no upper length limit, so will work for long domains.
|
||||||
|
usernameStringRelaxed = `[a-z0-9_\.]{2,}`
|
||||||
|
|
||||||
|
userPathString = fmt.Sprintf(`^/?%s/(%s)$`, users, usernameStringRelaxed)
|
||||||
// UserPath parses a path that validates and captures the username part from eg /users/example_username
|
// UserPath parses a path that validates and captures the username part from eg /users/example_username
|
||||||
UserPath = regexp.MustCompile(userPathString)
|
UserPath = regexp.MustCompile(userPathString)
|
||||||
|
|
||||||
publicKeyPath = fmt.Sprintf(`^?/%s/(%s)/%s`, users, usernameString, publicKey)
|
publicKeyPath = fmt.Sprintf(`^/?%s/(%s)/%s`, users, usernameStringRelaxed, publicKey)
|
||||||
// PublicKeyPath parses a path that validates and captures the username part from eg /users/example_username/main-key
|
// PublicKeyPath parses a path that validates and captures the username part from eg /users/example_username/main-key
|
||||||
PublicKeyPath = regexp.MustCompile(publicKeyPath)
|
PublicKeyPath = regexp.MustCompile(publicKeyPath)
|
||||||
|
|
||||||
inboxPath = fmt.Sprintf(`^/?%s/(%s)/%s$`, users, usernameString, inbox)
|
inboxPath = fmt.Sprintf(`^/?%s/(%s)/%s$`, users, usernameStringRelaxed, inbox)
|
||||||
// InboxPath parses a path that validates and captures the username part from eg /users/example_username/inbox
|
// InboxPath parses a path that validates and captures the username part from eg /users/example_username/inbox
|
||||||
InboxPath = regexp.MustCompile(inboxPath)
|
InboxPath = regexp.MustCompile(inboxPath)
|
||||||
|
|
||||||
outboxPath = fmt.Sprintf(`^/?%s/(%s)/%s$`, users, usernameString, outbox)
|
outboxPath = fmt.Sprintf(`^/?%s/(%s)/%s$`, users, usernameStringRelaxed, outbox)
|
||||||
// OutboxPath parses a path that validates and captures the username part from eg /users/example_username/outbox
|
// OutboxPath parses a path that validates and captures the username part from eg /users/example_username/outbox
|
||||||
OutboxPath = regexp.MustCompile(outboxPath)
|
OutboxPath = regexp.MustCompile(outboxPath)
|
||||||
|
|
||||||
actorPath = fmt.Sprintf(`^?/%s/(%s)$`, actors, usernameString)
|
actorPath = fmt.Sprintf(`^/?%s/(%s)$`, actors, usernameStringRelaxed)
|
||||||
// ActorPath parses a path that validates and captures the username part from eg /actors/example_username
|
// ActorPath parses a path that validates and captures the username part from eg /actors/example_username
|
||||||
ActorPath = regexp.MustCompile(actorPath)
|
ActorPath = regexp.MustCompile(actorPath)
|
||||||
|
|
||||||
followersPath = fmt.Sprintf(`^/?%s/(%s)/%s$`, users, usernameString, followers)
|
followersPath = fmt.Sprintf(`^/?%s/(%s)/%s$`, users, usernameStringRelaxed, followers)
|
||||||
// FollowersPath parses a path that validates and captures the username part from eg /users/example_username/followers
|
// FollowersPath parses a path that validates and captures the username part from eg /users/example_username/followers
|
||||||
FollowersPath = regexp.MustCompile(followersPath)
|
FollowersPath = regexp.MustCompile(followersPath)
|
||||||
|
|
||||||
followingPath = fmt.Sprintf(`^/?%s/(%s)/%s$`, users, usernameString, following)
|
followingPath = fmt.Sprintf(`^/?%s/(%s)/%s$`, users, usernameStringRelaxed, following)
|
||||||
// FollowingPath parses a path that validates and captures the username part from eg /users/example_username/following
|
// FollowingPath parses a path that validates and captures the username part from eg /users/example_username/following
|
||||||
FollowingPath = regexp.MustCompile(followingPath)
|
FollowingPath = regexp.MustCompile(followingPath)
|
||||||
|
|
||||||
followPath = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, users, usernameString, follow, ulid)
|
followPath = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, users, usernameStringRelaxed, follow, ulid)
|
||||||
// FollowPath parses a path that validates and captures the username part and the ulid part
|
// FollowPath parses a path that validates and captures the username part and the ulid part
|
||||||
// from eg /users/example_username/follow/01F7XT5JZW1WMVSW1KADS8PVDH
|
// from eg /users/example_username/follow/01F7XT5JZW1WMVSW1KADS8PVDH
|
||||||
FollowPath = regexp.MustCompile(followPath)
|
FollowPath = regexp.MustCompile(followPath)
|
||||||
|
@ -119,22 +124,22 @@ var (
|
||||||
// ULID parses and validate a ULID.
|
// ULID parses and validate a ULID.
|
||||||
ULID = regexp.MustCompile(fmt.Sprintf(`^%s$`, ulid))
|
ULID = regexp.MustCompile(fmt.Sprintf(`^%s$`, ulid))
|
||||||
|
|
||||||
likedPath = fmt.Sprintf(`^/?%s/(%s)/%s$`, users, usernameString, liked)
|
likedPath = fmt.Sprintf(`^/?%s/(%s)/%s$`, users, usernameStringRelaxed, liked)
|
||||||
// LikedPath parses a path that validates and captures the username part from eg /users/example_username/liked
|
// LikedPath parses a path that validates and captures the username part from eg /users/example_username/liked
|
||||||
LikedPath = regexp.MustCompile(likedPath)
|
LikedPath = regexp.MustCompile(likedPath)
|
||||||
|
|
||||||
likePath = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, users, usernameString, liked, ulid)
|
likePath = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, users, usernameStringRelaxed, liked, ulid)
|
||||||
// LikePath parses a path that validates and captures the username part and the ulid part
|
// LikePath parses a path that validates and captures the username part and the ulid part
|
||||||
// from eg /users/example_username/like/01F7XT5JZW1WMVSW1KADS8PVDH
|
// from eg /users/example_username/like/01F7XT5JZW1WMVSW1KADS8PVDH
|
||||||
LikePath = regexp.MustCompile(likePath)
|
LikePath = regexp.MustCompile(likePath)
|
||||||
|
|
||||||
statusesPath = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, users, usernameString, statuses, ulid)
|
statusesPath = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, users, usernameStringRelaxed, statuses, ulid)
|
||||||
// StatusesPath parses a path that validates and captures the username part and the ulid part
|
// StatusesPath parses a path that validates and captures the username part and the ulid part
|
||||||
// from eg /users/example_username/statuses/01F7XT5JZW1WMVSW1KADS8PVDH
|
// from eg /users/example_username/statuses/01F7XT5JZW1WMVSW1KADS8PVDH
|
||||||
// The regex can be played with here: https://regex101.com/r/G9zuxQ/1
|
// The regex can be played with here: https://regex101.com/r/G9zuxQ/1
|
||||||
StatusesPath = regexp.MustCompile(statusesPath)
|
StatusesPath = regexp.MustCompile(statusesPath)
|
||||||
|
|
||||||
blockPath = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, users, usernameString, blocks, ulid)
|
blockPath = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, users, usernameStringRelaxed, blocks, ulid)
|
||||||
// BlockPath parses a path that validates and captures the username part and the ulid part
|
// BlockPath parses a path that validates and captures the username part and the ulid part
|
||||||
// from eg /users/example_username/blocks/01F7XT5JZW1WMVSW1KADS8PVDH
|
// from eg /users/example_username/blocks/01F7XT5JZW1WMVSW1KADS8PVDH
|
||||||
BlockPath = regexp.MustCompile(blockPath)
|
BlockPath = regexp.MustCompile(blockPath)
|
||||||
|
|
|
@ -188,6 +188,8 @@ type TypeConverter interface {
|
||||||
//
|
//
|
||||||
// Appropriate 'next' and 'prev' fields will be created based on the highest and lowest IDs present in the statuses slice.
|
// Appropriate 'next' and 'prev' fields will be created based on the highest and lowest IDs present in the statuses slice.
|
||||||
StatusesToASOutboxPage(ctx context.Context, outboxID string, maxID string, minID string, statuses []*gtsmodel.Status) (vocab.ActivityStreamsOrderedCollectionPage, error)
|
StatusesToASOutboxPage(ctx context.Context, outboxID string, maxID string, minID string, statuses []*gtsmodel.Status) (vocab.ActivityStreamsOrderedCollectionPage, error)
|
||||||
|
// ReportToASFlag converts a gts model report into an activitystreams FLAG, suitable for federation.
|
||||||
|
ReportToASFlag(ctx context.Context, r *gtsmodel.Report) (vocab.ActivityStreamsFlag, error)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
INTERNAL (gts) MODEL TO INTERNAL MODEL
|
INTERNAL (gts) MODEL TO INTERNAL MODEL
|
||||||
|
|
|
@ -1295,3 +1295,53 @@ func (c *converter) OutboxToASCollection(ctx context.Context, outboxID string) (
|
||||||
|
|
||||||
return collection, nil
|
return collection, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *converter) ReportToASFlag(ctx context.Context, r *gtsmodel.Report) (vocab.ActivityStreamsFlag, error) {
|
||||||
|
flag := streams.NewActivityStreamsFlag()
|
||||||
|
|
||||||
|
flagIDProp := streams.NewJSONLDIdProperty()
|
||||||
|
idURI, err := url.Parse(r.URI)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing url %s: %w", r.URI, err)
|
||||||
|
}
|
||||||
|
flagIDProp.SetIRI(idURI)
|
||||||
|
flag.SetJSONLDId(flagIDProp)
|
||||||
|
|
||||||
|
// for privacy, set the actor as the INSTANCE ACTOR,
|
||||||
|
// not as the actor who created the report
|
||||||
|
instanceAccount, err := c.db.GetInstanceAccount(ctx, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error getting instance account: %w", err)
|
||||||
|
}
|
||||||
|
instanceAccountIRI, err := url.Parse(instanceAccount.URI)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing url %s: %w", instanceAccount.URI, err)
|
||||||
|
}
|
||||||
|
flagActorProp := streams.NewActivityStreamsActorProperty()
|
||||||
|
flagActorProp.AppendIRI(instanceAccountIRI)
|
||||||
|
flag.SetActivityStreamsActor(flagActorProp)
|
||||||
|
|
||||||
|
// content should be the comment submitted when the report was created
|
||||||
|
contentProp := streams.NewActivityStreamsContentProperty()
|
||||||
|
contentProp.AppendXMLSchemaString(r.Comment)
|
||||||
|
flag.SetActivityStreamsContent(contentProp)
|
||||||
|
|
||||||
|
// set at least the target account uri as the object of the flag
|
||||||
|
objectProp := streams.NewActivityStreamsObjectProperty()
|
||||||
|
targetAccountURI, err := url.Parse(r.TargetAccount.URI)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing url %s: %w", r.TargetAccount.URI, err)
|
||||||
|
}
|
||||||
|
objectProp.AppendIRI(targetAccountURI)
|
||||||
|
// also set status URIs if they were provided with the report
|
||||||
|
for _, s := range r.Statuses {
|
||||||
|
statusURI, err := url.Parse(s.URI)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing url %s: %w", s.URI, err)
|
||||||
|
}
|
||||||
|
objectProp.AppendIRI(statusURI)
|
||||||
|
}
|
||||||
|
flag.SetActivityStreamsObject(objectProp)
|
||||||
|
|
||||||
|
return flag, nil
|
||||||
|
}
|
||||||
|
|
|
@ -510,6 +510,40 @@ func (suite *InternalToASTestSuite) TestSelfBoostFollowersOnlyToAS() {
|
||||||
}`, string(bytes))
|
}`, string(bytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *InternalToASTestSuite) TestReportToAS() {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
testReport := suite.testReports["local_account_2_report_remote_account_1"]
|
||||||
|
account := suite.testAccounts["local_account_2"]
|
||||||
|
targetAccount := suite.testAccounts["remote_account_1"]
|
||||||
|
statuses := []*gtsmodel.Status{suite.testStatuses["remote_account_1_status_1"]}
|
||||||
|
|
||||||
|
testReport.Account = account
|
||||||
|
testReport.TargetAccount = targetAccount
|
||||||
|
testReport.Statuses = statuses
|
||||||
|
|
||||||
|
flag, err := suite.typeconverter.ReportToASFlag(ctx, testReport)
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
ser, err := streams.Serialize(flag)
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
bytes, err := json.MarshalIndent(ser, "", " ")
|
||||||
|
suite.NoError(err)
|
||||||
|
|
||||||
|
suite.Equal(`{
|
||||||
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
|
"actor": "http://localhost:8080/users/localhost:8080",
|
||||||
|
"content": "dark souls sucks, please yeet this nerd",
|
||||||
|
"id": "http://localhost:8080/reports/01GP3AWY4CRDVRNZKW0TEAMB5R",
|
||||||
|
"object": [
|
||||||
|
"http://fossbros-anonymous.io/users/foss_satan",
|
||||||
|
"http://fossbros-anonymous.io/users/foss_satan/statuses/01FVW7JHQFSFK166WWKR8CBA6M"
|
||||||
|
],
|
||||||
|
"type": "Flag"
|
||||||
|
}`, string(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
func TestInternalToASTestSuite(t *testing.T) {
|
func TestInternalToASTestSuite(t *testing.T) {
|
||||||
suite.Run(t, new(InternalToASTestSuite))
|
suite.Run(t, new(InternalToASTestSuite))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1928,7 +1928,7 @@ func NewTestReports() map[string]*gtsmodel.Report {
|
||||||
ID: "01GP3AWY4CRDVRNZKW0TEAMB5R",
|
ID: "01GP3AWY4CRDVRNZKW0TEAMB5R",
|
||||||
CreatedAt: TimeMustParse("2022-05-14T12:20:03+02:00"),
|
CreatedAt: TimeMustParse("2022-05-14T12:20:03+02:00"),
|
||||||
UpdatedAt: TimeMustParse("2022-05-14T12:20:03+02:00"),
|
UpdatedAt: TimeMustParse("2022-05-14T12:20:03+02:00"),
|
||||||
URI: "http://localhost:8080/01GP3AWY4CRDVRNZKW0TEAMB5R",
|
URI: "http://localhost:8080/reports/01GP3AWY4CRDVRNZKW0TEAMB5R",
|
||||||
AccountID: "01F8MH5NBDF2MV7CTC4Q5128HF",
|
AccountID: "01F8MH5NBDF2MV7CTC4Q5128HF",
|
||||||
TargetAccountID: "01F8MH5ZK5VRH73AKHQM6Y9VNX",
|
TargetAccountID: "01F8MH5ZK5VRH73AKHQM6Y9VNX",
|
||||||
Comment: "dark souls sucks, please yeet this nerd",
|
Comment: "dark souls sucks, please yeet this nerd",
|
||||||
|
|
Loading…
Reference in New Issue