diff options
Diffstat (limited to 'internal/typeutils')
-rw-r--r-- | internal/typeutils/asextractionutil.go | 570 | ||||
-rw-r--r-- | internal/typeutils/asinterfaces.go | 265 | ||||
-rw-r--r-- | internal/typeutils/astointernal.go | 83 | ||||
-rw-r--r-- | internal/typeutils/astointernal_test.go | 9 | ||||
-rw-r--r-- | internal/typeutils/converter.go | 22 | ||||
-rw-r--r-- | internal/typeutils/converter_test.go | 3 | ||||
-rw-r--r-- | internal/typeutils/internaltoas.go | 145 | ||||
-rw-r--r-- | internal/typeutils/internaltoas_test.go | 2 |
8 files changed, 208 insertions, 891 deletions
diff --git a/internal/typeutils/asextractionutil.go b/internal/typeutils/asextractionutil.go deleted file mode 100644 index b3e6eb2c4..000000000 --- a/internal/typeutils/asextractionutil.go +++ /dev/null @@ -1,570 +0,0 @@ -/* - 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 typeutils - -import ( - "crypto/rsa" - "crypto/x509" - "encoding/pem" - "errors" - "fmt" - "net/url" - "strings" - "time" - - "github.com/go-fed/activity/pub" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/util" -) - -func extractPreferredUsername(i withPreferredUsername) (string, error) { - u := i.GetActivityStreamsPreferredUsername() - if u == nil || !u.IsXMLSchemaString() { - return "", errors.New("preferredUsername was not a string") - } - if u.GetXMLSchemaString() == "" { - return "", errors.New("preferredUsername was empty") - } - return u.GetXMLSchemaString(), nil -} - -func extractName(i withName) (string, error) { - nameProp := i.GetActivityStreamsName() - if nameProp == nil { - return "", errors.New("activityStreamsName not found") - } - - // take the first name string we can find - for iter := nameProp.Begin(); iter != nameProp.End(); iter = iter.Next() { - if iter.IsXMLSchemaString() && iter.GetXMLSchemaString() != "" { - return iter.GetXMLSchemaString(), nil - } - } - - return "", errors.New("activityStreamsName not found") -} - -func extractInReplyToURI(i withInReplyTo) (*url.URL, error) { - inReplyToProp := i.GetActivityStreamsInReplyTo() - if inReplyToProp == nil { - return nil, errors.New("in reply to prop was nil") - } - for iter := inReplyToProp.Begin(); iter != inReplyToProp.End(); iter = iter.Next() { - if iter.IsIRI() { - if iter.GetIRI() != nil { - return iter.GetIRI(), nil - } - } - } - return nil, errors.New("couldn't find iri for in reply to") -} - -func extractTos(i withTo) ([]*url.URL, error) { - to := []*url.URL{} - toProp := i.GetActivityStreamsTo() - if toProp == nil { - return nil, errors.New("toProp was nil") - } - for iter := toProp.Begin(); iter != toProp.End(); iter = iter.Next() { - if iter.IsIRI() { - if iter.GetIRI() != nil { - to = append(to, iter.GetIRI()) - } - } - } - return to, nil -} - -func extractCCs(i withCC) ([]*url.URL, error) { - cc := []*url.URL{} - ccProp := i.GetActivityStreamsCc() - if ccProp == nil { - return cc, nil - } - for iter := ccProp.Begin(); iter != ccProp.End(); iter = iter.Next() { - if iter.IsIRI() { - if iter.GetIRI() != nil { - cc = append(cc, iter.GetIRI()) - } - } - } - return cc, nil -} - -func extractAttributedTo(i withAttributedTo) (*url.URL, error) { - attributedToProp := i.GetActivityStreamsAttributedTo() - if attributedToProp == nil { - return nil, errors.New("attributedToProp was nil") - } - for iter := attributedToProp.Begin(); iter != attributedToProp.End(); iter = iter.Next() { - if iter.IsIRI() { - if iter.GetIRI() != nil { - return iter.GetIRI(), nil - } - } - } - return nil, errors.New("couldn't find iri for attributed to") -} - -func extractPublished(i withPublished) (time.Time, error) { - publishedProp := i.GetActivityStreamsPublished() - if publishedProp == nil { - return time.Time{}, errors.New("published prop was nil") - } - - if !publishedProp.IsXMLSchemaDateTime() { - return time.Time{}, errors.New("published prop was not date time") - } - - t := publishedProp.Get() - if t.IsZero() { - return time.Time{}, errors.New("published time was zero") - } - return t, nil -} - -// extractIconURL extracts a URL to a supported image file from something like: -// "icon": { -// "mediaType": "image/jpeg", -// "type": "Image", -// "url": "http://example.org/path/to/some/file.jpeg" -// }, -func extractIconURL(i withIcon) (*url.URL, error) { - iconProp := i.GetActivityStreamsIcon() - if iconProp == nil { - return nil, errors.New("icon property was nil") - } - - // icon can potentially contain multiple entries, so we iterate through all of them - // here in order to find the first one that meets these criteria: - // 1. is an image - // 2. has a URL so we can grab it - for iter := iconProp.Begin(); iter != iconProp.End(); iter = iter.Next() { - // 1. is an image - if !iter.IsActivityStreamsImage() { - continue - } - imageValue := iter.GetActivityStreamsImage() - if imageValue == nil { - continue - } - - // 2. has a URL so we can grab it - url, err := extractURL(imageValue) - if err == nil && url != nil { - return url, nil - } - } - // if we get to this point we didn't find an icon meeting our criteria :'( - return nil, errors.New("could not extract valid image from icon") -} - -// extractImageURL extracts a URL to a supported image file from something like: -// "image": { -// "mediaType": "image/jpeg", -// "type": "Image", -// "url": "http://example.org/path/to/some/file.jpeg" -// }, -func extractImageURL(i withImage) (*url.URL, error) { - imageProp := i.GetActivityStreamsImage() - if imageProp == nil { - return nil, errors.New("icon property was nil") - } - - // icon can potentially contain multiple entries, so we iterate through all of them - // here in order to find the first one that meets these criteria: - // 1. is an image - // 2. has a URL so we can grab it - for iter := imageProp.Begin(); iter != imageProp.End(); iter = iter.Next() { - // 1. is an image - if !iter.IsActivityStreamsImage() { - continue - } - imageValue := iter.GetActivityStreamsImage() - if imageValue == nil { - continue - } - - // 2. has a URL so we can grab it - url, err := extractURL(imageValue) - if err == nil && url != nil { - return url, nil - } - } - // if we get to this point we didn't find an image meeting our criteria :'( - return nil, errors.New("could not extract valid image from image property") -} - -func extractSummary(i withSummary) (string, error) { - summaryProp := i.GetActivityStreamsSummary() - if summaryProp == nil { - return "", errors.New("summary property was nil") - } - - for iter := summaryProp.Begin(); iter != summaryProp.End(); iter = iter.Next() { - if iter.IsXMLSchemaString() && iter.GetXMLSchemaString() != "" { - return iter.GetXMLSchemaString(), nil - } - } - - return "", errors.New("could not extract summary") -} - -func extractDiscoverable(i withDiscoverable) (bool, error) { - if i.GetTootDiscoverable() == nil { - return false, errors.New("discoverable was nil") - } - return i.GetTootDiscoverable().Get(), nil -} - -func extractURL(i withURL) (*url.URL, error) { - urlProp := i.GetActivityStreamsUrl() - if urlProp == nil { - return nil, errors.New("url property was nil") - } - - for iter := urlProp.Begin(); iter != urlProp.End(); iter = iter.Next() { - if iter.IsIRI() && iter.GetIRI() != nil { - return iter.GetIRI(), nil - } - } - - return nil, errors.New("could not extract url") -} - -func extractPublicKeyForOwner(i withPublicKey, forOwner *url.URL) (*rsa.PublicKey, *url.URL, error) { - publicKeyProp := i.GetW3IDSecurityV1PublicKey() - if publicKeyProp == nil { - return nil, nil, errors.New("public key property was nil") - } - - for iter := publicKeyProp.Begin(); iter != publicKeyProp.End(); iter = iter.Next() { - pkey := iter.Get() - if pkey == nil { - continue - } - - pkeyID, err := pub.GetId(pkey) - if err != nil || pkeyID == nil { - continue - } - - if pkey.GetW3IDSecurityV1Owner() == nil || pkey.GetW3IDSecurityV1Owner().Get() == nil || pkey.GetW3IDSecurityV1Owner().Get().String() != forOwner.String() { - continue - } - - if pkey.GetW3IDSecurityV1PublicKeyPem() == nil { - continue - } - - pkeyPem := pkey.GetW3IDSecurityV1PublicKeyPem().Get() - if pkeyPem == "" { - continue - } - - block, _ := pem.Decode([]byte(pkeyPem)) - if block == nil || block.Type != "PUBLIC KEY" { - return nil, nil, errors.New("could not decode publicKeyPem to PUBLIC KEY pem block type") - } - - p, err := x509.ParsePKIXPublicKey(block.Bytes) - if err != nil { - return nil, nil, fmt.Errorf("could not parse public key from block bytes: %s", err) - } - if p == nil { - return nil, nil, errors.New("returned public key was empty") - } - - if publicKey, ok := p.(*rsa.PublicKey); ok { - return publicKey, pkeyID, nil - } - } - return nil, nil, errors.New("couldn't find public key") -} - -func extractContent(i withContent) (string, error) { - contentProperty := i.GetActivityStreamsContent() - if contentProperty == nil { - return "", errors.New("content property was nil") - } - for iter := contentProperty.Begin(); iter != contentProperty.End(); iter = iter.Next() { - if iter.IsXMLSchemaString() && iter.GetXMLSchemaString() != "" { - return iter.GetXMLSchemaString(), nil - } - } - return "", errors.New("no content found") -} - -func extractAttachments(i withAttachment) ([]*gtsmodel.MediaAttachment, error) { - attachments := []*gtsmodel.MediaAttachment{} - attachmentProp := i.GetActivityStreamsAttachment() - if attachmentProp == nil { - return attachments, nil - } - for iter := attachmentProp.Begin(); iter != attachmentProp.End(); iter = iter.Next() { - t := iter.GetType() - if t == nil { - continue - } - attachmentable, ok := t.(Attachmentable) - if !ok { - continue - } - attachment, err := extractAttachment(attachmentable) - if err != nil { - continue - } - attachments = append(attachments, attachment) - } - return attachments, nil -} - -func extractAttachment(i Attachmentable) (*gtsmodel.MediaAttachment, error) { - attachment := >smodel.MediaAttachment{ - File: gtsmodel.File{}, - } - - attachmentURL, err := extractURL(i) - if err != nil { - return nil, err - } - attachment.RemoteURL = attachmentURL.String() - - mediaType := i.GetActivityStreamsMediaType() - if mediaType == nil { - return nil, errors.New("no media type") - } - if mediaType.Get() == "" { - return nil, errors.New("no media type") - } - attachment.File.ContentType = mediaType.Get() - attachment.Type = gtsmodel.FileTypeImage - - name, err := extractName(i) - if err == nil { - attachment.Description = name - } - - attachment.Processing = gtsmodel.ProcessingStatusReceived - - return attachment, nil -} - -// func extractBlurhash(i withBlurhash) (string, error) { -// if i.GetTootBlurhashProperty() == nil { -// return "", errors.New("blurhash property was nil") -// } -// if i.GetTootBlurhashProperty().Get() == "" { -// return "", errors.New("empty blurhash string") -// } -// return i.GetTootBlurhashProperty().Get(), nil -// } - -func extractHashtags(i withTag) ([]*gtsmodel.Tag, error) { - tags := []*gtsmodel.Tag{} - tagsProp := i.GetActivityStreamsTag() - if tagsProp == nil { - return tags, nil - } - for iter := tagsProp.Begin(); iter != tagsProp.End(); iter = iter.Next() { - t := iter.GetType() - if t == nil { - continue - } - - if t.GetTypeName() != "Hashtag" { - continue - } - - hashtaggable, ok := t.(Hashtaggable) - if !ok { - continue - } - - tag, err := extractHashtag(hashtaggable) - if err != nil { - continue - } - - tags = append(tags, tag) - } - return tags, nil -} - -func extractHashtag(i Hashtaggable) (*gtsmodel.Tag, error) { - tag := >smodel.Tag{} - - hrefProp := i.GetActivityStreamsHref() - if hrefProp == nil || !hrefProp.IsIRI() { - return nil, errors.New("no href prop") - } - tag.URL = hrefProp.GetIRI().String() - - name, err := extractName(i) - if err != nil { - return nil, err - } - tag.Name = strings.TrimPrefix(name, "#") - - return tag, nil -} - -func extractEmojis(i withTag) ([]*gtsmodel.Emoji, error) { - emojis := []*gtsmodel.Emoji{} - tagsProp := i.GetActivityStreamsTag() - if tagsProp == nil { - return emojis, nil - } - for iter := tagsProp.Begin(); iter != tagsProp.End(); iter = iter.Next() { - t := iter.GetType() - if t == nil { - continue - } - - if t.GetTypeName() != "Emoji" { - continue - } - - emojiable, ok := t.(Emojiable) - if !ok { - continue - } - - emoji, err := extractEmoji(emojiable) - if err != nil { - continue - } - - emojis = append(emojis, emoji) - } - return emojis, nil -} - -func extractEmoji(i Emojiable) (*gtsmodel.Emoji, error) { - emoji := >smodel.Emoji{} - - idProp := i.GetJSONLDId() - if idProp == nil || !idProp.IsIRI() { - return nil, errors.New("no id for emoji") - } - uri := idProp.GetIRI() - emoji.URI = uri.String() - emoji.Domain = uri.Host - - name, err := extractName(i) - if err != nil { - return nil, err - } - emoji.Shortcode = strings.Trim(name, ":") - - if i.GetActivityStreamsIcon() == nil { - return nil, errors.New("no icon for emoji") - } - imageURL, err := extractIconURL(i) - if err != nil { - return nil, errors.New("no url for emoji image") - } - emoji.ImageRemoteURL = imageURL.String() - - return emoji, nil -} - -func extractMentions(i withTag) ([]*gtsmodel.Mention, error) { - mentions := []*gtsmodel.Mention{} - tagsProp := i.GetActivityStreamsTag() - if tagsProp == nil { - return mentions, nil - } - for iter := tagsProp.Begin(); iter != tagsProp.End(); iter = iter.Next() { - t := iter.GetType() - if t == nil { - continue - } - - if t.GetTypeName() != "Mention" { - continue - } - - mentionable, ok := t.(Mentionable) - if !ok { - continue - } - - mention, err := extractMention(mentionable) - if err != nil { - continue - } - - mentions = append(mentions, mention) - } - return mentions, nil -} - -func extractMention(i Mentionable) (*gtsmodel.Mention, error) { - mention := >smodel.Mention{} - - mentionString, err := extractName(i) - if err != nil { - return nil, err - } - - // just make sure the mention string is valid so we can handle it properly later on... - username, domain, err := util.ExtractMentionParts(mentionString) - if err != nil { - return nil, err - } - if username == "" || domain == "" { - return nil, errors.New("username or domain was empty") - } - mention.NameString = mentionString - - // the href prop should be the AP URI of a user we know, eg https://example.org/users/whatever_user - hrefProp := i.GetActivityStreamsHref() - if hrefProp == nil || !hrefProp.IsIRI() { - return nil, errors.New("no href prop") - } - mention.MentionedAccountURI = hrefProp.GetIRI().String() - return mention, nil -} - -func extractActor(i withActor) (*url.URL, error) { - actorProp := i.GetActivityStreamsActor() - if actorProp == nil { - return nil, errors.New("actor property was nil") - } - for iter := actorProp.Begin(); iter != actorProp.End(); iter = iter.Next() { - if iter.IsIRI() && iter.GetIRI() != nil { - return iter.GetIRI(), nil - } - } - return nil, errors.New("no iri found for actor prop") -} - -func extractObject(i withObject) (*url.URL, error) { - objectProp := i.GetActivityStreamsObject() - if objectProp == nil { - return nil, errors.New("object property was nil") - } - for iter := objectProp.Begin(); iter != objectProp.End(); iter = iter.Next() { - if iter.IsIRI() && iter.GetIRI() != nil { - return iter.GetIRI(), nil - } - } - return nil, errors.New("no iri found for object prop") -} diff --git a/internal/typeutils/asinterfaces.go b/internal/typeutils/asinterfaces.go deleted file mode 100644 index d0b1cf617..000000000 --- a/internal/typeutils/asinterfaces.go +++ /dev/null @@ -1,265 +0,0 @@ -/* - 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 typeutils - -import "github.com/go-fed/activity/streams/vocab" - -// Accountable represents the minimum activitypub interface for representing an 'account'. -// This interface is fulfilled by: Person, Application, Organization, Service, and Group -type Accountable interface { - withJSONLDId - withTypeName - - withPreferredUsername - withIcon - withName - withImage - withSummary - withDiscoverable - withURL - withPublicKey - withInbox - withOutbox - withFollowing - withFollowers - withFeatured -} - -// Statusable represents the minimum activitypub interface for representing a 'status'. -// This interface is fulfilled by: Article, Document, Image, Video, Note, Page, Event, Place, Mention, Profile -type Statusable interface { - withJSONLDId - withTypeName - - withSummary - withInReplyTo - withPublished - withURL - withAttributedTo - withTo - withCC - withSensitive - withConversation - withContent - withAttachment - withTag - withReplies -} - -// Attachmentable represents the minimum activitypub interface for representing a 'mediaAttachment'. -// This interface is fulfilled by: Audio, Document, Image, Video -type Attachmentable interface { - withTypeName - withMediaType - withURL - withName -} - -// Hashtaggable represents the minimum activitypub interface for representing a 'hashtag' tag. -type Hashtaggable interface { - withTypeName - withHref - withName -} - -// Emojiable represents the minimum interface for an 'emoji' tag. -type Emojiable interface { - withJSONLDId - withTypeName - withName - withUpdated - withIcon -} - -// Mentionable represents the minimum interface for a 'mention' tag. -type Mentionable interface { - withName - withHref -} - -// Followable represents the minimum interface for an activitystreams 'follow' activity. -type Followable interface { - withJSONLDId - withTypeName - - withActor - withObject -} - -// Likeable represents the minimum interface for an activitystreams 'like' activity. -type Likeable interface { - withJSONLDId - withTypeName - - withActor - withObject -} - -// Blockable represents the minimum interface for an activitystreams 'block' activity. -type Blockable interface { - withJSONLDId - withTypeName - - withActor - withObject -} - -// Announceable represents the minimum interface for an activitystreams 'announce' activity. -type Announceable interface { - withJSONLDId - withTypeName - - withActor - withObject - withPublished - withTo - withCC -} - -type withJSONLDId interface { - GetJSONLDId() vocab.JSONLDIdProperty -} - -type withTypeName interface { - GetTypeName() string -} - -type withPreferredUsername interface { - GetActivityStreamsPreferredUsername() vocab.ActivityStreamsPreferredUsernameProperty -} - -type withIcon interface { - GetActivityStreamsIcon() vocab.ActivityStreamsIconProperty -} - -type withName interface { - GetActivityStreamsName() vocab.ActivityStreamsNameProperty -} - -type withImage interface { - GetActivityStreamsImage() vocab.ActivityStreamsImageProperty -} - -type withSummary interface { - GetActivityStreamsSummary() vocab.ActivityStreamsSummaryProperty -} - -type withDiscoverable interface { - GetTootDiscoverable() vocab.TootDiscoverableProperty -} - -type withURL interface { - GetActivityStreamsUrl() vocab.ActivityStreamsUrlProperty -} - -type withPublicKey interface { - GetW3IDSecurityV1PublicKey() vocab.W3IDSecurityV1PublicKeyProperty -} - -type withInbox interface { - GetActivityStreamsInbox() vocab.ActivityStreamsInboxProperty -} - -type withOutbox interface { - GetActivityStreamsOutbox() vocab.ActivityStreamsOutboxProperty -} - -type withFollowing interface { - GetActivityStreamsFollowing() vocab.ActivityStreamsFollowingProperty -} - -type withFollowers interface { - GetActivityStreamsFollowers() vocab.ActivityStreamsFollowersProperty -} - -type withFeatured interface { - GetTootFeatured() vocab.TootFeaturedProperty -} - -type withAttributedTo interface { - GetActivityStreamsAttributedTo() vocab.ActivityStreamsAttributedToProperty -} - -type withAttachment interface { - GetActivityStreamsAttachment() vocab.ActivityStreamsAttachmentProperty -} - -type withTo interface { - GetActivityStreamsTo() vocab.ActivityStreamsToProperty -} - -type withInReplyTo interface { - GetActivityStreamsInReplyTo() vocab.ActivityStreamsInReplyToProperty -} - -type withCC interface { - GetActivityStreamsCc() vocab.ActivityStreamsCcProperty -} - -type withSensitive interface { - // TODO -} - -type withConversation interface { - // TODO -} - -type withContent interface { - GetActivityStreamsContent() vocab.ActivityStreamsContentProperty -} - -type withPublished interface { - GetActivityStreamsPublished() vocab.ActivityStreamsPublishedProperty -} - -type withTag interface { - GetActivityStreamsTag() vocab.ActivityStreamsTagProperty -} - -type withReplies interface { - GetActivityStreamsReplies() vocab.ActivityStreamsRepliesProperty -} - -type withMediaType interface { - GetActivityStreamsMediaType() vocab.ActivityStreamsMediaTypeProperty -} - -// type withBlurhash interface { -// GetTootBlurhashProperty() vocab.TootBlurhashProperty -// } - -// type withFocalPoint interface { -// // TODO -// } - -type withHref interface { - GetActivityStreamsHref() vocab.ActivityStreamsHrefProperty -} - -type withUpdated interface { - GetActivityStreamsUpdated() vocab.ActivityStreamsUpdatedProperty -} - -type withActor interface { - GetActivityStreamsActor() vocab.ActivityStreamsActorProperty -} - -type withObject interface { - GetActivityStreamsObject() vocab.ActivityStreamsObjectProperty -} diff --git a/internal/typeutils/astointernal.go b/internal/typeutils/astointernal.go index 394de6e82..f754d282a 100644 --- a/internal/typeutils/astointernal.go +++ b/internal/typeutils/astointernal.go @@ -24,11 +24,12 @@ import ( "net/url" "strings" + "github.com/superseriousbusiness/gotosocial/internal/ap" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) -func (c *converter) ASRepresentationToAccount(accountable Accountable, update bool) (*gtsmodel.Account, error) { +func (c *converter) ASRepresentationToAccount(accountable ap.Accountable, update bool) (*gtsmodel.Account, error) { // first check if we actually already know this account uriProp := accountable.GetJSONLDId() if uriProp == nil || !uriProp.IsIRI() { @@ -55,7 +56,7 @@ func (c *converter) ASRepresentationToAccount(accountable Accountable, update bo // Username aka preferredUsername // We need this one so bail if it's not set. - username, err := extractPreferredUsername(accountable) + username, err := ap.ExtractPreferredUsername(accountable) if err != nil { return nil, fmt.Errorf("couldn't extract username: %s", err) } @@ -66,27 +67,27 @@ func (c *converter) ASRepresentationToAccount(accountable Accountable, update bo // avatar aka icon // if this one isn't extractable in a format we recognise we'll just skip it - if avatarURL, err := extractIconURL(accountable); err == nil { + if avatarURL, err := ap.ExtractIconURL(accountable); err == nil { acct.AvatarRemoteURL = avatarURL.String() } // header aka image // if this one isn't extractable in a format we recognise we'll just skip it - if headerURL, err := extractImageURL(accountable); err == nil { + if headerURL, err := ap.ExtractImageURL(accountable); err == nil { acct.HeaderRemoteURL = headerURL.String() } // display name aka name // we default to the username, but take the more nuanced name property if it exists acct.DisplayName = username - if displayName, err := extractName(accountable); err == nil { + if displayName, err := ap.ExtractName(accountable); err == nil { acct.DisplayName = displayName } // TODO: fields aka attachment array // note aka summary - note, err := extractSummary(accountable) + note, err := ap.ExtractSummary(accountable) if err == nil && note != "" { acct.Note = note } @@ -110,13 +111,13 @@ func (c *converter) ASRepresentationToAccount(accountable Accountable, update bo // discoverable // default to false -- take custom value if it's set though acct.Discoverable = false - discoverable, err := extractDiscoverable(accountable) + discoverable, err := ap.ExtractDiscoverable(accountable) if err == nil { acct.Discoverable = discoverable } // url property - url, err := extractURL(accountable) + url, err := ap.ExtractURL(accountable) if err == nil { // take the URL if we can find it acct.URL = url.String() @@ -155,7 +156,7 @@ func (c *converter) ASRepresentationToAccount(accountable Accountable, update bo // TODO: alsoKnownAs // publicKey - pkey, pkeyURL, err := extractPublicKeyForOwner(accountable, uri) + pkey, pkeyURL, err := ap.ExtractPublicKeyForOwner(accountable, uri) if err != nil { return nil, fmt.Errorf("couldn't get public key for person %s: %s", uri.String(), err) } @@ -165,7 +166,7 @@ func (c *converter) ASRepresentationToAccount(accountable Accountable, update bo return acct, nil } -func (c *converter) ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, error) { +func (c *converter) ASStatusToStatus(statusable ap.Statusable) (*gtsmodel.Status, error) { status := >smodel.Status{} // uri at which this status is reachable @@ -176,49 +177,49 @@ func (c *converter) ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, e status.URI = uriProp.GetIRI().String() // web url for viewing this status - if statusURL, err := extractURL(statusable); err == nil { + if statusURL, err := ap.ExtractURL(statusable); err == nil { status.URL = statusURL.String() } // the html-formatted content of this status - if content, err := extractContent(statusable); err == nil { + if content, err := ap.ExtractContent(statusable); err == nil { status.Content = content } // attachments to dereference and fetch later on (we don't do that here) - if attachments, err := extractAttachments(statusable); err == nil { + if attachments, err := ap.ExtractAttachments(statusable); err == nil { status.GTSMediaAttachments = attachments } // hashtags to dereference later on - if hashtags, err := extractHashtags(statusable); err == nil { + if hashtags, err := ap.ExtractHashtags(statusable); err == nil { status.GTSTags = hashtags } // emojis to dereference and fetch later on - if emojis, err := extractEmojis(statusable); err == nil { + if emojis, err := ap.ExtractEmojis(statusable); err == nil { status.GTSEmojis = emojis } // mentions to dereference later on - if mentions, err := extractMentions(statusable); err == nil { + if mentions, err := ap.ExtractMentions(statusable); err == nil { status.GTSMentions = mentions } // cw string for this status - if cw, err := extractSummary(statusable); err == nil { + if cw, err := ap.ExtractSummary(statusable); err == nil { status.ContentWarning = cw } // when was this status created? - published, err := extractPublished(statusable) + published, err := ap.ExtractPublished(statusable) if err == nil { status.CreatedAt = published } // which account posted this status? // if we don't know the account yet we can dereference it later - attributedTo, err := extractAttributedTo(statusable) + attributedTo, err := ap.ExtractAttributedTo(statusable) if err != nil { return nil, errors.New("attributedTo was empty") } @@ -233,8 +234,8 @@ func (c *converter) ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, e status.GTSAuthorAccount = statusOwner // check if there's a post that this is a reply to - inReplyToURI, err := extractInReplyToURI(statusable) - if err == nil { + inReplyToURI := ap.ExtractInReplyToURI(statusable) + if inReplyToURI != nil { // something is set so we can at least set this field on the // status and dereference using this later if we need to status.InReplyToURI = inReplyToURI.String() @@ -259,12 +260,12 @@ func (c *converter) ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, e // visibility entry for this status var visibility gtsmodel.Visibility - to, err := extractTos(statusable) + to, err := ap.ExtractTos(statusable) if err != nil { return nil, fmt.Errorf("error extracting TO values: %s", err) } - cc, err := extractCCs(statusable) + cc, err := ap.ExtractCCs(statusable) if err != nil { return nil, fmt.Errorf("error extracting CC values: %s", err) } @@ -315,7 +316,7 @@ func (c *converter) ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, e return status, nil } -func (c *converter) ASFollowToFollowRequest(followable Followable) (*gtsmodel.FollowRequest, error) { +func (c *converter) ASFollowToFollowRequest(followable ap.Followable) (*gtsmodel.FollowRequest, error) { idProp := followable.GetJSONLDId() if idProp == nil || !idProp.IsIRI() { @@ -323,7 +324,7 @@ func (c *converter) ASFollowToFollowRequest(followable Followable) (*gtsmodel.Fo } uri := idProp.GetIRI().String() - origin, err := extractActor(followable) + origin, err := ap.ExtractActor(followable) if err != nil { return nil, errors.New("error extracting actor property from follow") } @@ -332,7 +333,7 @@ func (c *converter) ASFollowToFollowRequest(followable Followable) (*gtsmodel.Fo return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err) } - target, err := extractObject(followable) + target, err := ap.ExtractObject(followable) if err != nil { return nil, errors.New("error extracting object property from follow") } @@ -350,14 +351,14 @@ func (c *converter) ASFollowToFollowRequest(followable Followable) (*gtsmodel.Fo return followRequest, nil } -func (c *converter) ASFollowToFollow(followable Followable) (*gtsmodel.Follow, error) { +func (c *converter) ASFollowToFollow(followable ap.Followable) (*gtsmodel.Follow, error) { idProp := followable.GetJSONLDId() if idProp == nil || !idProp.IsIRI() { return nil, errors.New("no id property set on follow, or was not an iri") } uri := idProp.GetIRI().String() - origin, err := extractActor(followable) + origin, err := ap.ExtractActor(followable) if err != nil { return nil, errors.New("error extracting actor property from follow") } @@ -366,7 +367,7 @@ func (c *converter) ASFollowToFollow(followable Followable) (*gtsmodel.Follow, e return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err) } - target, err := extractObject(followable) + target, err := ap.ExtractObject(followable) if err != nil { return nil, errors.New("error extracting object property from follow") } @@ -384,14 +385,14 @@ func (c *converter) ASFollowToFollow(followable Followable) (*gtsmodel.Follow, e return follow, nil } -func (c *converter) ASLikeToFave(likeable Likeable) (*gtsmodel.StatusFave, error) { +func (c *converter) ASLikeToFave(likeable ap.Likeable) (*gtsmodel.StatusFave, error) { idProp := likeable.GetJSONLDId() if idProp == nil || !idProp.IsIRI() { return nil, errors.New("no id property set on like, or was not an iri") } uri := idProp.GetIRI().String() - origin, err := extractActor(likeable) + origin, err := ap.ExtractActor(likeable) if err != nil { return nil, errors.New("error extracting actor property from like") } @@ -400,7 +401,7 @@ func (c *converter) ASLikeToFave(likeable Likeable) (*gtsmodel.StatusFave, error return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err) } - target, err := extractObject(likeable) + target, err := ap.ExtractObject(likeable) if err != nil { return nil, errors.New("error extracting object property from like") } @@ -426,14 +427,14 @@ func (c *converter) ASLikeToFave(likeable Likeable) (*gtsmodel.StatusFave, error }, nil } -func (c *converter) ASBlockToBlock(blockable Blockable) (*gtsmodel.Block, error) { +func (c *converter) ASBlockToBlock(blockable ap.Blockable) (*gtsmodel.Block, error) { idProp := blockable.GetJSONLDId() if idProp == nil || !idProp.IsIRI() { return nil, errors.New("ASBlockToBlock: no id property set on block, or was not an iri") } uri := idProp.GetIRI().String() - origin, err := extractActor(blockable) + origin, err := ap.ExtractActor(blockable) if err != nil { return nil, errors.New("ASBlockToBlock: error extracting actor property from block") } @@ -442,7 +443,7 @@ func (c *converter) ASBlockToBlock(blockable Blockable) (*gtsmodel.Block, error) return nil, fmt.Errorf("ASBlockToBlock: error extracting account with uri %s from the database: %s", origin.String(), err) } - target, err := extractObject(blockable) + target, err := ap.ExtractObject(blockable) if err != nil { return nil, errors.New("ASBlockToBlock: error extracting object property from block") } @@ -461,7 +462,7 @@ func (c *converter) ASBlockToBlock(blockable Blockable) (*gtsmodel.Block, error) }, nil } -func (c *converter) ASAnnounceToStatus(announceable Announceable) (*gtsmodel.Status, bool, error) { +func (c *converter) ASAnnounceToStatus(announceable ap.Announceable) (*gtsmodel.Status, bool, error) { status := >smodel.Status{} isNew := true @@ -480,7 +481,7 @@ func (c *converter) ASAnnounceToStatus(announceable Announceable) (*gtsmodel.Sta status.URI = uri // get the URI of the announced/boosted status - boostedStatusURI, err := extractObject(announceable) + boostedStatusURI, err := ap.ExtractObject(announceable) if err != nil { return nil, isNew, fmt.Errorf("ASAnnounceToStatus: error getting object from announce: %s", err) } @@ -491,7 +492,7 @@ func (c *converter) ASAnnounceToStatus(announceable Announceable) (*gtsmodel.Sta } // get the published time for the announce - published, err := extractPublished(announceable) + published, err := ap.ExtractPublished(announceable) if err != nil { return nil, isNew, fmt.Errorf("ASAnnounceToStatus: error extracting published time: %s", err) } @@ -499,7 +500,7 @@ func (c *converter) ASAnnounceToStatus(announceable Announceable) (*gtsmodel.Sta status.UpdatedAt = published // get the actor's IRI (ie., the person who boosted the status) - actor, err := extractActor(announceable) + actor, err := ap.ExtractActor(announceable) if err != nil { return nil, isNew, fmt.Errorf("ASAnnounceToStatus: error extracting actor: %s", err) } @@ -522,12 +523,12 @@ func (c *converter) ASAnnounceToStatus(announceable Announceable) (*gtsmodel.Sta // parse the visibility from the To and CC entries var visibility gtsmodel.Visibility - to, err := extractTos(announceable) + to, err := ap.ExtractTos(announceable) if err != nil { return nil, isNew, fmt.Errorf("error extracting TO values: %s", err) } - cc, err := extractCCs(announceable) + cc, err := ap.ExtractCCs(announceable) if err != nil { return nil, isNew, fmt.Errorf("error extracting CC values: %s", err) } diff --git a/internal/typeutils/astointernal_test.go b/internal/typeutils/astointernal_test.go index 9d6ce4e0a..2e33271c5 100644 --- a/internal/typeutils/astointernal_test.go +++ b/internal/typeutils/astointernal_test.go @@ -28,6 +28,7 @@ import ( "github.com/go-fed/activity/streams/vocab" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/ap" "github.com/superseriousbusiness/gotosocial/internal/typeutils" "github.com/superseriousbusiness/gotosocial/testrig" ) @@ -342,7 +343,7 @@ func (suite *ASToInternalTestSuite) SetupSuite() { } func (suite *ASToInternalTestSuite) SetupTest() { - testrig.StandardDBSetup(suite.db) + testrig.StandardDBSetup(suite.db, nil) } func (suite *ASToInternalTestSuite) TestParsePerson() { @@ -364,7 +365,7 @@ func (suite *ASToInternalTestSuite) TestParseGargron() { t, err := streams.ToType(context.Background(), m) assert.NoError(suite.T(), err) - rep, ok := t.(typeutils.Accountable) + rep, ok := t.(ap.Accountable) assert.True(suite.T(), ok) acct, err := suite.typeconverter.ASRepresentationToAccount(rep, false) @@ -391,7 +392,7 @@ func (suite *ASToInternalTestSuite) TestParseStatus() { first := obj.Begin() assert.NotNil(suite.T(), first) - rep, ok := first.GetType().(typeutils.Statusable) + rep, ok := first.GetType().(ap.Statusable) assert.True(suite.T(), ok) status, err := suite.typeconverter.ASStatusToStatus(rep) @@ -418,7 +419,7 @@ func (suite *ASToInternalTestSuite) TestParseStatusWithMention() { first := obj.Begin() assert.NotNil(suite.T(), first) - rep, ok := first.GetType().(typeutils.Statusable) + rep, ok := first.GetType().(ap.Statusable) assert.True(suite.T(), ok) status, err := suite.typeconverter.ASStatusToStatus(rep) diff --git a/internal/typeutils/converter.go b/internal/typeutils/converter.go index 57c2a1f6d..10d9a0f18 100644 --- a/internal/typeutils/converter.go +++ b/internal/typeutils/converter.go @@ -19,7 +19,10 @@ package typeutils import ( + "net/url" + "github.com/go-fed/activity/streams/vocab" + "github.com/superseriousbusiness/gotosocial/internal/ap" "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" @@ -99,17 +102,17 @@ type TypeConverter interface { // If update is false, and the account is already known in the database, then the existing account entry will be returned. // If update is true, then even if the account is already known, all fields in the accountable will be parsed and a new *gtsmodel.Account // will be generated. This is useful when one needs to force refresh of an account, eg., during an Update of a Profile. - ASRepresentationToAccount(accountable Accountable, update bool) (*gtsmodel.Account, error) + ASRepresentationToAccount(accountable ap.Accountable, update bool) (*gtsmodel.Account, error) // ASStatus converts a remote activitystreams 'status' representation into a gts model status. - ASStatusToStatus(statusable Statusable) (*gtsmodel.Status, error) + ASStatusToStatus(statusable ap.Statusable) (*gtsmodel.Status, error) // ASFollowToFollowRequest converts a remote activitystreams `follow` representation into gts model follow request. - ASFollowToFollowRequest(followable Followable) (*gtsmodel.FollowRequest, error) + ASFollowToFollowRequest(followable ap.Followable) (*gtsmodel.FollowRequest, error) // ASFollowToFollowRequest converts a remote activitystreams `follow` representation into gts model follow. - ASFollowToFollow(followable Followable) (*gtsmodel.Follow, error) + ASFollowToFollow(followable ap.Followable) (*gtsmodel.Follow, error) // ASLikeToFave converts a remote activitystreams 'like' representation into a gts model status fave. - ASLikeToFave(likeable Likeable) (*gtsmodel.StatusFave, error) + ASLikeToFave(likeable ap.Likeable) (*gtsmodel.StatusFave, error) // ASBlockToBlock converts a remote activity streams 'block' representation into a gts model block. - ASBlockToBlock(blockable Blockable) (*gtsmodel.Block, error) + ASBlockToBlock(blockable ap.Blockable) (*gtsmodel.Block, error) // ASAnnounceToStatus converts an activitystreams 'announce' into a status. // // The returned bool indicates whether this status is new (true) or not new (false). @@ -122,7 +125,7 @@ type TypeConverter interface { // This is useful when multiple users on an instance might receive the same boost, and we only want to process the boost once. // // NOTE -- this is different from one status being boosted multiple times! In this case, new boosts should indeed be created. - ASAnnounceToStatus(announceable Announceable) (status *gtsmodel.Status, new bool, err error) + ASAnnounceToStatus(announceable ap.Announceable) (status *gtsmodel.Status, new bool, err error) /* INTERNAL (gts) MODEL TO ACTIVITYSTREAMS MODEL @@ -150,7 +153,10 @@ type TypeConverter interface { BoostToAS(boostWrapperStatus *gtsmodel.Status, boostingAccount *gtsmodel.Account, boostedAccount *gtsmodel.Account) (vocab.ActivityStreamsAnnounce, error) // BlockToAS converts a gts model block into an activityStreams BLOCK, suitable for federation. BlockToAS(block *gtsmodel.Block) (vocab.ActivityStreamsBlock, error) - + // StatusToASRepliesCollection converts a gts model status into an activityStreams REPLIES collection. + StatusToASRepliesCollection(status *gtsmodel.Status, onlyOtherAccounts bool) (vocab.ActivityStreamsCollection, error) + // StatusURIsToASRepliesPage returns a collection page with appropriate next/part of pagination. + StatusURIsToASRepliesPage(status *gtsmodel.Status, onlyOtherAccounts bool, minID string, replies map[string]*url.URL) (vocab.ActivityStreamsCollectionPage, error) /* INTERNAL (gts) MODEL TO INTERNAL MODEL */ diff --git a/internal/typeutils/converter_test.go b/internal/typeutils/converter_test.go index b2272f50c..c104ab06c 100644 --- a/internal/typeutils/converter_test.go +++ b/internal/typeutils/converter_test.go @@ -21,6 +21,7 @@ package typeutils_test import ( "github.com/sirupsen/logrus" "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/ap" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" @@ -34,7 +35,7 @@ type ConverterStandardTestSuite struct { db db.DB log *logrus.Logger accounts map[string]*gtsmodel.Account - people map[string]typeutils.Accountable + people map[string]ap.Accountable typeconverter typeutils.TypeConverter } diff --git a/internal/typeutils/internaltoas.go b/internal/typeutils/internaltoas.go index b24b07e13..333f131d4 100644 --- a/internal/typeutils/internaltoas.go +++ b/internal/typeutils/internaltoas.go @@ -27,6 +27,7 @@ import ( "github.com/go-fed/activity/streams" "github.com/go-fed/activity/streams/vocab" "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) @@ -505,7 +506,14 @@ func (c *converter) StatusToAS(s *gtsmodel.Status) (vocab.ActivityStreamsNote, e status.SetActivityStreamsAttachment(attachmentProp) // replies - // TODO + repliesCollection, err := c.StatusToASRepliesCollection(s, false) + if err != nil { + return nil, fmt.Errorf("error creating repliesCollection: %s", err) + } + + repliesProp := streams.NewActivityStreamsRepliesProperty() + repliesProp.SetActivityStreamsCollection(repliesCollection) + status.SetActivityStreamsReplies(repliesProp) return status, nil } @@ -850,3 +858,138 @@ func (c *converter) BlockToAS(b *gtsmodel.Block) (vocab.ActivityStreamsBlock, er return block, nil } + +/* + the goal is to end up with something like this: + + { + "@context": "https://www.w3.org/ns/activitystreams", + "id": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies", + "type": "Collection", + "first": { + "id": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies?page=true", + "type": "CollectionPage", + "next": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies?only_other_accounts=true&page=true", + "partOf": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies", + "items": [] + } + } +*/ +func (c *converter) StatusToASRepliesCollection(status *gtsmodel.Status, onlyOtherAccounts bool) (vocab.ActivityStreamsCollection, error) { + collectionID := fmt.Sprintf("%s/replies", status.URI) + collectionIDURI, err := url.Parse(collectionID) + if err != nil { + return nil, err + } + + collection := streams.NewActivityStreamsCollection() + + // collection.id + collectionIDProp := streams.NewJSONLDIdProperty() + collectionIDProp.SetIRI(collectionIDURI) + collection.SetJSONLDId(collectionIDProp) + + // first + first := streams.NewActivityStreamsFirstProperty() + firstPage := streams.NewActivityStreamsCollectionPage() + + // first.id + firstPageIDProp := streams.NewJSONLDIdProperty() + firstPageID, err := url.Parse(fmt.Sprintf("%s?page=true", collectionID)) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + firstPageIDProp.SetIRI(firstPageID) + firstPage.SetJSONLDId(firstPageIDProp) + + // first.next + nextProp := streams.NewActivityStreamsNextProperty() + nextPropID, err := url.Parse(fmt.Sprintf("%s?only_other_accounts=%t&page=true", collectionID, onlyOtherAccounts)) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + nextProp.SetIRI(nextPropID) + firstPage.SetActivityStreamsNext(nextProp) + + // first.partOf + partOfProp := streams.NewActivityStreamsPartOfProperty() + partOfProp.SetIRI(collectionIDURI) + firstPage.SetActivityStreamsPartOf(partOfProp) + + first.SetActivityStreamsCollectionPage(firstPage) + + // collection.first + collection.SetActivityStreamsFirst(first) + + return collection, nil +} + +/* + the goal is to end up with something like this: + { + "@context": "https://www.w3.org/ns/activitystreams", + "id": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies?only_other_accounts=true&page=true", + "type": "CollectionPage", + "next": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies?min_id=106720870266901180&only_other_accounts=true&page=true", + "partOf": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies", + "items": [ + "https://example.com/users/someone/statuses/106720752853216226", + "https://somewhere.online/users/eeeeeeeeeep/statuses/106720870163727231" + ] + } +*/ +func (c *converter) StatusURIsToASRepliesPage(status *gtsmodel.Status, onlyOtherAccounts bool, minID string, replies map[string]*url.URL) (vocab.ActivityStreamsCollectionPage, error) { + collectionID := fmt.Sprintf("%s/replies", status.URI) + + page := streams.NewActivityStreamsCollectionPage() + + // .id + pageIDProp := streams.NewJSONLDIdProperty() + pageIDString := fmt.Sprintf("%s?page=true&only_other_accounts=%t", collectionID, onlyOtherAccounts) + if minID != "" { + pageIDString = fmt.Sprintf("%s&min_id=%s", pageIDString, minID) + } + + pageID, err := url.Parse(pageIDString) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + pageIDProp.SetIRI(pageID) + page.SetJSONLDId(pageIDProp) + + // .partOf + collectionIDURI, err := url.Parse(collectionID) + if err != nil { + return nil, err + } + partOfProp := streams.NewActivityStreamsPartOfProperty() + partOfProp.SetIRI(collectionIDURI) + page.SetActivityStreamsPartOf(partOfProp) + + // .items + items := streams.NewActivityStreamsItemsProperty() + var highestID string + for k, v := range replies { + items.AppendIRI(v) + if k > highestID { + highestID = k + } + } + page.SetActivityStreamsItems(items) + + // .next + nextProp := streams.NewActivityStreamsNextProperty() + nextPropIDString := fmt.Sprintf("%s?only_other_accounts=%t&page=true", collectionID, onlyOtherAccounts) + if highestID != "" { + nextPropIDString = fmt.Sprintf("%s&min_id=%s", nextPropIDString, highestID) + } + + nextPropID, err := url.Parse(nextPropIDString) + if err != nil { + return nil, gtserror.NewErrorInternalError(err) + } + nextProp.SetIRI(nextPropID) + page.SetActivityStreamsNext(nextProp) + + return page, nil +} diff --git a/internal/typeutils/internaltoas_test.go b/internal/typeutils/internaltoas_test.go index 8eb827e35..caa56ce0d 100644 --- a/internal/typeutils/internaltoas_test.go +++ b/internal/typeutils/internaltoas_test.go @@ -47,7 +47,7 @@ func (suite *InternalToASTestSuite) SetupSuite() { } func (suite *InternalToASTestSuite) SetupTest() { - testrig.StandardDBSetup(suite.db) + testrig.StandardDBSetup(suite.db, nil) } // TearDownTest drops tables to make sure there's no data in the db |