mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-05-12 22:10:25 +00:00
Federated user activity following: Isolated model changes (#8078)
This PR is part of https://codeberg.org/forgejo/forgejo/pulls/4767 This should not have an outside impact but bring all model changes needed & bring migrations. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8078 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Co-authored-by: Michael Jerger <michael.jerger@meissa-gmbh.de> Co-committed-by: Michael Jerger <michael.jerger@meissa-gmbh.de>
This commit is contained in:
parent
1c0e9d8015
commit
25d596d387
19 changed files with 604 additions and 48 deletions
|
|
@ -442,6 +442,12 @@ func (a *Action) GetIssueContent(ctx context.Context) string {
|
|||
return a.Issue.Content
|
||||
}
|
||||
|
||||
func GetActivityByID(ctx context.Context, id int64) (*Action, error) {
|
||||
var act Action
|
||||
_, err := db.GetEngine(ctx).ID(id).Get(&act)
|
||||
return &act, err
|
||||
}
|
||||
|
||||
// GetFeedsOptions options for retrieving feeds
|
||||
type GetFeedsOptions struct {
|
||||
db.ListOptions
|
||||
|
|
@ -595,13 +601,14 @@ func DeleteOldActions(ctx context.Context, olderThan time.Duration) (err error)
|
|||
}
|
||||
|
||||
// NotifyWatchers creates batch of actions for every watcher.
|
||||
func NotifyWatchers(ctx context.Context, actions ...*Action) error {
|
||||
func NotifyWatchers(ctx context.Context, actions ...*Action) ([]Action, error) {
|
||||
var watchers []*repo_model.Watch
|
||||
var repo *repo_model.Repository
|
||||
var err error
|
||||
var permCode []bool
|
||||
var permIssue []bool
|
||||
var permPR []bool
|
||||
var out []Action
|
||||
|
||||
e := db.GetEngine(ctx)
|
||||
|
||||
|
|
@ -612,14 +619,14 @@ func NotifyWatchers(ctx context.Context, actions ...*Action) error {
|
|||
// Add feeds for user self and all watchers.
|
||||
watchers, err = repo_model.GetWatchers(ctx, act.RepoID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get watchers: %w", err)
|
||||
return nil, fmt.Errorf("get watchers: %w", err)
|
||||
}
|
||||
|
||||
// Be aware that optimizing this correctly into the `GetWatchers` SQL
|
||||
// query is for most cases less performant than doing this.
|
||||
blockedDoerUserIDs, err := user_model.ListBlockedByUsersID(ctx, act.ActUserID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("user_model.ListBlockedByUsersID: %w", err)
|
||||
return nil, fmt.Errorf("user_model.ListBlockedByUsersID: %w", err)
|
||||
}
|
||||
|
||||
if len(blockedDoerUserIDs) > 0 {
|
||||
|
|
@ -634,8 +641,9 @@ func NotifyWatchers(ctx context.Context, actions ...*Action) error {
|
|||
// Add feed for actioner.
|
||||
act.UserID = act.ActUserID
|
||||
if _, err = e.Insert(act); err != nil {
|
||||
return fmt.Errorf("insert new actioner: %w", err)
|
||||
return nil, fmt.Errorf("insert new actioner: %w", err)
|
||||
}
|
||||
out = append(out, *act)
|
||||
|
||||
if repoChanged {
|
||||
act.loadRepo(ctx)
|
||||
|
|
@ -643,7 +651,7 @@ func NotifyWatchers(ctx context.Context, actions ...*Action) error {
|
|||
|
||||
// check repo owner exist.
|
||||
if err := act.Repo.LoadOwner(ctx); err != nil {
|
||||
return fmt.Errorf("can't get repo owner: %w", err)
|
||||
return nil, fmt.Errorf("can't get repo owner: %w", err)
|
||||
}
|
||||
} else if act.Repo == nil {
|
||||
act.Repo = repo
|
||||
|
|
@ -654,7 +662,7 @@ func NotifyWatchers(ctx context.Context, actions ...*Action) error {
|
|||
act.ID = 0
|
||||
act.UserID = act.Repo.Owner.ID
|
||||
if err = db.Insert(ctx, act); err != nil {
|
||||
return fmt.Errorf("insert new actioner: %w", err)
|
||||
return nil, fmt.Errorf("insert new actioner: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -707,26 +715,29 @@ func NotifyWatchers(ctx context.Context, actions ...*Action) error {
|
|||
}
|
||||
|
||||
if err = db.Insert(ctx, act); err != nil {
|
||||
return fmt.Errorf("insert new action: %w", err)
|
||||
return nil, fmt.Errorf("insert new action: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// NotifyWatchersActions creates batch of actions for every watcher.
|
||||
func NotifyWatchersActions(ctx context.Context, acts []*Action) error {
|
||||
func NotifyWatchersActions(ctx context.Context, acts []*Action) ([]Action, error) {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
defer committer.Close()
|
||||
var out []Action
|
||||
for _, act := range acts {
|
||||
if err := NotifyWatchers(ctx, act); err != nil {
|
||||
return err
|
||||
as, err := NotifyWatchers(ctx, act)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, as...)
|
||||
}
|
||||
return committer.Commit()
|
||||
return out, committer.Commit()
|
||||
}
|
||||
|
||||
// DeleteIssueActions delete all actions related with issueID
|
||||
|
|
|
|||
|
|
@ -197,7 +197,8 @@ func TestNotifyWatchers(t *testing.T) {
|
|||
RepoID: 1,
|
||||
OpType: activities_model.ActionStarRepo,
|
||||
}
|
||||
require.NoError(t, activities_model.NotifyWatchers(db.DefaultContext, action))
|
||||
_, err := activities_model.NotifyWatchers(db.DefaultContext, action)
|
||||
require.NoError(t, err)
|
||||
|
||||
// One watchers are inactive, thus action is only created for user 8, 1, 4, 11
|
||||
unittest.AssertExistsAndLoadBean(t, &activities_model.Action{
|
||||
|
|
|
|||
106
models/activities/federated_user_activity.go
Normal file
106
models/activities/federated_user_activity.go
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package activities
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
user_model "forgejo.org/models/user"
|
||||
"forgejo.org/modules/json"
|
||||
"forgejo.org/modules/log"
|
||||
"forgejo.org/modules/timeutil"
|
||||
"forgejo.org/modules/validation"
|
||||
|
||||
ap "github.com/go-ap/activitypub"
|
||||
)
|
||||
|
||||
type FederatedUserActivity struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UserID int64 `xorm:"NOT NULL"`
|
||||
ActorID int64
|
||||
ActorURI string
|
||||
Actor *user_model.User `xorm:"-"` // transient
|
||||
NoteContent string `xorm:"TEXT"`
|
||||
NoteURL string `xorm:"VARCHAR(255)"`
|
||||
OriginalNote string `xorm:"TEXT"`
|
||||
Created timeutil.TimeStamp `xorm:"created"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(FederatedUserActivity))
|
||||
}
|
||||
|
||||
func NewFederatedUserActivity(userID, actorID int64, actorURI, noteContent, noteURL string, originalNote ap.Activity) (FederatedUserActivity, error) {
|
||||
jsonString, err := json.Marshal(originalNote)
|
||||
if err != nil {
|
||||
return FederatedUserActivity{}, err
|
||||
}
|
||||
result := FederatedUserActivity{
|
||||
UserID: userID,
|
||||
ActorID: actorID,
|
||||
ActorURI: actorURI,
|
||||
NoteContent: noteContent,
|
||||
NoteURL: noteURL,
|
||||
OriginalNote: string(jsonString),
|
||||
}
|
||||
if valid, err := validation.IsValid(result); !valid {
|
||||
return FederatedUserActivity{}, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (federatedUserActivity FederatedUserActivity) Validate() []string {
|
||||
var result []string
|
||||
result = append(result, validation.ValidateNotEmpty(federatedUserActivity.UserID, "UserID")...)
|
||||
result = append(result, validation.ValidateNotEmpty(federatedUserActivity.ActorID, "ActorID")...)
|
||||
result = append(result, validation.ValidateNotEmpty(federatedUserActivity.ActorURI, "ActorURI")...)
|
||||
result = append(result, validation.ValidateNotEmpty(federatedUserActivity.NoteContent, "NoteContent")...)
|
||||
result = append(result, validation.ValidateNotEmpty(federatedUserActivity.NoteURL, "NoteURL")...)
|
||||
result = append(result, validation.ValidateNotEmpty(federatedUserActivity.OriginalNote, "OriginalNote")...)
|
||||
return result
|
||||
}
|
||||
|
||||
func CreateUserActivity(ctx context.Context, federatedUserActivity *FederatedUserActivity) error {
|
||||
if valid, err := validation.IsValid(federatedUserActivity); !valid {
|
||||
return err
|
||||
}
|
||||
_, err := db.GetEngine(ctx).Insert(federatedUserActivity)
|
||||
return err
|
||||
}
|
||||
|
||||
type GetFollowingFeedsOptions struct {
|
||||
db.ListOptions
|
||||
}
|
||||
|
||||
func GetFollowingFeeds(ctx context.Context, actorID int64, opts GetFollowingFeedsOptions) ([]*FederatedUserActivity, int64, error) {
|
||||
log.Debug("user_id = %s", actorID)
|
||||
sess := db.GetEngine(ctx).Where("user_id = ?", actorID)
|
||||
opts.SetDefaultValues()
|
||||
sess = db.SetSessionPagination(sess, &opts)
|
||||
|
||||
actions := make([]*FederatedUserActivity, 0, opts.PageSize)
|
||||
count, err := sess.FindAndCount(&actions)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("FindAndCount: %w", err)
|
||||
}
|
||||
for _, act := range actions {
|
||||
if err := act.loadActor(ctx); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
}
|
||||
return actions, count, err
|
||||
}
|
||||
|
||||
func (federatedUserActivity *FederatedUserActivity) loadActor(ctx context.Context) error {
|
||||
log.Debug("for activity %s", federatedUserActivity)
|
||||
actorUser, _, err := user_model.GetFederatedUserByUserID(ctx, federatedUserActivity.ActorID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
federatedUserActivity.Actor = actorUser
|
||||
|
||||
return nil
|
||||
}
|
||||
24
models/activities/federated_user_activity_test.go
Normal file
24
models/activities/federated_user_activity_test.go
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package activities
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"forgejo.org/modules/validation"
|
||||
)
|
||||
|
||||
func Test_FederatedUserActivityValidation(t *testing.T) {
|
||||
sut := FederatedUserActivity{}
|
||||
sut.UserID = 13
|
||||
sut.ActorID = 33
|
||||
sut.ActorURI = "33"
|
||||
sut.NoteContent = "Any content!"
|
||||
sut.NoteURL = "https://example.org/note/17"
|
||||
sut.OriginalNote = "federatedUserActivityNote-17"
|
||||
|
||||
if res, _ := validation.IsValid(sut); !res {
|
||||
t.Errorf("sut expected to be valid: %v\n", sut.Validate())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue