Skip to content

Commit

Permalink
[chore] introduce interaction policy gts models
Browse files Browse the repository at this point in the history
  • Loading branch information
tsmethurst committed Jul 6, 2024
1 parent 49009fb commit 6e4dc8b
Show file tree
Hide file tree
Showing 46 changed files with 1,296 additions and 552 deletions.
15 changes: 0 additions & 15 deletions docs/api/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7987,21 +7987,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:
Expand Down
73 changes: 37 additions & 36 deletions internal/api/client/statuses/statusboost_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

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")

// 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)

// 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))
}
// 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"]

// // 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,
// },
// }

// suite.statusModule.StatusBoostPOSTHandler(ctx)

// // 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))
// }

// try to boost a status that's not visible to the user
func (suite *StatusBoostTestSuite) TestPostNotVisible() {
Expand Down
18 changes: 0 additions & 18 deletions internal/api/client/statuses/statuscreate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 0 additions & 3 deletions internal/api/client/statuses/statuscreate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
73 changes: 37 additions & 36 deletions internal/api/client/statuses/statusfave_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

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")

// 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)

// 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))
}
// 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

// // 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,
// },
// }

// suite.statusModule.StatusFavePOSTHandler(ctx)

// // 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))
// }

func TestStatusFaveTestSuite(t *testing.T) {
suite.Run(t, new(StatusFaveTestSuite))
Expand Down
3 changes: 0 additions & 3 deletions internal/api/client/statuses/statuspin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 0 additions & 6 deletions internal/api/model/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions internal/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ func (c *Caches) Init() {
c.initFollowRequestIDs()
c.initInReplyToIDs()
c.initInstance()
c.initInteractionApproval()
c.initList()
c.initListEntry()
c.initMarker()
Expand Down Expand Up @@ -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)
Expand Down
37 changes: 37 additions & 0 deletions internal/cache/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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},
},
Expand Down
17 changes: 14 additions & 3 deletions internal/cache/size.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ func totalOfRatios() float64 {
config.GetCacheFollowRequestMemRatio() +
config.GetCacheFollowRequestIDsMemRatio() +
config.GetCacheInstanceMemRatio() +
config.GetCacheInteractionApprovalMemRatio() +
config.GetCacheInReplyToIDsMemRatio() +
config.GetCacheListMemRatio() +
config.GetCacheListEntryMemRatio() +
Expand Down Expand Up @@ -425,6 +426,19 @@ func sizeofInstance() uintptr {
}))
}

func sizeofInteractionApproval() uintptr {
return uintptr(size.Of(&gtsmodel.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(&gtsmodel.List{
ID: exampleID,
Expand Down Expand Up @@ -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,
}))
}
Expand Down
Loading

0 comments on commit 6e4dc8b

Please sign in to comment.