[feature] Enable federation in/out of profile PropertyValue fields (#1722)

Co-authored-by: kim <grufwub@gmail.com>
Co-authored-by: kim <89579420+NyaaaWhatsUpDoc@users.noreply.github.com>
This commit is contained in:
tobi 2023-05-09 12:16:10 +02:00 committed by GitHub
commit 0e29f1f5bb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
180 changed files with 9278 additions and 1550 deletions

View file

@ -86,7 +86,8 @@ func (c *converter) ASRepresentationToAccount(ctx context.Context, accountable a
acct.Emojis = emojis
}
// TODO: fields aka attachment array
// fields aka attachment array
acct.Fields = ap.ExtractFields(accountable)
// note aka summary
acct.Note = ap.ExtractSummary(accountable)

View file

@ -240,7 +240,25 @@ func (c *converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
// attachment
// Used for profile fields.
// TODO: The PropertyValue type has to be added: https://schema.org/PropertyValue
if len(a.Fields) != 0 {
attachmentProp := streams.NewActivityStreamsAttachmentProperty()
for _, field := range a.Fields {
propertyValue := streams.NewSchemaPropertyValue()
nameProp := streams.NewActivityStreamsNameProperty()
nameProp.AppendXMLSchemaString(field.Name)
propertyValue.SetActivityStreamsName(nameProp)
valueProp := streams.NewSchemaValueProperty()
valueProp.Set(field.Value)
propertyValue.SetSchemaValue(valueProp)
attachmentProp.AppendSchemaPropertyValue(propertyValue)
}
person.SetActivityStreamsAttachment(attachmentProp)
}
// endpoints
// NOT IMPLEMENTED -- this is for shared inbox which we don't use

View file

@ -21,11 +21,11 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/activity/streams"
"github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
@ -43,7 +43,7 @@ func (suite *InternalToASTestSuite) TestAccountToAS() {
asPerson, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
suite.NoError(err)
ser, err := streams.Serialize(asPerson)
ser, err := ap.Serialize(asPerson)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@ -85,6 +85,107 @@ func (suite *InternalToASTestSuite) TestAccountToAS() {
}`, trimmed)
}
func (suite *InternalToASTestSuite) TestAccountToASWithFields() {
testAccount := &gtsmodel.Account{}
*testAccount = *suite.testAccounts["local_account_2"]
asPerson, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
suite.NoError(err)
ser, err := ap.Serialize(asPerson)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
suite.NoError(err)
// trim off everything up to 'attachment';
// this is necessary because the order of multiple 'context' entries is not determinate
trimmed := strings.Split(string(bytes), "\"attachment\"")[1]
fmt.Printf("\n\n\n%s\n\n\n", string(bytes))
suite.Equal(`: [
{
"name": "should you follow me?",
"type": "PropertyValue",
"value": "maybe!"
},
{
"name": "age",
"type": "PropertyValue",
"value": "120"
}
],
"discoverable": false,
"featured": "http://localhost:8080/users/1happyturtle/collections/featured",
"followers": "http://localhost:8080/users/1happyturtle/followers",
"following": "http://localhost:8080/users/1happyturtle/following",
"id": "http://localhost:8080/users/1happyturtle",
"inbox": "http://localhost:8080/users/1happyturtle/inbox",
"manuallyApprovesFollowers": true,
"name": "happy little turtle :3",
"outbox": "http://localhost:8080/users/1happyturtle/outbox",
"preferredUsername": "1happyturtle",
"publicKey": {
"id": "http://localhost:8080/users/1happyturtle#main-key",
"owner": "http://localhost:8080/users/1happyturtle",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtTc6Jpg6LrRPhVQG4KLz\n2+YqEUUtZPd4YR+TKXuCnwEG9ZNGhgP046xa9h3EWzrZXaOhXvkUQgJuRqPrAcfN\nvc8jBHV2xrUeD8pu/MWKEabAsA/tgCv3nUC47HQ3/c12aHfYoPz3ufWsGGnrkhci\nv8PaveJ3LohO5vjCn1yZ00v6osMJMViEZvZQaazyE9A8FwraIexXabDpoy7tkHRg\nA1fvSkg4FeSG1XMcIz2NN7xyUuFACD+XkuOk7UqzRd4cjPUPLxiDwIsTlcgGOd3E\nUFMWVlPxSGjY2hIKa3lEHytaYK9IMYdSuyCsJshd3/yYC9LqxZY2KdlKJ80VOVyh\nyQIDAQAB\n-----END PUBLIC KEY-----\n"
},
"summary": "\u003cp\u003ei post about things that concern me\u003c/p\u003e",
"tag": [],
"type": "Person",
"url": "http://localhost:8080/@1happyturtle"
}`, trimmed)
}
func (suite *InternalToASTestSuite) TestAccountToASWithOneField() {
testAccount := &gtsmodel.Account{}
*testAccount = *suite.testAccounts["local_account_2"]
testAccount.Fields = testAccount.Fields[0:1] // Take only one field.
asPerson, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
suite.NoError(err)
ser, err := ap.Serialize(asPerson)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
suite.NoError(err)
// trim off everything up to 'attachment';
// this is necessary because the order of multiple 'context' entries is not determinate
trimmed := strings.Split(string(bytes), "\"attachment\"")[1]
// Despite only one field being set, attachments should still be a slice/array.
suite.Equal(`: [
{
"name": "should you follow me?",
"type": "PropertyValue",
"value": "maybe!"
}
],
"discoverable": false,
"featured": "http://localhost:8080/users/1happyturtle/collections/featured",
"followers": "http://localhost:8080/users/1happyturtle/followers",
"following": "http://localhost:8080/users/1happyturtle/following",
"id": "http://localhost:8080/users/1happyturtle",
"inbox": "http://localhost:8080/users/1happyturtle/inbox",
"manuallyApprovesFollowers": true,
"name": "happy little turtle :3",
"outbox": "http://localhost:8080/users/1happyturtle/outbox",
"preferredUsername": "1happyturtle",
"publicKey": {
"id": "http://localhost:8080/users/1happyturtle#main-key",
"owner": "http://localhost:8080/users/1happyturtle",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtTc6Jpg6LrRPhVQG4KLz\n2+YqEUUtZPd4YR+TKXuCnwEG9ZNGhgP046xa9h3EWzrZXaOhXvkUQgJuRqPrAcfN\nvc8jBHV2xrUeD8pu/MWKEabAsA/tgCv3nUC47HQ3/c12aHfYoPz3ufWsGGnrkhci\nv8PaveJ3LohO5vjCn1yZ00v6osMJMViEZvZQaazyE9A8FwraIexXabDpoy7tkHRg\nA1fvSkg4FeSG1XMcIz2NN7xyUuFACD+XkuOk7UqzRd4cjPUPLxiDwIsTlcgGOd3E\nUFMWVlPxSGjY2hIKa3lEHytaYK9IMYdSuyCsJshd3/yYC9LqxZY2KdlKJ80VOVyh\nyQIDAQAB\n-----END PUBLIC KEY-----\n"
},
"summary": "\u003cp\u003ei post about things that concern me\u003c/p\u003e",
"tag": [],
"type": "Person",
"url": "http://localhost:8080/@1happyturtle"
}`, trimmed)
}
func (suite *InternalToASTestSuite) TestAccountToASWithEmoji() {
testAccount := &gtsmodel.Account{}
*testAccount = *suite.testAccounts["local_account_1"] // take zork for this test
@ -93,7 +194,7 @@ func (suite *InternalToASTestSuite) TestAccountToASWithEmoji() {
asPerson, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
suite.NoError(err)
ser, err := streams.Serialize(asPerson)
ser, err := ap.Serialize(asPerson)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@ -154,7 +255,7 @@ func (suite *InternalToASTestSuite) TestAccountToASWithSharedInbox() {
asPerson, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
suite.NoError(err)
ser, err := streams.Serialize(asPerson)
ser, err := ap.Serialize(asPerson)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@ -206,7 +307,7 @@ func (suite *InternalToASTestSuite) TestOutboxToASCollection() {
collection, err := suite.typeconverter.OutboxToASCollection(ctx, testAccount.OutboxURI)
suite.NoError(err)
ser, err := streams.Serialize(collection)
ser, err := ap.Serialize(collection)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@ -227,7 +328,7 @@ func (suite *InternalToASTestSuite) TestStatusToAS() {
asStatus, err := suite.typeconverter.StatusToAS(ctx, testStatus)
suite.NoError(err)
ser, err := streams.Serialize(asStatus)
ser, err := ap.Serialize(asStatus)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@ -268,7 +369,7 @@ func (suite *InternalToASTestSuite) TestStatusWithTagsToASWithIDs() {
asStatus, err := suite.typeconverter.StatusToAS(ctx, testStatus)
suite.NoError(err)
ser, err := streams.Serialize(asStatus)
ser, err := ap.Serialize(asStatus)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@ -328,7 +429,7 @@ func (suite *InternalToASTestSuite) TestStatusWithTagsToASFromDB() {
asStatus, err := suite.typeconverter.StatusToAS(ctx, testStatus)
suite.NoError(err)
ser, err := streams.Serialize(asStatus)
ser, err := ap.Serialize(asStatus)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@ -389,7 +490,7 @@ func (suite *InternalToASTestSuite) TestStatusToASWithMentions() {
asStatus, err := suite.typeconverter.StatusToAS(ctx, testStatus)
suite.NoError(err)
ser, err := streams.Serialize(asStatus)
ser, err := ap.Serialize(asStatus)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@ -437,7 +538,7 @@ func (suite *InternalToASTestSuite) TestStatusToASDeletePublicReply() {
asDelete, err := suite.typeconverter.StatusToASDelete(ctx, testStatus)
suite.NoError(err)
ser, err := streams.Serialize(asDelete)
ser, err := ap.Serialize(asDelete)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@ -475,7 +576,7 @@ func (suite *InternalToASTestSuite) TestStatusToASDeletePublicReplyOriginalDelet
asDelete, err := suite.typeconverter.StatusToASDelete(ctx, testStatus)
suite.NoError(err)
ser, err := streams.Serialize(asDelete)
ser, err := ap.Serialize(asDelete)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@ -501,7 +602,7 @@ func (suite *InternalToASTestSuite) TestStatusToASDeletePublic() {
asDelete, err := suite.typeconverter.StatusToASDelete(ctx, testStatus)
suite.NoError(err)
ser, err := streams.Serialize(asDelete)
ser, err := ap.Serialize(asDelete)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@ -524,7 +625,7 @@ func (suite *InternalToASTestSuite) TestStatusToASDeleteDirectMessage() {
asDelete, err := suite.typeconverter.StatusToASDelete(ctx, testStatus)
suite.NoError(err)
ser, err := streams.Serialize(asDelete)
ser, err := ap.Serialize(asDelete)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@ -551,7 +652,7 @@ func (suite *InternalToASTestSuite) TestStatusesToASOutboxPage() {
page, err := suite.typeconverter.StatusesToASOutboxPage(ctx, testAccount.OutboxURI, "", "", statuses)
suite.NoError(err)
ser, err := streams.Serialize(page)
ser, err := ap.Serialize(page)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@ -604,7 +705,7 @@ func (suite *InternalToASTestSuite) TestSelfBoostFollowersOnlyToAS() {
asBoost, err := suite.typeconverter.BoostToAS(ctx, boostWrapperStatus, testAccount, testAccount)
suite.NoError(err)
ser, err := streams.Serialize(asBoost)
ser, err := ap.Serialize(asBoost)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@ -637,7 +738,7 @@ func (suite *InternalToASTestSuite) TestReportToAS() {
flag, err := suite.typeconverter.ReportToASFlag(ctx, testReport)
suite.NoError(err)
ser, err := streams.Serialize(flag)
ser, err := ap.Serialize(flag)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@ -670,7 +771,7 @@ func (suite *InternalToASTestSuite) TestPinnedStatusesToASSomeItems() {
suite.FailNow(err.Error())
}
ser, err := ap.SerializeOrderedCollection(collection)
ser, err := ap.Serialize(collection)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@ -702,7 +803,7 @@ func (suite *InternalToASTestSuite) TestPinnedStatusesToASNoItems() {
suite.FailNow(err.Error())
}
ser, err := ap.SerializeOrderedCollection(collection)
ser, err := ap.Serialize(collection)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@ -731,7 +832,7 @@ func (suite *InternalToASTestSuite) TestPinnedStatusesToASOneItem() {
suite.FailNow(err.Error())
}
ser, err := ap.SerializeOrderedCollection(collection)
ser, err := ap.Serialize(collection)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")

View file

@ -77,7 +77,7 @@ func (c *converter) AccountToAPIAccountSensitive(ctx context.Context, a *gtsmode
Language: a.Language,
StatusContentType: statusContentType,
Note: a.NoteRaw,
Fields: apiAccount.Fields,
Fields: c.fieldsToAPIFields(a.FieldsRaw),
FollowRequestsCount: frc,
}
@ -131,7 +131,6 @@ func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
aviURLStatic string
headerURL string
headerURLStatic string
fields = make([]apimodel.Field, len(a.Fields))
)
if a.AvatarMediaAttachment != nil {
@ -144,19 +143,8 @@ func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
headerURLStatic = a.HeaderMediaAttachment.Thumbnail.URL
}
// GTS model fields -> frontend.
for i, field := range a.Fields {
mField := apimodel.Field{
Name: field.Name,
Value: field.Value,
}
if !field.VerifiedAt.IsZero() {
mField.VerifiedAt = util.FormatISO8601(field.VerifiedAt)
}
fields[i] = mField
}
// convert account gts model fields to front api model fields
fields := c.fieldsToAPIFields(a.Fields)
// GTS model emojis -> frontend.
apiEmojis, err := c.convertEmojisToAPIEmojis(ctx, a.Emojis, a.EmojiIDs)
@ -239,6 +227,25 @@ func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
return accountFrontend, nil
}
func (c *converter) fieldsToAPIFields(f []*gtsmodel.Field) []apimodel.Field {
fields := make([]apimodel.Field, len(f))
for i, field := range f {
mField := apimodel.Field{
Name: field.Name,
Value: field.Value,
}
if !field.VerifiedAt.IsZero() {
mField.VerifiedAt = func() *string { s := util.FormatISO8601(field.VerifiedAt); return &s }()
}
fields[i] = mField
}
return fields
}
func (c *converter) AccountToAPIAccountBlocked(ctx context.Context, a *gtsmodel.Account) (*apimodel.Account, error) {
var (
acct string

View file

@ -873,7 +873,18 @@ func (suite *InternalToFrontendTestSuite) TestReportToFrontend2() {
"statuses_count": 7,
"last_status_at": "2021-10-20T10:40:37.000Z",
"emojis": [],
"fields": [],
"fields": [
{
"name": "should you follow me?",
"value": "maybe!",
"verified_at": null
},
{
"name": "age",
"value": "120",
"verified_at": null
}
],
"role": {
"name": "user"
}
@ -977,7 +988,18 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend1() {
"statuses_count": 7,
"last_status_at": "2021-10-20T10:40:37.000Z",
"emojis": [],
"fields": [],
"fields": [
{
"name": "should you follow me?",
"value": "maybe!",
"verified_at": null
},
{
"name": "age",
"value": "120",
"verified_at": null
}
],
"role": {
"name": "user"
}
@ -1137,7 +1159,18 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend2() {
"statuses_count": 7,
"last_status_at": "2021-10-20T10:40:37.000Z",
"emojis": [],
"fields": [],
"fields": [
{
"name": "should you follow me?",
"value": "maybe!",
"verified_at": null
},
{
"name": "age",
"value": "120",
"verified_at": null
}
],
"role": {
"name": "user"
}

View file

@ -23,7 +23,7 @@ import (
"testing"
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/activity/streams"
"github.com/superseriousbusiness/gotosocial/internal/ap"
)
type WrapTestSuite struct {
@ -40,7 +40,7 @@ func (suite *WrapTestSuite) TestWrapNoteInCreateIRIOnly() {
suite.NoError(err)
suite.NotNil(create)
createI, err := streams.Serialize(create)
createI, err := ap.Serialize(create)
suite.NoError(err)
bytes, err := json.MarshalIndent(createI, "", " ")
@ -68,7 +68,7 @@ func (suite *WrapTestSuite) TestWrapNoteInCreate() {
suite.NoError(err)
suite.NotNil(create)
createI, err := streams.Serialize(create)
createI, err := ap.Serialize(create)
suite.NoError(err)
bytes, err := json.MarshalIndent(createI, "", " ")