diff --git a/docs/api/swagger.yaml b/docs/api/swagger.yaml
index 4ce234374..f7ce844af 100644
--- a/docs/api/swagger.yaml
+++ b/docs/api/swagger.yaml
@@ -8017,21 +8017,6 @@ paths:
name: federated
type: boolean
x-go-name: Federated
- - description: This status can be boosted/reblogged.
- in: formData
- name: boostable
- type: boolean
- x-go-name: Boostable
- - description: This status can be replied to.
- in: formData
- name: replyable
- type: boolean
- x-go-name: Replyable
- - description: This status can be liked/faved.
- in: formData
- name: likeable
- type: boolean
- x-go-name: Likeable
produces:
- application/json
responses:
diff --git a/internal/api/client/statuses/statusboost_test.go b/internal/api/client/statuses/statusboost_test.go
index ae7c364bf..3e6a5853d 100644
--- a/internal/api/client/statuses/statusboost_test.go
+++ b/internal/api/client/statuses/statusboost_test.go
@@ -173,42 +173,43 @@ func (suite *StatusBoostTestSuite) TestPostBoostOwnFollowersOnly() {
}
// try to boost a status that's not boostable / visible to us
-func (suite *StatusBoostTestSuite) TestPostUnboostable() {
- t := suite.testTokens["local_account_1"]
- oauthToken := oauth.DBTokenToToken(t)
+// TODO: sort this out with new interaction policies
+// func (suite *StatusBoostTestSuite) TestPostUnboostable() {
+// t := suite.testTokens["local_account_1"]
+// oauthToken := oauth.DBTokenToToken(t)
- targetStatus := suite.testStatuses["local_account_2_status_4"]
+// targetStatus := suite.testStatuses["local_account_2_status_4"]
- // setup
- recorder := httptest.NewRecorder()
- ctx, _ := testrig.CreateGinTestContext(recorder, nil)
- ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
- ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
- ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
- ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
- ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.ReblogPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting
- ctx.Request.Header.Set("accept", "application/json")
+// // setup
+// recorder := httptest.NewRecorder()
+// ctx, _ := testrig.CreateGinTestContext(recorder, nil)
+// ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
+// ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
+// ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
+// ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
+// ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.ReblogPath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting
+// ctx.Request.Header.Set("accept", "application/json")
- // normally the router would populate these params from the path values,
- // but because we're calling the function directly, we need to set them manually.
- ctx.Params = gin.Params{
- gin.Param{
- Key: statuses.IDKey,
- Value: targetStatus.ID,
- },
- }
+// // normally the router would populate these params from the path values,
+// // but because we're calling the function directly, we need to set them manually.
+// ctx.Params = gin.Params{
+// gin.Param{
+// Key: statuses.IDKey,
+// Value: targetStatus.ID,
+// },
+// }
- suite.statusModule.StatusBoostPOSTHandler(ctx)
+// suite.statusModule.StatusBoostPOSTHandler(ctx)
- // check response
- suite.Equal(http.StatusNotFound, recorder.Code) // we 404 unboostable statuses
+// // check response
+// suite.Equal(http.StatusNotFound, recorder.Code) // we 404 unboostable statuses
- result := recorder.Result()
- defer result.Body.Close()
- b, err := ioutil.ReadAll(result.Body)
- suite.NoError(err)
- suite.Equal(`{"error":"Not Found"}`, string(b))
-}
+// result := recorder.Result()
+// defer result.Body.Close()
+// b, err := ioutil.ReadAll(result.Body)
+// suite.NoError(err)
+// suite.Equal(`{"error":"Not Found"}`, string(b))
+// }
// try to boost a status that's not visible to the user
func (suite *StatusBoostTestSuite) TestPostNotVisible() {
diff --git a/internal/api/client/statuses/statuscreate.go b/internal/api/client/statuses/statuscreate.go
index 5a9654195..7b30e0ee6 100644
--- a/internal/api/client/statuses/statuscreate.go
+++ b/internal/api/client/statuses/statuscreate.go
@@ -168,24 +168,6 @@ import (
// description: This status will be federated beyond the local timeline(s).
// in: formData
// type: boolean
-// -
-// name: boostable
-// x-go-name: Boostable
-// description: This status can be boosted/reblogged.
-// in: formData
-// type: boolean
-// -
-// name: replyable
-// x-go-name: Replyable
-// description: This status can be replied to.
-// in: formData
-// type: boolean
-// -
-// name: likeable
-// x-go-name: Likeable
-// description: This status can be liked/faved.
-// in: formData
-// type: boolean
//
// produces:
// - application/json
diff --git a/internal/api/client/statuses/statuscreate_test.go b/internal/api/client/statuses/statuscreate_test.go
index b49e72ead..94d91c8b9 100644
--- a/internal/api/client/statuses/statuscreate_test.go
+++ b/internal/api/client/statuses/statuscreate_test.go
@@ -67,9 +67,6 @@ func (suite *StatusCreateTestSuite) TestPostNewStatus() {
"spoiler_text": {"hello hello"},
"sensitive": {"true"},
"visibility": {string(apimodel.VisibilityMutualsOnly)},
- "likeable": {"false"},
- "replyable": {"false"},
- "federated": {"false"},
}
suite.statusModule.StatusCreatePOSTHandler(ctx)
diff --git a/internal/api/client/statuses/statusfave_test.go b/internal/api/client/statuses/statusfave_test.go
index ebe4603a8..5a35351e4 100644
--- a/internal/api/client/statuses/statusfave_test.go
+++ b/internal/api/client/statuses/statusfave_test.go
@@ -89,42 +89,43 @@ func (suite *StatusFaveTestSuite) TestPostFave() {
}
// try to fave a status that's not faveable
-func (suite *StatusFaveTestSuite) TestPostUnfaveable() {
- t := suite.testTokens["local_account_1"]
- oauthToken := oauth.DBTokenToToken(t)
+// TODO: replace this when interaction policies enforced.
+// func (suite *StatusFaveTestSuite) TestPostUnfaveable() {
+// t := suite.testTokens["local_account_1"]
+// oauthToken := oauth.DBTokenToToken(t)
- targetStatus := suite.testStatuses["local_account_2_status_3"] // this one is unlikeable and unreplyable
+// targetStatus := suite.testStatuses["local_account_2_status_3"] // this one is unlikeable and unreplyable
- // setup
- recorder := httptest.NewRecorder()
- ctx, _ := testrig.CreateGinTestContext(recorder, nil)
- ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
- ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
- ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
- ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
- ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.FavouritePath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting
- ctx.Request.Header.Set("accept", "application/json")
+// // setup
+// recorder := httptest.NewRecorder()
+// ctx, _ := testrig.CreateGinTestContext(recorder, nil)
+// ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
+// ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
+// ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
+// ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
+// ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080%s", strings.Replace(statuses.FavouritePath, ":id", targetStatus.ID, 1)), nil) // the endpoint we're hitting
+// ctx.Request.Header.Set("accept", "application/json")
- // normally the router would populate these params from the path values,
- // but because we're calling the function directly, we need to set them manually.
- ctx.Params = gin.Params{
- gin.Param{
- Key: statuses.IDKey,
- Value: targetStatus.ID,
- },
- }
+// // normally the router would populate these params from the path values,
+// // but because we're calling the function directly, we need to set them manually.
+// ctx.Params = gin.Params{
+// gin.Param{
+// Key: statuses.IDKey,
+// Value: targetStatus.ID,
+// },
+// }
- suite.statusModule.StatusFavePOSTHandler(ctx)
+// suite.statusModule.StatusFavePOSTHandler(ctx)
- // check response
- suite.EqualValues(http.StatusForbidden, recorder.Code)
+// // check response
+// suite.EqualValues(http.StatusForbidden, recorder.Code)
- result := recorder.Result()
- defer result.Body.Close()
- b, err := ioutil.ReadAll(result.Body)
- assert.NoError(suite.T(), err)
- assert.Equal(suite.T(), `{"error":"Forbidden: status is not faveable"}`, string(b))
-}
+// result := recorder.Result()
+// defer result.Body.Close()
+// b, err := ioutil.ReadAll(result.Body)
+// assert.NoError(suite.T(), err)
+// assert.Equal(suite.T(), `{"error":"Forbidden: status is not faveable"}`, string(b))
+// }
func TestStatusFaveTestSuite(t *testing.T) {
suite.Run(t, new(StatusFaveTestSuite))
diff --git a/internal/api/client/statuses/statuspin_test.go b/internal/api/client/statuses/statuspin_test.go
index 39909e6c4..e9b65bd37 100644
--- a/internal/api/client/statuses/statuspin_test.go
+++ b/internal/api/client/statuses/statuspin_test.go
@@ -183,9 +183,6 @@ func (suite *StatusPinTestSuite) TestPinStatusTooManyPins() {
AccountURI: testAccount.URI,
Visibility: gtsmodel.VisibilityPublic,
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
ActivityStreamsType: ap.ObjectNote,
}
if err := suite.db.PutStatus(ctx, status); err != nil {
diff --git a/internal/api/model/status.go b/internal/api/model/status.go
index 9098cb59d..0d925d211 100644
--- a/internal/api/model/status.go
+++ b/internal/api/model/status.go
@@ -230,12 +230,6 @@ type AdvancedStatusCreateForm struct {
type AdvancedVisibilityFlagsForm struct {
// This status will be federated beyond the local timeline(s).
Federated *bool `form:"federated" json:"federated" xml:"federated"`
- // This status can be boosted/reblogged.
- Boostable *bool `form:"boostable" json:"boostable" xml:"boostable"`
- // This status can be replied to.
- Replyable *bool `form:"replyable" json:"replyable" xml:"replyable"`
- // This status can be liked/faved.
- Likeable *bool `form:"likeable" json:"likeable" xml:"likeable"`
}
// StatusContentType is the content type with which to parse the submitted status.
diff --git a/internal/cache/cache.go b/internal/cache/cache.go
index bb910f3e6..5a8a92ca3 100644
--- a/internal/cache/cache.go
+++ b/internal/cache/cache.go
@@ -73,6 +73,7 @@ func (c *Caches) Init() {
c.initFollowRequestIDs()
c.initInReplyToIDs()
c.initInstance()
+ c.initInteractionApproval()
c.initList()
c.initListEntry()
c.initMarker()
@@ -145,6 +146,7 @@ func (c *Caches) Sweep(threshold float64) {
c.GTS.FollowRequestIDs.Trim(threshold)
c.GTS.InReplyToIDs.Trim(threshold)
c.GTS.Instance.Trim(threshold)
+ c.GTS.InteractionApproval.Trim(threshold)
c.GTS.List.Trim(threshold)
c.GTS.ListEntry.Trim(threshold)
c.GTS.Marker.Trim(threshold)
diff --git a/internal/cache/db.go b/internal/cache/db.go
index d0fe77649..50acf00d1 100644
--- a/internal/cache/db.go
+++ b/internal/cache/db.go
@@ -100,6 +100,9 @@ type GTSCaches struct {
// Instance provides access to the gtsmodel Instance database cache.
Instance StructCache[*gtsmodel.Instance]
+ // InteractionApproval provides access to the gtsmodel InteractionApproval database cache.
+ InteractionApproval StructCache[*gtsmodel.InteractionApproval]
+
// InReplyToIDs provides access to the status in reply to IDs list database cache.
InReplyToIDs SliceCache[string]
@@ -737,6 +740,39 @@ func (c *Caches) initInstance() {
})
}
+func (c *Caches) initInteractionApproval() {
+ // Calculate maximum cache size.
+ cap := calculateResultCacheMax(
+ sizeofInteractionApproval(),
+ config.GetCacheInteractionApprovalMemRatio(),
+ )
+
+ log.Infof(nil, "cache size = %d", cap)
+
+ copyF := func(i1 *gtsmodel.InteractionApproval) *gtsmodel.InteractionApproval {
+ i2 := new(gtsmodel.InteractionApproval)
+ *i2 = *i1
+
+ // Don't include ptr fields that
+ // will be populated separately.
+ // See internal/db/bundb/interaction.go.
+ i2.Account = nil
+ i2.InteractingAccount = nil
+
+ return i2
+ }
+
+ c.GTS.InteractionApproval.Init(structr.CacheConfig[*gtsmodel.InteractionApproval]{
+ Indices: []structr.IndexConfig{
+ {Fields: "ID"},
+ {Fields: "URI"},
+ },
+ MaxSize: cap,
+ IgnoreErr: ignoreErrors,
+ Copy: copyF,
+ })
+}
+
func (c *Caches) initList() {
// Calculate maximum cache size.
cap := calculateResultCacheMax(
@@ -1188,6 +1224,7 @@ func (c *Caches) initStatusFave() {
c.GTS.StatusFave.Init(structr.CacheConfig[*gtsmodel.StatusFave]{
Indices: []structr.IndexConfig{
{Fields: "ID"},
+ {Fields: "URI"},
{Fields: "AccountID,StatusID"},
{Fields: "StatusID", Multiple: true},
},
diff --git a/internal/cache/size.go b/internal/cache/size.go
index fb1f165c2..4ec30fbb7 100644
--- a/internal/cache/size.go
+++ b/internal/cache/size.go
@@ -189,6 +189,7 @@ func totalOfRatios() float64 {
config.GetCacheFollowRequestMemRatio() +
config.GetCacheFollowRequestIDsMemRatio() +
config.GetCacheInstanceMemRatio() +
+ config.GetCacheInteractionApprovalMemRatio() +
config.GetCacheInReplyToIDsMemRatio() +
config.GetCacheListMemRatio() +
config.GetCacheListEntryMemRatio() +
@@ -425,6 +426,19 @@ func sizeofInstance() uintptr {
}))
}
+func sizeofInteractionApproval() uintptr {
+ return uintptr(size.Of(>smodel.InteractionApproval{
+ ID: exampleID,
+ CreatedAt: exampleTime,
+ UpdatedAt: exampleTime,
+ AccountID: exampleID,
+ InteractingAccountID: exampleID,
+ InteractionURI: exampleURI,
+ InteractionType: gtsmodel.InteractionAnnounce,
+ URI: exampleURI,
+ }))
+}
+
func sizeofList() uintptr {
return uintptr(size.Of(>smodel.List{
ID: exampleID,
@@ -591,9 +605,6 @@ func sizeofStatus() uintptr {
Language: "en",
CreatedWithApplicationID: exampleID,
Federated: func() *bool { ok := true; return &ok }(),
- Boostable: func() *bool { ok := true; return &ok }(),
- Replyable: func() *bool { ok := true; return &ok }(),
- Likeable: func() *bool { ok := true; return &ok }(),
ActivityStreamsType: ap.ObjectNote,
}))
}
diff --git a/internal/config/config.go b/internal/config/config.go
index 8d410f6ac..015213184 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -191,52 +191,53 @@ type HTTPClientConfiguration struct {
}
type CacheConfiguration struct {
- MemoryTarget bytesize.Size `name:"memory-target"`
- AccountMemRatio float64 `name:"account-mem-ratio"`
- AccountNoteMemRatio float64 `name:"account-note-mem-ratio"`
- AccountSettingsMemRatio float64 `name:"account-settings-mem-ratio"`
- AccountStatsMemRatio float64 `name:"account-stats-mem-ratio"`
- ApplicationMemRatio float64 `name:"application-mem-ratio"`
- BlockMemRatio float64 `name:"block-mem-ratio"`
- BlockIDsMemRatio float64 `name:"block-ids-mem-ratio"`
- BoostOfIDsMemRatio float64 `name:"boost-of-ids-mem-ratio"`
- ClientMemRatio float64 `name:"client-mem-ratio"`
- EmojiMemRatio float64 `name:"emoji-mem-ratio"`
- EmojiCategoryMemRatio float64 `name:"emoji-category-mem-ratio"`
- FilterMemRatio float64 `name:"filter-mem-ratio"`
- FilterKeywordMemRatio float64 `name:"filter-keyword-mem-ratio"`
- FilterStatusMemRatio float64 `name:"filter-status-mem-ratio"`
- FollowMemRatio float64 `name:"follow-mem-ratio"`
- FollowIDsMemRatio float64 `name:"follow-ids-mem-ratio"`
- FollowRequestMemRatio float64 `name:"follow-request-mem-ratio"`
- FollowRequestIDsMemRatio float64 `name:"follow-request-ids-mem-ratio"`
- InReplyToIDsMemRatio float64 `name:"in-reply-to-ids-mem-ratio"`
- InstanceMemRatio float64 `name:"instance-mem-ratio"`
- ListMemRatio float64 `name:"list-mem-ratio"`
- ListEntryMemRatio float64 `name:"list-entry-mem-ratio"`
- MarkerMemRatio float64 `name:"marker-mem-ratio"`
- MediaMemRatio float64 `name:"media-mem-ratio"`
- MentionMemRatio float64 `name:"mention-mem-ratio"`
- MoveMemRatio float64 `name:"move-mem-ratio"`
- NotificationMemRatio float64 `name:"notification-mem-ratio"`
- PollMemRatio float64 `name:"poll-mem-ratio"`
- PollVoteMemRatio float64 `name:"poll-vote-mem-ratio"`
- PollVoteIDsMemRatio float64 `name:"poll-vote-ids-mem-ratio"`
- ReportMemRatio float64 `name:"report-mem-ratio"`
- StatusMemRatio float64 `name:"status-mem-ratio"`
- StatusBookmarkMemRatio float64 `name:"status-bookmark-mem-ratio"`
- StatusBookmarkIDsMemRatio float64 `name:"status-bookmark-ids-mem-ratio"`
- StatusFaveMemRatio float64 `name:"status-fave-mem-ratio"`
- StatusFaveIDsMemRatio float64 `name:"status-fave-ids-mem-ratio"`
- TagMemRatio float64 `name:"tag-mem-ratio"`
- ThreadMuteMemRatio float64 `name:"thread-mute-mem-ratio"`
- TokenMemRatio float64 `name:"token-mem-ratio"`
- TombstoneMemRatio float64 `name:"tombstone-mem-ratio"`
- UserMemRatio float64 `name:"user-mem-ratio"`
- UserMuteMemRatio float64 `name:"user-mute-mem-ratio"`
- UserMuteIDsMemRatio float64 `name:"user-mute-ids-mem-ratio"`
- WebfingerMemRatio float64 `name:"webfinger-mem-ratio"`
- VisibilityMemRatio float64 `name:"visibility-mem-ratio"`
+ MemoryTarget bytesize.Size `name:"memory-target"`
+ AccountMemRatio float64 `name:"account-mem-ratio"`
+ AccountNoteMemRatio float64 `name:"account-note-mem-ratio"`
+ AccountSettingsMemRatio float64 `name:"account-settings-mem-ratio"`
+ AccountStatsMemRatio float64 `name:"account-stats-mem-ratio"`
+ ApplicationMemRatio float64 `name:"application-mem-ratio"`
+ BlockMemRatio float64 `name:"block-mem-ratio"`
+ BlockIDsMemRatio float64 `name:"block-ids-mem-ratio"`
+ BoostOfIDsMemRatio float64 `name:"boost-of-ids-mem-ratio"`
+ ClientMemRatio float64 `name:"client-mem-ratio"`
+ EmojiMemRatio float64 `name:"emoji-mem-ratio"`
+ EmojiCategoryMemRatio float64 `name:"emoji-category-mem-ratio"`
+ FilterMemRatio float64 `name:"filter-mem-ratio"`
+ FilterKeywordMemRatio float64 `name:"filter-keyword-mem-ratio"`
+ FilterStatusMemRatio float64 `name:"filter-status-mem-ratio"`
+ FollowMemRatio float64 `name:"follow-mem-ratio"`
+ FollowIDsMemRatio float64 `name:"follow-ids-mem-ratio"`
+ FollowRequestMemRatio float64 `name:"follow-request-mem-ratio"`
+ FollowRequestIDsMemRatio float64 `name:"follow-request-ids-mem-ratio"`
+ InReplyToIDsMemRatio float64 `name:"in-reply-to-ids-mem-ratio"`
+ InstanceMemRatio float64 `name:"instance-mem-ratio"`
+ InteractionApprovalMemRatio float64 `name:"interaction-approval-mem-ratio"`
+ ListMemRatio float64 `name:"list-mem-ratio"`
+ ListEntryMemRatio float64 `name:"list-entry-mem-ratio"`
+ MarkerMemRatio float64 `name:"marker-mem-ratio"`
+ MediaMemRatio float64 `name:"media-mem-ratio"`
+ MentionMemRatio float64 `name:"mention-mem-ratio"`
+ MoveMemRatio float64 `name:"move-mem-ratio"`
+ NotificationMemRatio float64 `name:"notification-mem-ratio"`
+ PollMemRatio float64 `name:"poll-mem-ratio"`
+ PollVoteMemRatio float64 `name:"poll-vote-mem-ratio"`
+ PollVoteIDsMemRatio float64 `name:"poll-vote-ids-mem-ratio"`
+ ReportMemRatio float64 `name:"report-mem-ratio"`
+ StatusMemRatio float64 `name:"status-mem-ratio"`
+ StatusBookmarkMemRatio float64 `name:"status-bookmark-mem-ratio"`
+ StatusBookmarkIDsMemRatio float64 `name:"status-bookmark-ids-mem-ratio"`
+ StatusFaveMemRatio float64 `name:"status-fave-mem-ratio"`
+ StatusFaveIDsMemRatio float64 `name:"status-fave-ids-mem-ratio"`
+ TagMemRatio float64 `name:"tag-mem-ratio"`
+ ThreadMuteMemRatio float64 `name:"thread-mute-mem-ratio"`
+ TokenMemRatio float64 `name:"token-mem-ratio"`
+ TombstoneMemRatio float64 `name:"tombstone-mem-ratio"`
+ UserMemRatio float64 `name:"user-mem-ratio"`
+ UserMuteMemRatio float64 `name:"user-mute-mem-ratio"`
+ UserMuteIDsMemRatio float64 `name:"user-mute-ids-mem-ratio"`
+ WebfingerMemRatio float64 `name:"webfinger-mem-ratio"`
+ VisibilityMemRatio float64 `name:"visibility-mem-ratio"`
}
// MarshalMap will marshal current Configuration into a map structure (useful for JSON/TOML/YAML).
diff --git a/internal/config/defaults.go b/internal/config/defaults.go
index 8a76cc21a..ba068761e 100644
--- a/internal/config/defaults.go
+++ b/internal/config/defaults.go
@@ -156,51 +156,52 @@ var Defaults = Configuration{
// when TODO items in the size.go source
// file have been addressed, these should
// be able to make some more sense :D
- AccountMemRatio: 5,
- AccountNoteMemRatio: 1,
- AccountSettingsMemRatio: 0.1,
- AccountStatsMemRatio: 2,
- ApplicationMemRatio: 0.1,
- BlockMemRatio: 2,
- BlockIDsMemRatio: 3,
- BoostOfIDsMemRatio: 3,
- ClientMemRatio: 0.1,
- EmojiMemRatio: 3,
- EmojiCategoryMemRatio: 0.1,
- FilterMemRatio: 0.5,
- FilterKeywordMemRatio: 0.5,
- FilterStatusMemRatio: 0.5,
- FollowMemRatio: 2,
- FollowIDsMemRatio: 4,
- FollowRequestMemRatio: 2,
- FollowRequestIDsMemRatio: 2,
- InReplyToIDsMemRatio: 3,
- InstanceMemRatio: 1,
- ListMemRatio: 1,
- ListEntryMemRatio: 2,
- MarkerMemRatio: 0.5,
- MediaMemRatio: 4,
- MentionMemRatio: 2,
- MoveMemRatio: 0.1,
- NotificationMemRatio: 2,
- PollMemRatio: 1,
- PollVoteMemRatio: 2,
- PollVoteIDsMemRatio: 2,
- ReportMemRatio: 1,
- StatusMemRatio: 5,
- StatusBookmarkMemRatio: 0.5,
- StatusBookmarkIDsMemRatio: 2,
- StatusFaveMemRatio: 2,
- StatusFaveIDsMemRatio: 3,
- TagMemRatio: 2,
- ThreadMuteMemRatio: 0.2,
- TokenMemRatio: 0.75,
- TombstoneMemRatio: 0.5,
- UserMemRatio: 0.25,
- UserMuteMemRatio: 2,
- UserMuteIDsMemRatio: 3,
- WebfingerMemRatio: 0.1,
- VisibilityMemRatio: 2,
+ AccountMemRatio: 5,
+ AccountNoteMemRatio: 1,
+ AccountSettingsMemRatio: 0.1,
+ AccountStatsMemRatio: 2,
+ ApplicationMemRatio: 0.1,
+ BlockMemRatio: 2,
+ BlockIDsMemRatio: 3,
+ BoostOfIDsMemRatio: 3,
+ ClientMemRatio: 0.1,
+ EmojiMemRatio: 3,
+ EmojiCategoryMemRatio: 0.1,
+ FilterMemRatio: 0.5,
+ FilterKeywordMemRatio: 0.5,
+ FilterStatusMemRatio: 0.5,
+ FollowMemRatio: 2,
+ FollowIDsMemRatio: 4,
+ FollowRequestMemRatio: 2,
+ FollowRequestIDsMemRatio: 2,
+ InReplyToIDsMemRatio: 3,
+ InstanceMemRatio: 1,
+ InteractionApprovalMemRatio: 1,
+ ListMemRatio: 1,
+ ListEntryMemRatio: 2,
+ MarkerMemRatio: 0.5,
+ MediaMemRatio: 4,
+ MentionMemRatio: 2,
+ MoveMemRatio: 0.1,
+ NotificationMemRatio: 2,
+ PollMemRatio: 1,
+ PollVoteMemRatio: 2,
+ PollVoteIDsMemRatio: 2,
+ ReportMemRatio: 1,
+ StatusMemRatio: 5,
+ StatusBookmarkMemRatio: 0.5,
+ StatusBookmarkIDsMemRatio: 2,
+ StatusFaveMemRatio: 2,
+ StatusFaveIDsMemRatio: 3,
+ TagMemRatio: 2,
+ ThreadMuteMemRatio: 0.2,
+ TokenMemRatio: 0.75,
+ TombstoneMemRatio: 0.5,
+ UserMemRatio: 0.25,
+ UserMuteMemRatio: 2,
+ UserMuteIDsMemRatio: 3,
+ WebfingerMemRatio: 0.1,
+ VisibilityMemRatio: 2,
},
HTTPClient: HTTPClientConfiguration{
diff --git a/internal/config/helpers.gen.go b/internal/config/helpers.gen.go
index 71a77e753..8dab7ac6a 100644
--- a/internal/config/helpers.gen.go
+++ b/internal/config/helpers.gen.go
@@ -3250,6 +3250,33 @@ func GetCacheInstanceMemRatio() float64 { return global.GetCacheInstanceMemRatio
// SetCacheInstanceMemRatio safely sets the value for global configuration 'Cache.InstanceMemRatio' field
func SetCacheInstanceMemRatio(v float64) { global.SetCacheInstanceMemRatio(v) }
+// GetCacheInteractionApprovalMemRatio safely fetches the Configuration value for state's 'Cache.InteractionApprovalMemRatio' field
+func (st *ConfigState) GetCacheInteractionApprovalMemRatio() (v float64) {
+ st.mutex.RLock()
+ v = st.config.Cache.InteractionApprovalMemRatio
+ st.mutex.RUnlock()
+ return
+}
+
+// SetCacheInteractionApprovalMemRatio safely sets the Configuration value for state's 'Cache.InteractionApprovalMemRatio' field
+func (st *ConfigState) SetCacheInteractionApprovalMemRatio(v float64) {
+ st.mutex.Lock()
+ defer st.mutex.Unlock()
+ st.config.Cache.InteractionApprovalMemRatio = v
+ st.reloadToViper()
+}
+
+// CacheInteractionApprovalMemRatioFlag returns the flag name for the 'Cache.InteractionApprovalMemRatio' field
+func CacheInteractionApprovalMemRatioFlag() string { return "cache-interaction-approval-mem-ratio" }
+
+// GetCacheInteractionApprovalMemRatio safely fetches the value for global configuration 'Cache.InteractionApprovalMemRatio' field
+func GetCacheInteractionApprovalMemRatio() float64 {
+ return global.GetCacheInteractionApprovalMemRatio()
+}
+
+// SetCacheInteractionApprovalMemRatio safely sets the value for global configuration 'Cache.InteractionApprovalMemRatio' field
+func SetCacheInteractionApprovalMemRatio(v float64) { global.SetCacheInteractionApprovalMemRatio(v) }
+
// GetCacheListMemRatio safely fetches the Configuration value for state's 'Cache.ListMemRatio' field
func (st *ConfigState) GetCacheListMemRatio() (v float64) {
st.mutex.RLock()
diff --git a/internal/db/bundb/account_test.go b/internal/db/bundb/account_test.go
index 5ed5d91a1..a9554e0d7 100644
--- a/internal/db/bundb/account_test.go
+++ b/internal/db/bundb/account_test.go
@@ -115,15 +115,6 @@ func (suite *AccountTestSuite) populateTestStatus(testAccountKey string, status
if status.Federated == nil {
status.Federated = util.Ptr(true)
}
- if status.Boostable == nil {
- status.Boostable = util.Ptr(true)
- }
- if status.Likeable == nil {
- status.Likeable = util.Ptr(true)
- }
- if status.Replyable == nil {
- status.Replyable = util.Ptr(true)
- }
if inReplyTo != nil {
status.InReplyToAccountID = inReplyTo.AccountID
diff --git a/internal/db/bundb/bundb.go b/internal/db/bundb/bundb.go
index e7256c276..57fb661df 100644
--- a/internal/db/bundb/bundb.go
+++ b/internal/db/bundb/bundb.go
@@ -60,6 +60,7 @@ type DBService struct {
db.Emoji
db.HeaderFilter
db.Instance
+ db.Interaction
db.Filter
db.List
db.Marker
@@ -203,6 +204,10 @@ func NewBunDBService(ctx context.Context, state *state.State) (db.DB, error) {
db: db,
state: state,
},
+ Interaction: &interactionDB{
+ db: db,
+ state: state,
+ },
Filter: &filterDB{
db: db,
state: state,
diff --git a/internal/db/bundb/interaction.go b/internal/db/bundb/interaction.go
new file mode 100644
index 000000000..b818f7a60
--- /dev/null
+++ b/internal/db/bundb/interaction.go
@@ -0,0 +1,149 @@
+// 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 .
+
+package bundb
+
+import (
+ "context"
+
+ "github.com/superseriousbusiness/gotosocial/internal/gtscontext"
+ "github.com/superseriousbusiness/gotosocial/internal/gtserror"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/state"
+ "github.com/uptrace/bun"
+)
+
+type interactionDB struct {
+ db *bun.DB
+ state *state.State
+}
+
+func (r *interactionDB) newInteractionApprovalQ(approval interface{}) *bun.SelectQuery {
+ return r.db.
+ NewSelect().
+ Model(approval)
+}
+
+func (r *interactionDB) GetInteractionApprovalByID(ctx context.Context, id string) (*gtsmodel.InteractionApproval, error) {
+ return r.getInteractionApproval(
+ ctx,
+ "ID",
+ func(approval *gtsmodel.InteractionApproval) error {
+ return r.
+ newInteractionApprovalQ(approval).
+ Where("? = ?", bun.Ident("interaction_approval.id"), id).
+ Scan(ctx)
+ },
+ id,
+ )
+}
+
+func (r *interactionDB) GetInteractionApprovalByURI(ctx context.Context, uri string) (*gtsmodel.InteractionApproval, error) {
+ return r.getInteractionApproval(
+ ctx,
+ "URI",
+ func(approval *gtsmodel.InteractionApproval) error {
+ return r.
+ newInteractionApprovalQ(approval).
+ Where("? = ?", bun.Ident("interaction_approval.uri"), uri).
+ Scan(ctx)
+ },
+ uri,
+ )
+}
+
+func (r *interactionDB) getInteractionApproval(
+ ctx context.Context,
+ lookup string,
+ dbQuery func(*gtsmodel.InteractionApproval) error,
+ keyParts ...any,
+) (*gtsmodel.InteractionApproval, error) {
+ // Fetch approval from database cache with loader callback
+ approval, err := r.state.Caches.GTS.InteractionApproval.LoadOne(lookup, func() (*gtsmodel.InteractionApproval, error) {
+ var approval gtsmodel.InteractionApproval
+
+ // Not cached! Perform database query
+ if err := dbQuery(&approval); err != nil {
+ return nil, err
+ }
+
+ return &approval, nil
+ }, keyParts...)
+ if err != nil {
+ // Error already processed.
+ return nil, err
+ }
+
+ if gtscontext.Barebones(ctx) {
+ // Only a barebones model was requested.
+ return approval, nil
+ }
+
+ if err := r.PopulateInteractionApproval(ctx, approval); err != nil {
+ return nil, err
+ }
+
+ return approval, nil
+}
+
+func (r *interactionDB) PopulateInteractionApproval(ctx context.Context, approval *gtsmodel.InteractionApproval) error {
+ var (
+ err error
+ errs = gtserror.NewMultiError(2)
+ )
+
+ if approval.Account == nil {
+ // Account is not set, fetch from the database.
+ approval.Account, err = r.state.DB.GetAccountByID(
+ gtscontext.SetBarebones(ctx),
+ approval.AccountID,
+ )
+ if err != nil {
+ errs.Appendf("error populating interactionApproval account: %w", err)
+ }
+ }
+
+ if approval.InteractingAccount == nil {
+ // InteractingAccount is not set, fetch from the database.
+ approval.InteractingAccount, err = r.state.DB.GetAccountByID(
+ gtscontext.SetBarebones(ctx),
+ approval.InteractingAccountID,
+ )
+ if err != nil {
+ errs.Appendf("error populating interactionApproval interacting account: %w", err)
+ }
+ }
+
+ return errs.Combine()
+}
+
+func (r *interactionDB) PutInteractionApproval(ctx context.Context, approval *gtsmodel.InteractionApproval) error {
+ return r.state.Caches.GTS.InteractionApproval.Store(approval, func() error {
+ _, err := r.db.NewInsert().Model(approval).Exec(ctx)
+ return err
+ })
+}
+
+func (r *interactionDB) DeleteInteractionApprovalByID(ctx context.Context, id string) error {
+ defer r.state.Caches.GTS.InteractionApproval.Invalidate("ID", id)
+
+ _, err := r.db.NewDelete().
+ TableExpr("? AS ?", bun.Ident("interaction_approvals"), bun.Ident("interaction_approval")).
+ Where("? = ?", bun.Ident("interaction_approval.id"), id).
+ Exec(ctx)
+ return err
+}
diff --git a/internal/db/bundb/migrations/20240620074530_interaction_policy.go b/internal/db/bundb/migrations/20240620074530_interaction_policy.go
new file mode 100644
index 000000000..424039a52
--- /dev/null
+++ b/internal/db/bundb/migrations/20240620074530_interaction_policy.go
@@ -0,0 +1,264 @@
+// 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 .
+
+package migrations
+
+import (
+ "context"
+
+ "github.com/superseriousbusiness/gotosocial/internal/log"
+
+ oldmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20240620074530_interaction_policy"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+
+ "github.com/uptrace/bun"
+)
+
+func init() {
+ up := func(ctx context.Context, db *bun.DB) error {
+ log.Info(ctx, "migrating statuses and account settings to interaction policy model, please wait...")
+ return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
+
+ // Add new columns for interaction
+ // policies + related fields.
+ type spec struct {
+ table string
+ column string
+ columnType string
+ defaultVal string
+ }
+ for _, spec := range []spec{
+ // Statuses.
+ {
+ table: "statuses",
+ column: "interaction_policy",
+ columnType: "JSONB",
+ defaultVal: "",
+ },
+ {
+ table: "statuses",
+ column: "pending_approval",
+ columnType: "BOOLEAN",
+ defaultVal: "DEFAULT false",
+ },
+ {
+ table: "statuses",
+ column: "approved_by_uri",
+ columnType: "varchar",
+ defaultVal: "",
+ },
+
+ // Status faves.
+ {
+ table: "status_faves",
+ column: "pending_approval",
+ columnType: "BOOLEAN",
+ defaultVal: "DEFAULT false",
+ },
+ {
+ table: "status_faves",
+ column: "approved_by_uri",
+ columnType: "varchar",
+ defaultVal: "",
+ },
+
+ // Columns that must be added to the
+ // `account_settings` table to populate
+ // default interaction policies for
+ // different status visibilities.
+ {
+ table: "account_settings",
+ column: "interaction_policy_direct",
+ columnType: "JSONB",
+ defaultVal: "",
+ },
+ {
+ table: "account_settings",
+ column: "interaction_policy_mutuals_only",
+ columnType: "JSONB",
+ defaultVal: "",
+ },
+ {
+ table: "account_settings",
+ column: "interaction_policy_followers_only",
+ columnType: "JSONB",
+ defaultVal: "",
+ },
+ {
+ table: "account_settings",
+ column: "interaction_policy_unlocked",
+ columnType: "JSONB",
+ defaultVal: "",
+ },
+ {
+ table: "account_settings",
+ column: "interaction_policy_public",
+ columnType: "JSONB",
+ defaultVal: "",
+ },
+ } {
+ exists, err := doesColumnExist(ctx, tx,
+ spec.table, spec.column,
+ )
+ if err != nil {
+ // Real error.
+ return err
+ } else if exists {
+ // Already created.
+ continue
+ }
+
+ args := []any{
+ bun.Ident(spec.table),
+ bun.Ident(spec.column),
+ bun.Safe(spec.columnType),
+ }
+
+ qStr := "ALTER TABLE ? ADD COLUMN ? ?"
+ if spec.defaultVal != "" {
+ qStr += " ?"
+ args = append(args, bun.Safe(spec.defaultVal))
+ }
+
+ if _, err := tx.ExecContext(ctx, qStr, args...); err != nil {
+ return err
+ }
+ }
+
+ // Select each locally-created status
+ // with non-default old flags set.
+ oldStatuses := []oldmodel.Status{}
+
+ if err := tx.
+ NewSelect().
+ Model(&oldStatuses).
+ Column("id", "likeable", "replyable", "boostable", "visibility").
+ Where("? = ?", bun.Ident("local"), true).
+ WhereGroup(" AND ", func(sq *bun.SelectQuery) *bun.SelectQuery {
+ return sq.
+ Where("? = ?", bun.Ident("likeable"), false).
+ WhereOr("? = ?", bun.Ident("replyable"), false).
+ WhereOr("? = ?", bun.Ident("boostable"), false)
+ }).
+ Scan(ctx); err != nil {
+ return err
+ }
+
+ // For each status found in this way, update
+ // to new version of interaction policy.
+ for _, oldStatus := range oldStatuses {
+ // Start with default policy for this visibility.
+ v := gtsmodel.Visibility(oldStatus.Visibility)
+ policy := gtsmodel.DefaultInteractionPolicyFor(v)
+
+ if !*oldStatus.Likeable {
+ // Only author can like.
+ policy.CanLike = gtsmodel.PolicyRules{
+ Always: gtsmodel.PolicyValues{
+ gtsmodel.PolicyValueAuthor,
+ },
+ WithApproval: make(gtsmodel.PolicyValues, 0),
+ }
+ }
+
+ if !*oldStatus.Replyable {
+ // Only author + mentioned can Reply.
+ policy.CanReply = gtsmodel.PolicyRules{
+ Always: gtsmodel.PolicyValues{
+ gtsmodel.PolicyValueAuthor,
+ gtsmodel.PolicyValueMentioned,
+ },
+ WithApproval: make(gtsmodel.PolicyValues, 0),
+ }
+ }
+
+ if !*oldStatus.Boostable {
+ // Only author can Announce.
+ policy.CanAnnounce = gtsmodel.PolicyRules{
+ Always: gtsmodel.PolicyValues{
+ gtsmodel.PolicyValueAuthor,
+ },
+ WithApproval: make(gtsmodel.PolicyValues, 0),
+ }
+ }
+
+ // Update status with the new interaction policy.
+ newStatus := >smodel.Status{
+ ID: oldStatus.ID,
+ InteractionPolicy: policy,
+ }
+ if _, err := tx.
+ NewUpdate().
+ Model(newStatus).
+ Column("interaction_policy").
+ Where("? = ?", bun.Ident("id"), newStatus.ID).
+ Exec(ctx); err != nil {
+ return err
+ }
+ }
+
+ // Drop now unused columns from statuses table.
+ oldColumns := []string{
+ "likeable",
+ "replyable",
+ "boostable",
+ }
+ for _, column := range oldColumns {
+ if _, err := tx.
+ NewDropColumn().
+ Table("statuses").
+ Column(column).
+ Exec(ctx); err != nil {
+ return err
+ }
+ }
+
+ // Add new indexes.
+ if _, err := tx.
+ NewCreateIndex().
+ Table("statuses").
+ Index("statuses_pending_approval_idx").
+ Column("pending_approval").
+ IfNotExists().
+ Exec(ctx); err != nil {
+ return err
+ }
+
+ if _, err := tx.
+ NewCreateIndex().
+ Table("status_faves").
+ Index("status_faves_pending_approval_idx").
+ Column("pending_approval").
+ IfNotExists().
+ Exec(ctx); err != nil {
+ return err
+ }
+
+ return nil
+ })
+ }
+
+ down := func(ctx context.Context, db *bun.DB) error {
+ return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
+ return nil
+ })
+ }
+
+ if err := Migrations.Register(up, down); err != nil {
+ panic(err)
+ }
+}
diff --git a/internal/db/bundb/migrations/20240620074530_interaction_policy/status.go b/internal/db/bundb/migrations/20240620074530_interaction_policy/status.go
new file mode 100644
index 000000000..ae96d047d
--- /dev/null
+++ b/internal/db/bundb/migrations/20240620074530_interaction_policy/status.go
@@ -0,0 +1,61 @@
+// 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 .
+
+package gtsmodel
+
+import (
+ "time"
+)
+
+type Status struct {
+ ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"`
+ CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`
+ UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`
+ FetchedAt time.Time `bun:"type:timestamptz,nullzero"`
+ PinnedAt time.Time `bun:"type:timestamptz,nullzero"`
+ URI string `bun:",unique,nullzero,notnull"`
+ URL string `bun:",nullzero"`
+ Content string `bun:""`
+ AttachmentIDs []string `bun:"attachments,array"`
+ TagIDs []string `bun:"tags,array"`
+ MentionIDs []string `bun:"mentions,array"`
+ EmojiIDs []string `bun:"emojis,array"`
+ Local *bool `bun:",nullzero,notnull,default:false"`
+ AccountID string `bun:"type:CHAR(26),nullzero,notnull"`
+ AccountURI string `bun:",nullzero,notnull"`
+ InReplyToID string `bun:"type:CHAR(26),nullzero"`
+ InReplyToURI string `bun:",nullzero"`
+ InReplyToAccountID string `bun:"type:CHAR(26),nullzero"`
+ InReplyTo *Status `bun:"-"`
+ BoostOfID string `bun:"type:CHAR(26),nullzero"`
+ BoostOfURI string `bun:"-"`
+ BoostOfAccountID string `bun:"type:CHAR(26),nullzero"`
+ BoostOf *Status `bun:"-"`
+ ThreadID string `bun:"type:CHAR(26),nullzero"`
+ PollID string `bun:"type:CHAR(26),nullzero"`
+ ContentWarning string `bun:",nullzero"`
+ Visibility string `bun:",nullzero,notnull"`
+ Sensitive *bool `bun:",nullzero,notnull,default:false"`
+ Language string `bun:",nullzero"`
+ CreatedWithApplicationID string `bun:"type:CHAR(26),nullzero"`
+ ActivityStreamsType string `bun:",nullzero,notnull"`
+ Text string `bun:""`
+ Federated *bool `bun:",notnull"`
+ Boostable *bool `bun:",notnull"`
+ Replyable *bool `bun:",notnull"`
+ Likeable *bool `bun:",notnull"`
+}
diff --git a/internal/db/bundb/status_test.go b/internal/db/bundb/status_test.go
index 4f0db5e2c..0111dc6e7 100644
--- a/internal/db/bundb/status_test.go
+++ b/internal/db/bundb/status_test.go
@@ -44,9 +44,6 @@ func (suite *StatusTestSuite) TestGetStatusByID() {
suite.Nil(status.InReplyTo)
suite.Nil(status.InReplyToAccount)
suite.True(*status.Federated)
- suite.True(*status.Boostable)
- suite.True(*status.Replyable)
- suite.True(*status.Likeable)
}
func (suite *StatusTestSuite) TestGetStatusesByIDs() {
@@ -73,9 +70,6 @@ func (suite *StatusTestSuite) TestGetStatusesByIDs() {
suite.Nil(status1.InReplyTo)
suite.Nil(status1.InReplyToAccount)
suite.True(*status1.Federated)
- suite.True(*status1.Boostable)
- suite.True(*status1.Replyable)
- suite.True(*status1.Likeable)
status2 := statuses[1]
suite.NotNil(status2)
@@ -86,9 +80,6 @@ func (suite *StatusTestSuite) TestGetStatusesByIDs() {
suite.Nil(status2.InReplyTo)
suite.Nil(status2.InReplyToAccount)
suite.True(*status2.Federated)
- suite.True(*status2.Boostable)
- suite.False(*status2.Replyable)
- suite.False(*status2.Likeable)
}
func (suite *StatusTestSuite) TestGetStatusByURI() {
@@ -104,9 +95,6 @@ func (suite *StatusTestSuite) TestGetStatusByURI() {
suite.Nil(status.InReplyTo)
suite.Nil(status.InReplyToAccount)
suite.True(*status.Federated)
- suite.True(*status.Boostable)
- suite.False(*status.Replyable)
- suite.False(*status.Likeable)
}
func (suite *StatusTestSuite) TestGetStatusWithExtras() {
@@ -121,9 +109,6 @@ func (suite *StatusTestSuite) TestGetStatusWithExtras() {
suite.NotEmpty(status.Attachments)
suite.NotEmpty(status.Emojis)
suite.True(*status.Federated)
- suite.True(*status.Boostable)
- suite.True(*status.Replyable)
- suite.True(*status.Likeable)
}
func (suite *StatusTestSuite) TestGetStatusWithMention() {
@@ -138,9 +123,6 @@ func (suite *StatusTestSuite) TestGetStatusWithMention() {
suite.NotEmpty(status.InReplyToID)
suite.NotEmpty(status.InReplyToAccountID)
suite.True(*status.Federated)
- suite.True(*status.Boostable)
- suite.True(*status.Replyable)
- suite.True(*status.Likeable)
}
// The below test was originally used to ensure that a second
diff --git a/internal/db/bundb/statusfave.go b/internal/db/bundb/statusfave.go
index 8e9ff501c..e3daa876b 100644
--- a/internal/db/bundb/statusfave.go
+++ b/internal/db/bundb/statusfave.go
@@ -23,6 +23,7 @@ import (
"errors"
"fmt"
"slices"
+ "time"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
@@ -77,6 +78,21 @@ func (s *statusFaveDB) GetStatusFaveByID(ctx context.Context, id string) (*gtsmo
)
}
+func (s *statusFaveDB) GetStatusFaveByURI(ctx context.Context, uri string) (*gtsmodel.StatusFave, error) {
+ return s.getStatusFave(
+ ctx,
+ "URI",
+ func(fave *gtsmodel.StatusFave) error {
+ return s.db.
+ NewSelect().
+ Model(fave).
+ Where("? = ?", bun.Ident("uri"), uri).
+ Scan(ctx)
+ },
+ uri,
+ )
+}
+
func (s *statusFaveDB) getStatusFave(ctx context.Context, lookup string, dbQuery func(*gtsmodel.StatusFave) error, keyParts ...any) (*gtsmodel.StatusFave, error) {
// Fetch status fave from database cache with loader callback
fave, err := s.state.Caches.GTS.StatusFave.LoadOne(lookup, func() (*gtsmodel.StatusFave, error) {
@@ -242,6 +258,26 @@ func (s *statusFaveDB) PutStatusFave(ctx context.Context, fave *gtsmodel.StatusF
})
}
+func (s *statusFaveDB) UpdateStatusFave(ctx context.Context, fave *gtsmodel.StatusFave, columns ...string) error {
+ fave.UpdatedAt = time.Now()
+ if len(columns) > 0 {
+ // If we're updating by column,
+ // ensure "updated_at" is included.
+ columns = append(columns, "updated_at")
+ }
+
+ // Update the status fave model in the database.
+ return s.state.Caches.GTS.StatusFave.Store(fave, func() error {
+ _, err := s.db.
+ NewUpdate().
+ Model(fave).
+ Where("? = ?", bun.Ident("status_fave.id"), fave.ID).
+ Column(columns...).
+ Exec(ctx)
+ return err
+ })
+}
+
func (s *statusFaveDB) DeleteStatusFaveByID(ctx context.Context, id string) error {
var statusID string
diff --git a/internal/db/bundb/timeline_test.go b/internal/db/bundb/timeline_test.go
index 98ae6b20f..0a014d321 100644
--- a/internal/db/bundb/timeline_test.go
+++ b/internal/db/bundb/timeline_test.go
@@ -65,9 +65,7 @@ func getFutureStatus() *gtsmodel.Status {
Language: "en",
CreatedWithApplicationID: "01F8MGXQRHYF5QPMTMXP78QC2F",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
+ InteractionPolicy: gtsmodel.DefaultInteractionPolicyPublic(),
ActivityStreamsType: ap.ObjectNote,
}
}
diff --git a/internal/db/db.go b/internal/db/db.go
index 330766306..a148d778a 100644
--- a/internal/db/db.go
+++ b/internal/db/db.go
@@ -32,6 +32,7 @@ type DB interface {
Emoji
HeaderFilter
Instance
+ Interaction
Filter
List
Marker
diff --git a/internal/db/interaction.go b/internal/db/interaction.go
new file mode 100644
index 000000000..6f595c54e
--- /dev/null
+++ b/internal/db/interaction.go
@@ -0,0 +1,41 @@
+// 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 .
+
+package db
+
+import (
+ "context"
+
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+)
+
+type Interaction interface {
+ // GetInteractionApprovalByID gets one approval with the given id.
+ GetInteractionApprovalByID(ctx context.Context, id string) (*gtsmodel.InteractionApproval, error)
+
+ // GetInteractionApprovalByID gets one approval with the given uri.
+ GetInteractionApprovalByURI(ctx context.Context, id string) (*gtsmodel.InteractionApproval, error)
+
+ // PopulateInteractionApproval ensures that the approval's struct fields are populated.
+ PopulateInteractionApproval(ctx context.Context, approval *gtsmodel.InteractionApproval) error
+
+ // PutInteractionApproval puts a new approval in the database.
+ PutInteractionApproval(ctx context.Context, approval *gtsmodel.InteractionApproval) error
+
+ // DeleteInteractionApprovalByID deletes one approval with the given ID.
+ DeleteInteractionApprovalByID(ctx context.Context, id string) error
+}
diff --git a/internal/db/statusfave.go b/internal/db/statusfave.go
index 343a80caa..192ef436b 100644
--- a/internal/db/statusfave.go
+++ b/internal/db/statusfave.go
@@ -27,9 +27,12 @@ type StatusFave interface {
// GetStatusFaveByAccountID gets one status fave created by the given accountID, targeting the given statusID.
GetStatusFave(ctx context.Context, accountID string, statusID string) (*gtsmodel.StatusFave, error)
- // GetStatusFave returns one status fave with the given id.
+ // GetStatusFaveByID returns one status fave with the given id.
GetStatusFaveByID(ctx context.Context, id string) (*gtsmodel.StatusFave, error)
+ // GetStatusFaveByURI returns one status fave with the given uri.
+ GetStatusFaveByURI(ctx context.Context, uri string) (*gtsmodel.StatusFave, error)
+
// GetStatusFaves returns a slice of faves/likes of the status with given ID.
// This slice will be unfiltered, not taking account of blocks and whatnot, so filter it before serving it back to a user.
GetStatusFaves(ctx context.Context, statusID string) ([]*gtsmodel.StatusFave, error)
@@ -40,6 +43,9 @@ type StatusFave interface {
// PutStatusFave inserts the given statusFave into the database.
PutStatusFave(ctx context.Context, statusFave *gtsmodel.StatusFave) error
+ // UpdateStatusFave updates one statusFave in the database.
+ UpdateStatusFave(ctx context.Context, statusFave *gtsmodel.StatusFave, columns ...string) error
+
// DeleteStatusFave deletes one status fave with the given id.
DeleteStatusFaveByID(ctx context.Context, id string) error
diff --git a/internal/federation/dereferencing/announce.go b/internal/federation/dereferencing/announce.go
index 6516bdced..51f1ffcdd 100644
--- a/internal/federation/dereferencing/announce.go
+++ b/internal/federation/dereferencing/announce.go
@@ -92,9 +92,6 @@ func (d *Dereferencer) EnrichAnnounce(
boost.BoostOfAccount = target.Account
boost.Visibility = target.Visibility
boost.Federated = target.Federated
- boost.Boostable = target.Boostable
- boost.Replyable = target.Replyable
- boost.Likeable = target.Likeable
// Store the boost wrapper status in database.
switch err = d.state.DB.PutStatus(ctx, boost); {
diff --git a/internal/federation/dereferencing/status.go b/internal/federation/dereferencing/status.go
index 406534457..0e227a0c1 100644
--- a/internal/federation/dereferencing/status.go
+++ b/internal/federation/dereferencing/status.go
@@ -633,9 +633,7 @@ func (d *Dereferencer) isPermittedStatus(
}
}
- if permitted &&
- *status.InReplyTo.Replyable {
- // Status is reply-able to.
+ if permitted {
return true, nil
}
diff --git a/internal/federation/dereferencing/status_test.go b/internal/federation/dereferencing/status_test.go
index 2d0085cce..3b2c2bff2 100644
--- a/internal/federation/dereferencing/status_test.go
+++ b/internal/federation/dereferencing/status_test.go
@@ -56,9 +56,6 @@ func (suite *StatusTestSuite) TestDereferenceSimpleStatus() {
suite.NoError(err)
suite.Equal(status.ID, dbStatus.ID)
suite.True(*dbStatus.Federated)
- suite.True(*dbStatus.Boostable)
- suite.True(*dbStatus.Replyable)
- suite.True(*dbStatus.Likeable)
// account should be in the database now too
account, err := suite.db.GetAccountByURI(context.Background(), status.AccountURI)
@@ -96,9 +93,6 @@ func (suite *StatusTestSuite) TestDereferenceStatusWithMention() {
suite.NoError(err)
suite.Equal(status.ID, dbStatus.ID)
suite.True(*dbStatus.Federated)
- suite.True(*dbStatus.Boostable)
- suite.True(*dbStatus.Replyable)
- suite.True(*dbStatus.Likeable)
// account should be in the database now too
account, err := suite.db.GetAccountByURI(context.Background(), status.AccountURI)
@@ -151,9 +145,6 @@ func (suite *StatusTestSuite) TestDereferenceStatusWithTag() {
suite.NoError(err)
suite.Equal(status.ID, dbStatus.ID)
suite.True(*dbStatus.Federated)
- suite.True(*dbStatus.Boostable)
- suite.True(*dbStatus.Replyable)
- suite.True(*dbStatus.Likeable)
// account should be in the database now too
account, err := suite.db.GetAccountByURI(context.Background(), status.AccountURI)
@@ -197,9 +188,6 @@ func (suite *StatusTestSuite) TestDereferenceStatusWithImageAndNoContent() {
suite.NoError(err)
suite.Equal(status.ID, dbStatus.ID)
suite.True(*dbStatus.Federated)
- suite.True(*dbStatus.Boostable)
- suite.True(*dbStatus.Replyable)
- suite.True(*dbStatus.Likeable)
// account should be in the database now too
account, err := suite.db.GetAccountByURI(context.Background(), status.AccountURI)
diff --git a/internal/filter/visibility/boostable.go b/internal/filter/visibility/boostable.go
index 7c8bda324..7362ad45c 100644
--- a/internal/filter/visibility/boostable.go
+++ b/internal/filter/visibility/boostable.go
@@ -53,10 +53,5 @@ func (f *Filter) StatusBoostable(ctx context.Context, requester *gtsmodel.Accoun
return false, nil
}
- if !*status.Boostable {
- log.Trace(ctx, "status marked not boostable")
- return false, nil
- }
-
return true, nil
}
diff --git a/internal/filter/visibility/home_timeline_test.go b/internal/filter/visibility/home_timeline_test.go
index d8211c8dd..9b7ce8c51 100644
--- a/internal/filter/visibility/home_timeline_test.go
+++ b/internal/filter/visibility/home_timeline_test.go
@@ -161,9 +161,6 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestThread() {
Language: "en",
CreatedWithApplicationID: "",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
ActivityStreamsType: ap.ObjectNote,
}
if err := suite.db.PutStatus(ctx, firstReplyStatus); err != nil {
@@ -214,9 +211,6 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyFollowersOnly(
Language: "en",
CreatedWithApplicationID: "",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
ActivityStreamsType: ap.ObjectNote,
}
if err := suite.db.PutStatus(ctx, originalStatus); err != nil {
@@ -248,9 +242,6 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyFollowersOnly(
Language: "en",
CreatedWithApplicationID: "",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
ActivityStreamsType: ap.ObjectNote,
}
if err := suite.db.PutStatus(ctx, firstReplyStatus); err != nil {
@@ -282,9 +273,6 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyFollowersOnly(
Language: "en",
CreatedWithApplicationID: "",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
ActivityStreamsType: ap.ObjectNote,
}
if err := suite.db.PutStatus(ctx, secondReplyStatus); err != nil {
@@ -327,9 +315,6 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyPublicAndUnloc
Language: "en",
CreatedWithApplicationID: "",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
ActivityStreamsType: ap.ObjectNote,
}
if err := suite.db.PutStatus(ctx, originalStatus); err != nil {
@@ -361,9 +346,6 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyPublicAndUnloc
Language: "en",
CreatedWithApplicationID: "",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
ActivityStreamsType: ap.ObjectNote,
}
if err := suite.db.PutStatus(ctx, firstReplyStatus); err != nil {
@@ -395,9 +377,6 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyPublicAndUnloc
Language: "en",
CreatedWithApplicationID: "",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
ActivityStreamsType: ap.ObjectNote,
}
if err := suite.db.PutStatus(ctx, secondReplyStatus); err != nil {
diff --git a/internal/gtsmodel/accountsettings.go b/internal/gtsmodel/accountsettings.go
index 109d90ad9..592a2330d 100644
--- a/internal/gtsmodel/accountsettings.go
+++ b/internal/gtsmodel/accountsettings.go
@@ -21,15 +21,20 @@ import "time"
// AccountSettings models settings / preferences for a local, non-instance account.
type AccountSettings struct {
- AccountID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // AccountID that owns this settings.
- CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created.
- UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item was last updated.
- Privacy Visibility `bun:",nullzero"` // Default post privacy for this account
- Sensitive *bool `bun:",nullzero,notnull,default:false"` // Set posts from this account to sensitive by default?
- Language string `bun:",nullzero,notnull,default:'en'"` // What language does this account post in?
- StatusContentType string `bun:",nullzero"` // What is the default format for statuses posted by this account (only for local accounts).
- Theme string `bun:",nullzero"` // Preset CSS theme filename selected by this Account (empty string if nothing set).
- CustomCSS string `bun:",nullzero"` // Custom CSS that should be displayed for this Account's profile and statuses.
- EnableRSS *bool `bun:",nullzero,notnull,default:false"` // enable RSS feed subscription for this account's public posts at [URL]/feed
- HideCollections *bool `bun:",nullzero,notnull,default:false"` // Hide this account's followers/following collections.
+ AccountID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // AccountID that owns this settings.
+ CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created.
+ UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item was last updated.
+ Privacy Visibility `bun:",nullzero"` // Default post privacy for this account
+ Sensitive *bool `bun:",nullzero,notnull,default:false"` // Set posts from this account to sensitive by default?
+ Language string `bun:",nullzero,notnull,default:'en'"` // What language does this account post in?
+ StatusContentType string `bun:",nullzero"` // What is the default format for statuses posted by this account (only for local accounts).
+ Theme string `bun:",nullzero"` // Preset CSS theme filename selected by this Account (empty string if nothing set).
+ CustomCSS string `bun:",nullzero"` // Custom CSS that should be displayed for this Account's profile and statuses.
+ EnableRSS *bool `bun:",nullzero,notnull,default:false"` // enable RSS feed subscription for this account's public posts at [URL]/feed
+ HideCollections *bool `bun:",nullzero,notnull,default:false"` // Hide this account's followers/following collections.
+ InteractionPolicyDirect *InteractionPolicy `bun:""` // Interaction policy to use for new direct visibility statuses by this account. If null, assume default policy.
+ InteractionPolicyMutualsOnly *InteractionPolicy `bun:""` // Interaction policy to use for new mutuals only visibility statuses. If null, assume default policy.
+ InteractionPolicyFollowersOnly *InteractionPolicy `bun:""` // Interaction policy to use for new followers only visibility statuses. If null, assume default policy.
+ InteractionPolicyUnlocked *InteractionPolicy `bun:""` // Interaction policy to use for new unlocked visibility statuses. If null, assume default policy.
+ InteractionPolicyPublic *InteractionPolicy `bun:""` // Interaction policy to use for new public visibility statuses. If null, assume default policy.
}
diff --git a/internal/gtsmodel/interactionapproval.go b/internal/gtsmodel/interactionapproval.go
new file mode 100644
index 000000000..f6a5da83b
--- /dev/null
+++ b/internal/gtsmodel/interactionapproval.go
@@ -0,0 +1,55 @@
+// 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 .
+
+package gtsmodel
+
+import "time"
+
+// InteractionApproval refers to a single Accept activity sent
+// *from this instance* in response to an interaction request,
+// in order to approve it.
+//
+// Accepts originating from remote instances are not stored
+// using this format; the URI of the remote Accept is instead
+// just added to the *gtsmodel.StatusFave or *gtsmodel.Status.
+type InteractionApproval struct {
+ ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
+ CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
+ UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
+ AccountID string `bun:"type:CHAR(26),nullzero,notnull"` // id of the account that owns this accept/approval
+ Account *Account `bun:"-"` // account corresponding to accountID
+ InteractingAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // id of the account that did the interaction that this Accept targets.
+ InteractingAccount *Account `bun:"-"` // account corresponding to targetAccountID
+ InteractionURI string `bun:",nullzero,notnull"` // URI of the target like, reply, or announce
+ InteractionType InteractionType `bun:",notnull"` // One of Like, Reply, or Announce.
+ URI string `bun:",nullzero,notnull,unique"` // ActivityPub URI of the Accept.
+}
+
+// Like / Reply / Announce
+type InteractionType int
+
+const (
+ // WARNING: DO NOT CHANGE THE ORDER OF THESE,
+ // as this will cause breakage of approvals!
+ //
+ // If you need to add new interaction types,
+ // add them *to the end* of the list.
+
+ InteractionLike InteractionType = iota
+ InteractionReply
+ InteractionAnnounce
+)
diff --git a/internal/gtsmodel/interactionpolicy.go b/internal/gtsmodel/interactionpolicy.go
new file mode 100644
index 000000000..ecb525b47
--- /dev/null
+++ b/internal/gtsmodel/interactionpolicy.go
@@ -0,0 +1,314 @@
+// 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 .
+
+package gtsmodel
+
+// A policy URI is GoToSocial's internal representation of
+// one ActivityPub URI for an Actor or a Collection of Actors,
+// specific to the domain of enforcing interaction policies.
+//
+// A PolicyValue can be stored in the database either as one
+// of the Value constants defined below (to save space), OR as
+// a full-fledged ActivityPub URI.
+//
+// A PolicyValue should be translated to the canonical string
+// value of the represented URI when federating an item, or
+// from the canonical string value of the URI when receiving
+// or retrieving an item.
+//
+// For example, if the PolicyValue `followers` was being
+// federated outwards in an interaction policy attached to an
+// item created by the actor `https://example.org/users/someone`,
+// then it should be translated to their followers URI when sent,
+// eg., `https://example.org/users/someone/followers`.
+//
+// Likewise, if GoToSocial receives an item with an interaction
+// policy containing `https://example.org/users/someone/followers`,
+// and the item was created by `https://example.org/users/someone`,
+// then the followers URI would be converted to `followers`
+// for internal storage.
+type PolicyValue string
+
+const (
+ // Stand-in for ActivityPub magic public URI,
+ // which encompasses every possible Actor URI.
+ PolicyValuePublic PolicyValue = "public"
+ // Stand-in for the Followers Collection of
+ // the item owner's Actor.
+ PolicyValueFollowers PolicyValue = "followers"
+ // Stand-in for the Following Collection of
+ // the item owner's Actor.
+ PolicyValueFollowing PolicyValue = "following"
+ // Stand-in for the Mutuals Collection of
+ // the item owner's Actor.
+ //
+ // (TODO: Reserved, currently unused).
+ PolicyValueMutuals PolicyValue = "mutuals"
+ // Stand-in for Actor URIs tagged in the item.
+ PolicyValueMentioned PolicyValue = "mentioned"
+ // Stand-in for the Actor URI of the item owner.
+ PolicyValueAuthor PolicyValue = "author"
+)
+
+// FeasibleForVisibility returns true if the PolicyValue could feasibly
+// be set in a policy for an item with the given visibility, otherwise
+// returns false.
+//
+// For example, PolicyValuePublic could not be set in a policy for an
+// item with visibility FollowersOnly, but could be set in a policy
+// for an item with visibility Public or Unlocked.
+//
+// This is not prescriptive, and should be used only to guide policy
+// choices. Eg., if a remote instance wants to do something wacky like
+// set "anyone can interact with this status" for a Direct visibility
+// status, that's their business; our normal visibility filtering will
+// prevent users on our instance from actually being able to interact
+// unless they can see the status anyway.
+func (p PolicyValue) FeasibleForVisibility(v Visibility) bool {
+ switch p {
+
+ // Mentioned and self Values are
+ // feasible for any visibility.
+ case PolicyValueAuthor,
+ PolicyValueMentioned:
+ return true
+
+ // Followers/following/mutual Values
+ // are only feasible for items with
+ // followers visibility and higher.
+ case PolicyValueFollowers,
+ PolicyValueFollowing:
+ return v == VisibilityFollowersOnly ||
+ v == VisibilityPublic ||
+ v == VisibilityUnlocked
+
+ // Public policy Value only feasible
+ // for items that are To or CC public.
+ case PolicyValuePublic:
+ return v == VisibilityUnlocked ||
+ v == VisibilityPublic
+
+ // Any other combo
+ // is probably fine.
+ default:
+ return true
+ }
+}
+
+type PolicyValues []PolicyValue
+
+// PolicyResult represents the result of
+// checking an Actor URI and interaction
+// type against the conditions of an
+// InteractionPolicy to determine if that
+// interaction is permitted.
+type PolicyResult int
+
+const (
+ // Interaction is forbidden for this
+ // PolicyValue + interaction combination.
+ PolicyResultForbidden PolicyResult = iota
+ // Interaction is conditionally permitted
+ // for this PolicyValue + interaction combo,
+ // pending approval by the item owner.
+ PolicyResultWithApproval
+ // Interaction is permitted for this
+ // PolicyValue + interaction combination.
+ PolicyResultPermitted
+)
+
+// An InteractionPolicy determines which
+// interactions will be accepted for an
+// item, and according to what rules.
+type InteractionPolicy struct {
+ // Conditions in which a Like
+ // interaction will be accepted
+ // for an item with this policy.
+ CanLike PolicyRules
+ // Conditions in which a Reply
+ // interaction will be accepted
+ // for an item with this policy.
+ CanReply PolicyRules
+ // Conditions in which an Announce
+ // interaction will be accepted
+ // for an item with this policy.
+ CanAnnounce PolicyRules
+}
+
+// PolicyRules represents the rules according
+// to which a certain interaction is permitted
+// to various Actor and Actor Collection URIs.
+type PolicyRules struct {
+ // Always is for PolicyValues who are
+ // permitted to do an interaction
+ // without requiring approval.
+ Always PolicyValues
+ // WithApproval is for PolicyValues who
+ // are conditionally permitted to do
+ // an interaction, pending approval.
+ WithApproval PolicyValues
+}
+
+// Returns the default interaction policy
+// for the given visibility level.
+func DefaultInteractionPolicyFor(v Visibility) *InteractionPolicy {
+ switch v {
+ case VisibilityPublic:
+ return DefaultInteractionPolicyPublic()
+ case VisibilityUnlocked:
+ return DefaultInteractionPolicyUnlocked()
+ case VisibilityFollowersOnly, VisibilityMutualsOnly:
+ return DefaultInteractionPolicyFollowersOnly()
+ case VisibilityDirect:
+ return DefaultInteractionPolicyDirect()
+ default:
+ panic("visibility " + v + " not recognized")
+ }
+}
+
+// Returns the default interaction policy
+// for a post with visibility of public.
+func DefaultInteractionPolicyPublic() *InteractionPolicy {
+ // Anyone can like.
+ canLikeAlways := make(PolicyValues, 1)
+ canLikeAlways[0] = PolicyValuePublic
+
+ // Unused, set empty.
+ canLikeWithApproval := make(PolicyValues, 0)
+
+ // Anyone can reply.
+ canReplyAlways := make(PolicyValues, 1)
+ canReplyAlways[0] = PolicyValuePublic
+
+ // Unused, set empty.
+ canReplyWithApproval := make(PolicyValues, 0)
+
+ // Anyone can announce.
+ canAnnounceAlways := make(PolicyValues, 1)
+ canAnnounceAlways[0] = PolicyValuePublic
+
+ // Unused, set empty.
+ canAnnounceWithApproval := make(PolicyValues, 0)
+
+ return &InteractionPolicy{
+ CanLike: PolicyRules{
+ Always: canLikeAlways,
+ WithApproval: canLikeWithApproval,
+ },
+ CanReply: PolicyRules{
+ Always: canReplyAlways,
+ WithApproval: canReplyWithApproval,
+ },
+ CanAnnounce: PolicyRules{
+ Always: canAnnounceAlways,
+ WithApproval: canAnnounceWithApproval,
+ },
+ }
+}
+
+// Returns the default interaction policy
+// for a post with visibility of unlocked.
+func DefaultInteractionPolicyUnlocked() *InteractionPolicy {
+ // Same as public (for now).
+ return DefaultInteractionPolicyPublic()
+}
+
+// Returns the default interaction policy for
+// a post with visibility of followers only.
+func DefaultInteractionPolicyFollowersOnly() *InteractionPolicy {
+ // Self, followers and mentioned can like.
+ canLikeAlways := make(PolicyValues, 3)
+ canLikeAlways[0] = PolicyValueAuthor
+ canLikeAlways[1] = PolicyValueFollowers
+ canLikeAlways[2] = PolicyValueMentioned
+
+ // Unused, set empty.
+ canLikeWithApproval := make(PolicyValues, 0)
+
+ // Self, followers and mentioned can reply.
+ canReplyAlways := make(PolicyValues, 3)
+ canReplyAlways[0] = PolicyValueAuthor
+ canReplyAlways[1] = PolicyValueFollowers
+ canReplyAlways[2] = PolicyValueMentioned
+
+ // Unused, set empty.
+ canReplyWithApproval := make(PolicyValues, 0)
+
+ // Only self can announce.
+ canAnnounceAlways := make(PolicyValues, 1)
+ canAnnounceAlways[0] = PolicyValueAuthor
+
+ // Unused, set empty.
+ canAnnounceWithApproval := make(PolicyValues, 0)
+
+ return &InteractionPolicy{
+ CanLike: PolicyRules{
+ Always: canLikeAlways,
+ WithApproval: canLikeWithApproval,
+ },
+ CanReply: PolicyRules{
+ Always: canReplyAlways,
+ WithApproval: canReplyWithApproval,
+ },
+ CanAnnounce: PolicyRules{
+ Always: canAnnounceAlways,
+ WithApproval: canAnnounceWithApproval,
+ },
+ }
+}
+
+// Returns the default interaction policy
+// for a post with visibility of direct.
+func DefaultInteractionPolicyDirect() *InteractionPolicy {
+ // Mentioned and self can always like.
+ canLikeAlways := make(PolicyValues, 2)
+ canLikeAlways[0] = PolicyValueAuthor
+ canLikeAlways[1] = PolicyValueMentioned
+
+ // Unused, set empty.
+ canLikeWithApproval := make(PolicyValues, 0)
+
+ // Mentioned and self can always reply.
+ canReplyAlways := make(PolicyValues, 2)
+ canReplyAlways[0] = PolicyValueAuthor
+ canReplyAlways[1] = PolicyValueMentioned
+
+ // Unused, set empty.
+ canReplyWithApproval := make(PolicyValues, 0)
+
+ // Only self can announce.
+ canAnnounceAlways := make(PolicyValues, 1)
+ canAnnounceAlways[0] = PolicyValueAuthor
+
+ // Unused, set empty.
+ canAnnounceWithApproval := make(PolicyValues, 0)
+
+ return &InteractionPolicy{
+ CanLike: PolicyRules{
+ Always: canLikeAlways,
+ WithApproval: canLikeWithApproval,
+ },
+ CanReply: PolicyRules{
+ Always: canReplyAlways,
+ WithApproval: canReplyWithApproval,
+ },
+ CanAnnounce: PolicyRules{
+ Always: canAnnounceAlways,
+ WithApproval: canAnnounceWithApproval,
+ },
+ }
+}
diff --git a/internal/gtsmodel/notification.go b/internal/gtsmodel/notification.go
index 0f946ed0f..5cf6b061a 100644
--- a/internal/gtsmodel/notification.go
+++ b/internal/gtsmodel/notification.go
@@ -39,12 +39,15 @@ type NotificationType string
// Notification Types
const (
- NotificationFollow NotificationType = "follow" // NotificationFollow -- someone followed you
- NotificationFollowRequest NotificationType = "follow_request" // NotificationFollowRequest -- someone requested to follow you
- NotificationMention NotificationType = "mention" // NotificationMention -- someone mentioned you in their status
- NotificationReblog NotificationType = "reblog" // NotificationReblog -- someone boosted one of your statuses
- NotificationFave NotificationType = "favourite" // NotificationFave -- someone faved/liked one of your statuses
- NotificationPoll NotificationType = "poll" // NotificationPoll -- a poll you voted in or created has ended
- NotificationStatus NotificationType = "status" // NotificationStatus -- someone you enabled notifications for has posted a status.
- NotificationSignup NotificationType = "admin.sign_up" // NotificationSignup -- someone has submitted a new account sign-up to the instance.
+ NotificationFollow NotificationType = "follow" // NotificationFollow -- someone followed you
+ NotificationFollowRequest NotificationType = "follow_request" // NotificationFollowRequest -- someone requested to follow you
+ NotificationMention NotificationType = "mention" // NotificationMention -- someone mentioned you in their status
+ NotificationReblog NotificationType = "reblog" // NotificationReblog -- someone boosted one of your statuses
+ NotificationFave NotificationType = "favourite" // NotificationFave -- someone faved/liked one of your statuses
+ NotificationPoll NotificationType = "poll" // NotificationPoll -- a poll you voted in or created has ended
+ NotificationStatus NotificationType = "status" // NotificationStatus -- someone you enabled notifications for has posted a status.
+ NotificationSignup NotificationType = "admin.sign_up" // NotificationSignup -- someone has submitted a new account sign-up to the instance.
+ NotificationPendingFave NotificationType = "pending.favourite" // Someone has faved a status of yours, which requires approval by you.
+ NotificationPendingReply NotificationType = "pending.reply" // Someone has replied to a status of yours, which requires approval by you.
+ NotificationPendingReblog NotificationType = "pending.reblog" // Someone has boosted a status of yours, which requires approval by you.
)
diff --git a/internal/gtsmodel/status.go b/internal/gtsmodel/status.go
index 3bbe82c08..221663ccd 100644
--- a/internal/gtsmodel/status.go
+++ b/internal/gtsmodel/status.go
@@ -66,9 +66,9 @@ type Status struct {
ActivityStreamsType string `bun:",nullzero,notnull"` // What is the activitystreams type of this status? See: https://www.w3.org/TR/activitystreams-vocabulary/#object-types. Will probably almost always be Note but who knows!.
Text string `bun:""` // Original text of the status without formatting
Federated *bool `bun:",notnull"` // This status will be federated beyond the local timeline(s)
- Boostable *bool `bun:",notnull"` // This status can be boosted/reblogged
- Replyable *bool `bun:",notnull"` // This status can be replied to
- Likeable *bool `bun:",notnull"` // This status can be liked/faved
+ InteractionPolicy *InteractionPolicy `bun:""` // InteractionPolicy for this status. If null then the default InteractionPolicy should be assumed for this status's Visibility. Always null for boost wrappers.
+ PendingApproval *bool `bun:",nullzero,notnull,default:false"` // If true then status is a reply or boost wrapper that must be Approved by the reply-ee or boost-ee before being fully distributed.
+ ApprovedByURI string `bun:",nullzero"` // URI of an Accept Activity that approves the Announce or Create Activity that this status was/will be attached to.
}
// GetID implements timeline.Timelineable{}.
diff --git a/internal/gtsmodel/statusfave.go b/internal/gtsmodel/statusfave.go
index f81226f8b..644b3ca63 100644
--- a/internal/gtsmodel/statusfave.go
+++ b/internal/gtsmodel/statusfave.go
@@ -31,4 +31,6 @@ type StatusFave struct {
StatusID string `bun:"type:CHAR(26),unique:statusfaveaccountstatus,nullzero,notnull"` // database id of the status that has been 'faved'
Status *Status `bun:"-"` // the faved status
URI string `bun:",nullzero,notnull,unique"` // ActivityPub URI of this fave
+ PendingApproval *bool `bun:",nullzero,notnull,default:false"` // If true then Like must be Approved by the like-ee before being fully distributed.
+ ApprovedByURI string `bun:",nullzero"` // URI of an Accept Activity that approves this Like.
}
diff --git a/internal/processing/status/create.go b/internal/processing/status/create.go
index 80cc65c7f..8898181ae 100644
--- a/internal/processing/status/create.go
+++ b/internal/processing/status/create.go
@@ -185,11 +185,6 @@ func (p *Processor) processInReplyTo(ctx context.Context, requester *gtsmodel.Ac
return errWithCode
}
- if !*inReplyTo.Replyable {
- const text = "in-reply-to status marked as not replyable"
- return gtserror.NewErrorForbidden(errors.New(text), text)
- }
-
// Set status fields from inReplyTo.
status.InReplyToID = inReplyTo.ID
status.InReplyTo = inReplyTo
@@ -289,9 +284,6 @@ func (p *Processor) processMediaIDs(ctx context.Context, form *apimodel.Advanced
func processVisibility(form *apimodel.AdvancedStatusCreateForm, accountDefaultVis gtsmodel.Visibility, status *gtsmodel.Status) error {
// by default all flags are set to true
federated := true
- boostable := true
- replyable := true
- likeable := true
// If visibility isn't set on the form, then just take the account default.
// If that's also not set, take the default for the whole instance.
@@ -305,57 +297,10 @@ func processVisibility(form *apimodel.AdvancedStatusCreateForm, accountDefaultVi
vis = gtsmodel.VisibilityDefault
}
- switch vis {
- case gtsmodel.VisibilityPublic:
- // for public, there's no need to change any of the advanced flags from true regardless of what the user filled out
- break
- case gtsmodel.VisibilityUnlocked:
- // for unlocked the user can set any combination of flags they like so look at them all to see if they're set and then apply them
- if form.Federated != nil {
- federated = *form.Federated
- }
-
- if form.Boostable != nil {
- boostable = *form.Boostable
- }
-
- if form.Replyable != nil {
- replyable = *form.Replyable
- }
-
- if form.Likeable != nil {
- likeable = *form.Likeable
- }
-
- case gtsmodel.VisibilityFollowersOnly, gtsmodel.VisibilityMutualsOnly:
- // for followers or mutuals only, boostable will *always* be false, but the other fields can be set so check and apply them
- boostable = false
-
- if form.Federated != nil {
- federated = *form.Federated
- }
-
- if form.Replyable != nil {
- replyable = *form.Replyable
- }
-
- if form.Likeable != nil {
- likeable = *form.Likeable
- }
-
- case gtsmodel.VisibilityDirect:
- // direct is pretty easy: there's only one possible setting so return it
- federated = true
- boostable = false
- replyable = true
- likeable = true
- }
+ // Todo: sort out likeable/replyable/boostable in next PR.
status.Visibility = vis
status.Federated = &federated
- status.Boostable = &boostable
- status.Replyable = &replyable
- status.Likeable = &likeable
return nil
}
diff --git a/internal/processing/status/create_test.go b/internal/processing/status/create_test.go
index e32d8bc52..a8211d1c1 100644
--- a/internal/processing/status/create_test.go
+++ b/internal/processing/status/create_test.go
@@ -53,9 +53,6 @@ func (suite *StatusCreateTestSuite) TestProcessContentWarningWithQuotationMarks(
},
AdvancedVisibilityFlagsForm: apimodel.AdvancedVisibilityFlagsForm{
Federated: nil,
- Boostable: nil,
- Replyable: nil,
- Likeable: nil,
},
}
@@ -87,9 +84,6 @@ func (suite *StatusCreateTestSuite) TestProcessContentWarningWithHTMLEscapedQuot
},
AdvancedVisibilityFlagsForm: apimodel.AdvancedVisibilityFlagsForm{
Federated: nil,
- Boostable: nil,
- Replyable: nil,
- Likeable: nil,
},
}
@@ -125,9 +119,6 @@ func (suite *StatusCreateTestSuite) TestProcessStatusMarkdownWithUnderscoreEmoji
},
AdvancedVisibilityFlagsForm: apimodel.AdvancedVisibilityFlagsForm{
Federated: nil,
- Boostable: nil,
- Replyable: nil,
- Likeable: nil,
},
}
@@ -159,9 +150,6 @@ func (suite *StatusCreateTestSuite) TestProcessStatusMarkdownWithSpoilerTextEmoj
},
AdvancedVisibilityFlagsForm: apimodel.AdvancedVisibilityFlagsForm{
Federated: nil,
- Boostable: nil,
- Replyable: nil,
- Likeable: nil,
},
}
@@ -197,9 +185,6 @@ func (suite *StatusCreateTestSuite) TestProcessMediaDescriptionTooShort() {
},
AdvancedVisibilityFlagsForm: apimodel.AdvancedVisibilityFlagsForm{
Federated: nil,
- Boostable: nil,
- Replyable: nil,
- Likeable: nil,
},
}
@@ -229,9 +214,6 @@ func (suite *StatusCreateTestSuite) TestProcessLanguageWithScriptPart() {
},
AdvancedVisibilityFlagsForm: apimodel.AdvancedVisibilityFlagsForm{
Federated: nil,
- Boostable: nil,
- Replyable: nil,
- Likeable: nil,
},
}
@@ -266,9 +248,6 @@ func (suite *StatusCreateTestSuite) TestProcessReplyToUnthreadedRemoteStatus() {
},
AdvancedVisibilityFlagsForm: apimodel.AdvancedVisibilityFlagsForm{
Federated: nil,
- Boostable: nil,
- Replyable: nil,
- Likeable: nil,
},
}
diff --git a/internal/processing/status/fave.go b/internal/processing/status/fave.go
index dd961c082..49dacf18d 100644
--- a/internal/processing/status/fave.go
+++ b/internal/processing/status/fave.go
@@ -62,11 +62,6 @@ func (p *Processor) getFaveableStatus(
return nil, nil, errWithCode
}
- if !*target.Likeable {
- err := errors.New("status is not faveable")
- return nil, nil, gtserror.NewErrorForbidden(err, err.Error())
- }
-
fave, err := p.state.DB.GetStatusFave(ctx, requester.ID, target.ID)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
err = fmt.Errorf("getFaveTarget: error checking existing fave: %w", err)
diff --git a/internal/processing/workers/fromclientapi_test.go b/internal/processing/workers/fromclientapi_test.go
index 15be23baf..49a68d27a 100644
--- a/internal/processing/workers/fromclientapi_test.go
+++ b/internal/processing/workers/fromclientapi_test.go
@@ -69,9 +69,6 @@ func (suite *FromClientAPITestSuite) newStatus(
Visibility: visibility,
ActivityStreamsType: ap.ObjectNote,
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
}
if replyToStatus != nil {
diff --git a/internal/processing/workers/fromfediapi_test.go b/internal/processing/workers/fromfediapi_test.go
index b28927f39..705795af4 100644
--- a/internal/processing/workers/fromfediapi_test.go
+++ b/internal/processing/workers/fromfediapi_test.go
@@ -89,77 +89,78 @@ func (suite *FromFediAPITestSuite) TestProcessFederationAnnounce() {
suite.False(*notif.Read)
}
-func (suite *FromFediAPITestSuite) TestProcessReplyMention() {
- testStructs := suite.SetupTestStructs()
- defer suite.TearDownTestStructs(testStructs)
+// Todo: fix this test up in interaction policies PR.
+// func (suite *FromFediAPITestSuite) TestProcessReplyMention() {
+// testStructs := suite.SetupTestStructs()
+// defer suite.TearDownTestStructs(testStructs)
- repliedAccount := suite.testAccounts["local_account_1"]
- repliedStatus := suite.testStatuses["local_account_1_status_1"]
- replyingAccount := suite.testAccounts["remote_account_1"]
+// repliedAccount := suite.testAccounts["local_account_1"]
+// repliedStatus := suite.testStatuses["local_account_1_status_1"]
+// replyingAccount := suite.testAccounts["remote_account_1"]
- // Set the replyingAccount's last fetched_at
- // date to something recent so no refresh is attempted,
- // and ensure it isn't a suspended account.
- replyingAccount.FetchedAt = time.Now()
- replyingAccount.SuspendedAt = time.Time{}
- replyingAccount.SuspensionOrigin = ""
- err := testStructs.State.DB.UpdateAccount(context.Background(),
- replyingAccount,
- "fetched_at",
- "suspended_at",
- "suspension_origin",
- )
- suite.NoError(err)
+// // Set the replyingAccount's last fetched_at
+// // date to something recent so no refresh is attempted,
+// // and ensure it isn't a suspended account.
+// replyingAccount.FetchedAt = time.Now()
+// replyingAccount.SuspendedAt = time.Time{}
+// replyingAccount.SuspensionOrigin = ""
+// err := testStructs.State.DB.UpdateAccount(context.Background(),
+// replyingAccount,
+// "fetched_at",
+// "suspended_at",
+// "suspension_origin",
+// )
+// suite.NoError(err)
- // Get replying statusable to use from remote test statuses.
- const replyingURI = "http://fossbros-anonymous.io/users/foss_satan/statuses/106221634728637552"
- replyingStatusable := testrig.NewTestFediStatuses()[replyingURI]
- ap.AppendInReplyTo(replyingStatusable, testrig.URLMustParse(repliedStatus.URI))
+// // Get replying statusable to use from remote test statuses.
+// const replyingURI = "http://fossbros-anonymous.io/users/foss_satan/statuses/106221634728637552"
+// replyingStatusable := testrig.NewTestFediStatuses()[replyingURI]
+// ap.AppendInReplyTo(replyingStatusable, testrig.URLMustParse(repliedStatus.URI))
- // Open a websocket stream to later test the streamed status reply.
- wssStream, errWithCode := testStructs.Processor.Stream().Open(context.Background(), repliedAccount, stream.TimelineHome)
- suite.NoError(errWithCode)
+// // Open a websocket stream to later test the streamed status reply.
+// wssStream, errWithCode := testStructs.Processor.Stream().Open(context.Background(), repliedAccount, stream.TimelineHome)
+// suite.NoError(errWithCode)
- // Send the replied status off to the fedi worker to be further processed.
- err = testStructs.Processor.Workers().ProcessFromFediAPI(context.Background(), &messages.FromFediAPI{
- APObjectType: ap.ObjectNote,
- APActivityType: ap.ActivityCreate,
- APObject: replyingStatusable,
- Receiving: repliedAccount,
- Requesting: replyingAccount,
- })
- suite.NoError(err)
+// // Send the replied status off to the fedi worker to be further processed.
+// err = testStructs.Processor.Workers().ProcessFromFediAPI(context.Background(), &messages.FromFediAPI{
+// APObjectType: ap.ObjectNote,
+// APActivityType: ap.ActivityCreate,
+// APObject: replyingStatusable,
+// Receiving: repliedAccount,
+// Requesting: replyingAccount,
+// })
+// suite.NoError(err)
- // side effects should be triggered
- // 1. status should be in the database
- replyingStatus, err := testStructs.State.DB.GetStatusByURI(context.Background(), replyingURI)
- suite.NoError(err)
+// // side effects should be triggered
+// // 1. status should be in the database
+// replyingStatus, err := testStructs.State.DB.GetStatusByURI(context.Background(), replyingURI)
+// suite.NoError(err)
- // 2. a notification should exist for the mention
- var notif gtsmodel.Notification
- err = testStructs.State.DB.GetWhere(context.Background(), []db.Where{
- {Key: "status_id", Value: replyingStatus.ID},
- }, ¬if)
- suite.NoError(err)
- suite.Equal(gtsmodel.NotificationMention, notif.NotificationType)
- suite.Equal(replyingStatus.InReplyToAccountID, notif.TargetAccountID)
- suite.Equal(replyingStatus.AccountID, notif.OriginAccountID)
- suite.Equal(replyingStatus.ID, notif.StatusID)
- suite.False(*notif.Read)
+// // 2. a notification should exist for the mention
+// var notif gtsmodel.Notification
+// err = testStructs.State.DB.GetWhere(context.Background(), []db.Where{
+// {Key: "status_id", Value: replyingStatus.ID},
+// }, ¬if)
+// suite.NoError(err)
+// suite.Equal(gtsmodel.NotificationMention, notif.NotificationType)
+// suite.Equal(replyingStatus.InReplyToAccountID, notif.TargetAccountID)
+// suite.Equal(replyingStatus.AccountID, notif.OriginAccountID)
+// suite.Equal(replyingStatus.ID, notif.StatusID)
+// suite.False(*notif.Read)
- ctx, _ := context.WithTimeout(context.Background(), time.Second*5)
- msg, ok := wssStream.Recv(ctx)
- suite.True(ok)
+// ctx, _ := context.WithTimeout(context.Background(), time.Second*5)
+// msg, ok := wssStream.Recv(ctx)
+// suite.True(ok)
- suite.Equal(stream.EventTypeNotification, msg.Event)
- suite.NotEmpty(msg.Payload)
- suite.EqualValues([]string{stream.TimelineHome}, msg.Stream)
- notifStreamed := &apimodel.Notification{}
- err = json.Unmarshal([]byte(msg.Payload), notifStreamed)
- suite.NoError(err)
- suite.Equal("mention", notifStreamed.Type)
- suite.Equal(replyingAccount.ID, notifStreamed.Account.ID)
-}
+// suite.Equal(stream.EventTypeNotification, msg.Event)
+// suite.NotEmpty(msg.Payload)
+// suite.EqualValues([]string{stream.TimelineHome}, msg.Stream)
+// notifStreamed := &apimodel.Notification{}
+// err = json.Unmarshal([]byte(msg.Payload), notifStreamed)
+// suite.NoError(err)
+// suite.Equal("mention", notifStreamed.Type)
+// suite.Equal(replyingAccount.ID, notifStreamed.Account.ID)
+// }
func (suite *FromFediAPITestSuite) TestProcessFave() {
testStructs := suite.SetupTestStructs()
diff --git a/internal/typeutils/astointernal.go b/internal/typeutils/astointernal.go
index ba370790a..cb3e320d9 100644
--- a/internal/typeutils/astointernal.go
+++ b/internal/typeutils/astointernal.go
@@ -399,9 +399,6 @@ func (c *Converter) ASStatusToStatus(ctx context.Context, statusable ap.Statusab
// needs to be created for this in go-fed/activity.
// Until this is implemented, assume all true.
status.Federated = util.Ptr(true)
- status.Boostable = util.Ptr(true)
- status.Replyable = util.Ptr(true)
- status.Likeable = util.Ptr(true)
// status.Sensitive
sensitive := ap.ExtractSensitive(statusable)
diff --git a/internal/typeutils/astointernal_test.go b/internal/typeutils/astointernal_test.go
index 2ee2f9607..6c49e332b 100644
--- a/internal/typeutils/astointernal_test.go
+++ b/internal/typeutils/astointernal_test.go
@@ -181,9 +181,6 @@ func (suite *ASToInternalTestSuite) TestParseReplyWithMention() {
suite.Equal(inReplyToStatus.ID, status.InReplyToID)
suite.Equal(inReplyToStatus.URI, status.InReplyToURI)
suite.True(*status.Federated)
- suite.True(*status.Boostable)
- suite.True(*status.Replyable)
- suite.True(*status.Likeable)
suite.Equal(`
@the_mighty_zork nice there it is:
https://social.pixie.town/users/f0x/statuses/106221628567855262/activity
`, status.Content)
suite.Len(status.Mentions, 1)
m1 := status.Mentions[0]
diff --git a/internal/typeutils/internal.go b/internal/typeutils/internal.go
index 095e2b121..76ec6232c 100644
--- a/internal/typeutils/internal.go
+++ b/internal/typeutils/internal.go
@@ -95,9 +95,6 @@ func (c *Converter) StatusToBoost(
BoostOfAccount: target.Account,
Visibility: target.Visibility,
Federated: util.Ptr(*target.Federated),
- Boostable: util.Ptr(*target.Boostable),
- Replyable: util.Ptr(*target.Replyable),
- Likeable: util.Ptr(*target.Likeable),
}
return boost, nil
diff --git a/internal/typeutils/internaltorss_test.go b/internal/typeutils/internaltorss_test.go
index e9848b806..0988b8ecb 100644
--- a/internal/typeutils/internaltorss_test.go
+++ b/internal/typeutils/internaltorss_test.go
@@ -98,9 +98,6 @@ func (suite *InternalToRSSTestSuite) TestStatusToRSSItem3() {
Visibility: gtsmodel.VisibilityDefault,
ActivityStreamsType: ap.ObjectNote,
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
}
item, err := suite.typeconverter.StatusToRSSItem(context.Background(), s)
suite.NoError(err)
diff --git a/test/envparsing.sh b/test/envparsing.sh
index a744717a4..29403011e 100755
--- a/test/envparsing.sh
+++ b/test/envparsing.sh
@@ -43,6 +43,7 @@ EXPECT=$(cat << "EOF"
"follow-request-mem-ratio": 2,
"in-reply-to-ids-mem-ratio": 3,
"instance-mem-ratio": 1,
+ "interaction-approval-mem-ratio": 1,
"list-entry-mem-ratio": 2,
"list-mem-ratio": 1,
"marker-mem-ratio": 0.5,
diff --git a/testrig/testmodels.go b/testrig/testmodels.go
index de6e97142..90c200585 100644
--- a/testrig/testmodels.go
+++ b/testrig/testmodels.go
@@ -1401,9 +1401,6 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Language: "en",
CreatedWithApplicationID: "01F8MGXQRHYF5QPMTMXP78QC2F",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
ActivityStreamsType: ap.ObjectNote,
},
"admin_account_status_2": {
@@ -1427,9 +1424,6 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Language: "en",
CreatedWithApplicationID: "01F8MGXQRHYF5QPMTMXP78QC2F",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
ActivityStreamsType: ap.ObjectNote,
},
"admin_account_status_3": {
@@ -1454,9 +1448,6 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Language: "en",
CreatedWithApplicationID: "01F8MGXQRHYF5QPMTMXP78QC2F",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
ActivityStreamsType: ap.ObjectNote,
},
"admin_account_status_4": {
@@ -1482,9 +1473,6 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Language: "en",
CreatedWithApplicationID: "01F8MGXQRHYF5QPMTMXP78QC2F",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
ActivityStreamsType: ap.ObjectNote,
},
"local_account_1_status_1": {
@@ -1507,9 +1495,6 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Language: "en",
CreatedWithApplicationID: "01F8MGY43H3N2C8EWPR2FPYEXG",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
ActivityStreamsType: ap.ObjectNote,
},
"local_account_1_status_2": {
@@ -1532,9 +1517,6 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Language: "en",
CreatedWithApplicationID: "01F8MGY43H3N2C8EWPR2FPYEXG",
Federated: util.Ptr(false),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
ActivityStreamsType: ap.ObjectNote,
},
"local_account_1_status_3": {
@@ -1557,10 +1539,18 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Language: "en",
CreatedWithApplicationID: "01F8MGY43H3N2C8EWPR2FPYEXG",
Federated: util.Ptr(true),
- Boostable: util.Ptr(false),
- Replyable: util.Ptr(false),
- Likeable: util.Ptr(false),
- ActivityStreamsType: ap.ObjectNote,
+ InteractionPolicy: >smodel.InteractionPolicy{
+ CanLike: gtsmodel.PolicyRules{
+ Always: gtsmodel.PolicyValues{gtsmodel.PolicyValueAuthor},
+ },
+ CanReply: gtsmodel.PolicyRules{
+ Always: gtsmodel.PolicyValues{gtsmodel.PolicyValueAuthor},
+ },
+ CanAnnounce: gtsmodel.PolicyRules{
+ Always: gtsmodel.PolicyValues{gtsmodel.PolicyValueAuthor},
+ },
+ },
+ ActivityStreamsType: ap.ObjectNote,
},
"local_account_1_status_4": {
ID: "01F8MH82FYRXD2RC6108DAJ5HB",
@@ -1583,9 +1573,6 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Language: "en",
CreatedWithApplicationID: "01F8MGY43H3N2C8EWPR2FPYEXG",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
ActivityStreamsType: ap.ObjectNote,
},
"local_account_1_status_5": {
@@ -1609,9 +1596,6 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Language: "en",
CreatedWithApplicationID: "01F8MGY43H3N2C8EWPR2FPYEXG",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
ActivityStreamsType: ap.ObjectNote,
},
"local_account_1_status_6": {
@@ -1635,9 +1619,6 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Language: "en",
CreatedWithApplicationID: "01F8MGY43H3N2C8EWPR2FPYEXG",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
ActivityStreamsType: ap.ActivityQuestion,
PollID: "01HEN2RKT1YTEZ80SA8HGP105F",
},
@@ -1661,9 +1642,6 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Language: "en",
CreatedWithApplicationID: "01F8MGY43H3N2C8EWPR2FPYEXG",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
ActivityStreamsType: ap.ObjectNote,
},
"local_account_2_status_1": {
@@ -1686,9 +1664,6 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Language: "en",
CreatedWithApplicationID: "01F8MGYG9E893WRHW0TAEXR8GJ",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
ActivityStreamsType: ap.ObjectNote,
},
"local_account_2_status_2": {
@@ -1711,17 +1686,25 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Language: "en",
CreatedWithApplicationID: "01F8MGYG9E893WRHW0TAEXR8GJ",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(false),
- Likeable: util.Ptr(true),
- ActivityStreamsType: ap.ObjectNote,
+ InteractionPolicy: >smodel.InteractionPolicy{
+ CanLike: gtsmodel.PolicyRules{
+ Always: gtsmodel.PolicyValues{gtsmodel.PolicyValuePublic},
+ },
+ CanReply: gtsmodel.PolicyRules{
+ Always: gtsmodel.PolicyValues{gtsmodel.PolicyValueAuthor},
+ },
+ CanAnnounce: gtsmodel.PolicyRules{
+ Always: gtsmodel.PolicyValues{gtsmodel.PolicyValuePublic},
+ },
+ },
+ ActivityStreamsType: ap.ObjectNote,
},
"local_account_2_status_3": {
ID: "01F8MHC8VWDRBQR0N1BATDDEM5",
URI: "http://localhost:8080/users/1happyturtle/statuses/01F8MHC8VWDRBQR0N1BATDDEM5",
URL: "http://localhost:8080/@1happyturtle/statuses/01F8MHC8VWDRBQR0N1BATDDEM5",
- Content: "🐢 i don't mind people sharing this one but I don't want likes or replies to it because cba🐢",
- Text: "🐢 i don't mind people sharing this one but I don't want likes or replies to it because cba🐢",
+ Content: "🐢 i don't mind people sharing and liking this one but I want to moderate replies to it 🐢",
+ Text: "🐢 i don't mind people sharing and liking this one but I want to moderate replies to it 🐢",
CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"),
UpdatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"),
Local: util.Ptr(true),
@@ -1730,16 +1713,25 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
InReplyToID: "",
BoostOfID: "",
ThreadID: "01HCWE4P0EW9HBA5WHW97D5YV0",
- ContentWarning: "you won't be able to like or reply to this",
+ ContentWarning: "you won't be able to reply to this without my approval",
Visibility: gtsmodel.VisibilityUnlocked,
Sensitive: util.Ptr(true),
Language: "en",
CreatedWithApplicationID: "01F8MGYG9E893WRHW0TAEXR8GJ",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(false),
- Likeable: util.Ptr(false),
- ActivityStreamsType: ap.ObjectNote,
+ InteractionPolicy: >smodel.InteractionPolicy{
+ CanLike: gtsmodel.PolicyRules{
+ Always: gtsmodel.PolicyValues{gtsmodel.PolicyValuePublic},
+ },
+ CanReply: gtsmodel.PolicyRules{
+ Always: gtsmodel.PolicyValues{gtsmodel.PolicyValueAuthor},
+ WithApproval: gtsmodel.PolicyValues{gtsmodel.PolicyValuePublic},
+ },
+ CanAnnounce: gtsmodel.PolicyRules{
+ Always: gtsmodel.PolicyValues{gtsmodel.PolicyValuePublic},
+ },
+ },
+ ActivityStreamsType: ap.ObjectNote,
},
"local_account_2_status_4": {
ID: "01F8MHCP5P2NWYQ416SBA0XSEV",
@@ -1761,10 +1753,17 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Language: "en",
CreatedWithApplicationID: "01F8MGYG9E893WRHW0TAEXR8GJ",
Federated: util.Ptr(false),
- Boostable: util.Ptr(false),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
-
+ InteractionPolicy: >smodel.InteractionPolicy{
+ CanLike: gtsmodel.PolicyRules{
+ Always: gtsmodel.PolicyValues{gtsmodel.PolicyValuePublic},
+ },
+ CanReply: gtsmodel.PolicyRules{
+ Always: gtsmodel.PolicyValues{gtsmodel.PolicyValuePublic},
+ },
+ CanAnnounce: gtsmodel.PolicyRules{
+ Always: gtsmodel.PolicyValues{gtsmodel.PolicyValueAuthor},
+ },
+ },
ActivityStreamsType: ap.ObjectNote,
},
"local_account_2_status_5": {
@@ -1790,9 +1789,6 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Language: "en",
CreatedWithApplicationID: "01F8MGYG9E893WRHW0TAEXR8GJ",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
ActivityStreamsType: ap.ObjectNote,
},
"local_account_2_status_6": {
@@ -1818,9 +1814,6 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Language: "en",
CreatedWithApplicationID: "01F8MGYG9E893WRHW0TAEXR8GJ",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
ActivityStreamsType: ap.ObjectNote,
},
"local_account_2_status_7": {
@@ -1845,9 +1838,6 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Language: "en",
CreatedWithApplicationID: "01F8MGYG9E893WRHW0TAEXR8GJ",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
ActivityStreamsType: ap.ObjectNote,
},
"local_account_2_status_8": {
@@ -1871,9 +1861,6 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Language: "en",
CreatedWithApplicationID: "01F8MGYG9E893WRHW0TAEXR8GJ",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
ActivityStreamsType: ap.ActivityQuestion,
PollID: "01HEN2QB5NR4NCEHGYC3HN84K6",
},
@@ -1899,9 +1886,6 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Language: "en",
CreatedWithApplicationID: "",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
ActivityStreamsType: ap.ObjectNote,
},
"remote_account_1_status_2": {
@@ -1925,9 +1909,6 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Language: "en",
CreatedWithApplicationID: "",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
ActivityStreamsType: ap.ActivityQuestion,
PollID: "01HEN2R65468ZG657C4ZPHJ4EX",
},
@@ -1952,9 +1933,6 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Language: "en",
CreatedWithApplicationID: "",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
ActivityStreamsType: ap.ActivityQuestion,
PollID: "01HEWV1GW2D49R919NPEDXPTZ5",
},
@@ -1980,9 +1958,6 @@ func NewTestStatuses() map[string]*gtsmodel.Status {
Language: "en",
CreatedWithApplicationID: "",
Federated: util.Ptr(true),
- Boostable: util.Ptr(true),
- Replyable: util.Ptr(true),
- Likeable: util.Ptr(true),
ActivityStreamsType: ap.ObjectNote,
},
}