diff options
Diffstat (limited to 'internal/processing')
| -rw-r--r-- | internal/processing/account/account.go | 3 | ||||
| -rw-r--r-- | internal/processing/account/getbookmarks.go | 88 | ||||
| -rw-r--r-- | internal/processing/bookmark.go | 31 | ||||
| -rw-r--r-- | internal/processing/processor.go | 7 | ||||
| -rw-r--r-- | internal/processing/status.go | 8 | ||||
| -rw-r--r-- | internal/processing/status/bookmark.go | 86 | ||||
| -rw-r--r-- | internal/processing/status/bookmark_test.go | 48 | ||||
| -rw-r--r-- | internal/processing/status/status.go | 4 | ||||
| -rw-r--r-- | internal/processing/status/unbookmark.go | 69 | ||||
| -rw-r--r-- | internal/processing/status/unbookmark_test.go | 54 | 
10 files changed, 398 insertions, 0 deletions
diff --git a/internal/processing/account/account.go b/internal/processing/account/account.go index 3eccbc27d..4c29621ed 100644 --- a/internal/processing/account/account.go +++ b/internal/processing/account/account.go @@ -64,6 +64,9 @@ type Processor interface {  	// WebStatusesGet fetches a number of statuses (in descending order) from the given account. It selects only  	// statuses which are suitable for showing on the public web profile of an account.  	WebStatusesGet(ctx context.Context, targetAccountID string, maxID string) (*apimodel.PageableResponse, gtserror.WithCode) +	// StatusesGet fetches a number of statuses (in time descending order) from the given account, filtered by visibility for +	// the account given in authed. +	BookmarksGet(ctx context.Context, requestingAccount *gtsmodel.Account, limit int, maxID string, minID string) (*apimodel.PageableResponse, gtserror.WithCode)  	// FollowersGet fetches a list of the target account's followers.  	FollowersGet(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) ([]apimodel.Account, gtserror.WithCode)  	// FollowingGet fetches a list of the accounts that target account is following. diff --git a/internal/processing/account/getbookmarks.go b/internal/processing/account/getbookmarks.go new file mode 100644 index 000000000..0b15806c3 --- /dev/null +++ b/internal/processing/account/getbookmarks.go @@ -0,0 +1,88 @@ +/* +   GoToSocial +   Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org + +   This program is free software: you can redistribute it and/or modify +   it under the terms of the GNU Affero General Public License as published by +   the Free Software Foundation, either version 3 of the License, or +   (at your option) any later version. + +   This program is distributed in the hope that it will be useful, +   but WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +   GNU Affero General Public License for more details. + +   You should have received a copy of the GNU Affero General Public License +   along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +package account + +import ( +	"context" +	"fmt" + +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	"github.com/superseriousbusiness/gotosocial/internal/gtserror" +	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +	"github.com/superseriousbusiness/gotosocial/internal/util" +) + +func (p *processor) BookmarksGet(ctx context.Context, requestingAccount *gtsmodel.Account, limit int, maxID string, minID string) (*apimodel.PageableResponse, gtserror.WithCode) { +	if requestingAccount == nil { +		return nil, gtserror.NewErrorForbidden(fmt.Errorf("cannot retrieve bookmarks without a requesting account")) +	} + +	bookmarks, err := p.db.GetBookmarks(ctx, requestingAccount.ID, limit, maxID, minID) +	if err != nil { +		return nil, gtserror.NewErrorInternalError(err) +	} + +	count := len(bookmarks) +	filtered := make([]*gtsmodel.Status, 0, len(bookmarks)) +	nextMaxIDValue := "" +	prevMinIDValue := "" +	for i, b := range bookmarks { +		s, err := p.db.GetStatusByID(ctx, b.StatusID) +		if err != nil { +			return nil, gtserror.NewErrorInternalError(err) +		} + +		visible, err := p.filter.StatusVisible(ctx, s, requestingAccount) +		if err == nil && visible { +			if i == count-1 { +				nextMaxIDValue = b.ID +			} + +			if i == 0 { +				prevMinIDValue = b.ID +			} + +			filtered = append(filtered, s) +		} +	} + +	count = len(filtered) + +	if count == 0 { +		return util.EmptyPageableResponse(), nil +	} + +	items := []interface{}{} +	for _, s := range filtered { +		item, err := p.tc.StatusToAPIStatus(ctx, s, requestingAccount) +		if err != nil { +			return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status to api: %s", err)) +		} +		items = append(items, item) +	} + +	return util.PackagePageableResponse(util.PageableResponseParams{ +		Items:            items, +		Path:             "/api/v1/bookmarks", +		NextMaxIDValue:   nextMaxIDValue, +		PrevMinIDValue:   prevMinIDValue, +		Limit:            limit, +		ExtraQueryParams: []string{}, +	}) +} diff --git a/internal/processing/bookmark.go b/internal/processing/bookmark.go new file mode 100644 index 000000000..c3bcfca4a --- /dev/null +++ b/internal/processing/bookmark.go @@ -0,0 +1,31 @@ +/* +   GoToSocial +   Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org + +   This program is free software: you can redistribute it and/or modify +   it under the terms of the GNU Affero General Public License as published by +   the Free Software Foundation, either version 3 of the License, or +   (at your option) any later version. + +   This program is distributed in the hope that it will be useful, +   but WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +   GNU Affero General Public License for more details. + +   You should have received a copy of the GNU Affero General Public License +   along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +package processing + +import ( +	"context" + +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	"github.com/superseriousbusiness/gotosocial/internal/gtserror" +	"github.com/superseriousbusiness/gotosocial/internal/oauth" +) + +func (p *processor) BookmarksGet(ctx context.Context, authed *oauth.Auth, maxID string, minID string, limit int) (*apimodel.PageableResponse, gtserror.WithCode) { +	return p.accountProcessor.BookmarksGet(ctx, authed.Account, limit, maxID, minID) +} diff --git a/internal/processing/processor.go b/internal/processing/processor.go index 22fb7b2b7..88b0f5594 100644 --- a/internal/processing/processor.go +++ b/internal/processing/processor.go @@ -146,6 +146,9 @@ type Processor interface {  	// CustomEmojisGet returns an array of info about the custom emojis on this server  	CustomEmojisGet(ctx context.Context) ([]*apimodel.Emoji, gtserror.WithCode) +	// BookmarksGet returns a pageable response of statuses that have been bookmarked +	BookmarksGet(ctx context.Context, authed *oauth.Auth, maxID string, minID string, limit int) (*apimodel.PageableResponse, gtserror.WithCode) +  	// FileGet handles the fetching of a media attachment file via the fileserver.  	FileGet(ctx context.Context, authed *oauth.Auth, form *apimodel.GetContentRequestForm) (*apimodel.Content, gtserror.WithCode) @@ -202,6 +205,10 @@ type Processor interface {  	StatusUnfave(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode)  	// StatusGetContext returns the context (previous and following posts) from the given status ID  	StatusGetContext(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Context, gtserror.WithCode) +	// StatusBookmark process a bookmark for a status +	StatusBookmark(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode) +	// StatusUnbookmark removes a bookmark for a status +	StatusUnbookmark(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode)  	// HomeTimelineGet returns statuses from the home timeline, with the given filters/parameters.  	HomeTimelineGet(ctx context.Context, authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.PageableResponse, gtserror.WithCode) diff --git a/internal/processing/status.go b/internal/processing/status.go index b2f222971..808079c97 100644 --- a/internal/processing/status.go +++ b/internal/processing/status.go @@ -65,3 +65,11 @@ func (p *processor) StatusUnfave(ctx context.Context, authed *oauth.Auth, target  func (p *processor) StatusGetContext(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Context, gtserror.WithCode) {  	return p.statusProcessor.Context(ctx, authed.Account, targetStatusID)  } + +func (p *processor) StatusBookmark(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { +	return p.statusProcessor.Bookmark(ctx, authed.Account, targetStatusID) +} + +func (p *processor) StatusUnbookmark(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { +	return p.statusProcessor.Unbookmark(ctx, authed.Account, targetStatusID) +} diff --git a/internal/processing/status/bookmark.go b/internal/processing/status/bookmark.go new file mode 100644 index 000000000..0f13fbacf --- /dev/null +++ b/internal/processing/status/bookmark.go @@ -0,0 +1,86 @@ +/* +   GoToSocial +   Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org + +   This program is free software: you can redistribute it and/or modify +   it under the terms of the GNU Affero General Public License as published by +   the Free Software Foundation, either version 3 of the License, or +   (at your option) any later version. + +   This program is distributed in the hope that it will be useful, +   but WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +   GNU Affero General Public License for more details. + +   You should have received a copy of the GNU Affero General Public License +   along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +package status + +import ( +	"context" +	"errors" +	"fmt" + +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	"github.com/superseriousbusiness/gotosocial/internal/db" +	"github.com/superseriousbusiness/gotosocial/internal/gtserror" +	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +	"github.com/superseriousbusiness/gotosocial/internal/id" +) + +func (p *processor) Bookmark(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { +	targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID) +	if err != nil { +		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) +	} +	if targetStatus.Account == nil { +		return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID)) +	} +	visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount) +	if err != nil { +		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)) +	} +	if !visible { +		return nil, gtserror.NewErrorNotFound(errors.New("status is not visible")) +	} + +	// first check if the status is already bookmarked, if so we don't need to do anything +	newBookmark := true +	gtsBookmark := >smodel.StatusBookmark{} +	if err := p.db.GetWhere(ctx, []db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: requestingAccount.ID}}, gtsBookmark); err == nil { +		// we already have a bookmark for this status +		newBookmark = false +	} + +	if newBookmark { +		thisBookmarkID, err := id.NewULID() +		if err != nil { +			return nil, gtserror.NewErrorInternalError(err) +		} + +		// we need to create a new bookmark in the database +		gtsBookmark := >smodel.StatusBookmark{ +			ID:              thisBookmarkID, +			AccountID:       requestingAccount.ID, +			Account:         requestingAccount, +			TargetAccountID: targetStatus.AccountID, +			TargetAccount:   targetStatus.Account, +			StatusID:        targetStatus.ID, +			Status:          targetStatus, +		} + +		if err := p.db.Put(ctx, gtsBookmark); err != nil { +			return nil, gtserror.NewErrorInternalError(fmt.Errorf("error putting bookmark in database: %s", err)) +		} +	} + +	// return the apidon representation of the target status +	apiStatus, err := p.tc.StatusToAPIStatus(ctx, targetStatus, requestingAccount) +	if err != nil { +		return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)) +	} + +	return apiStatus, nil +} diff --git a/internal/processing/status/bookmark_test.go b/internal/processing/status/bookmark_test.go new file mode 100644 index 000000000..ed1f9c774 --- /dev/null +++ b/internal/processing/status/bookmark_test.go @@ -0,0 +1,48 @@ +/* +   GoToSocial +   Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org + +   This program is free software: you can redistribute it and/or modify +   it under the terms of the GNU Affero General Public License as published by +   the Free Software Foundation, either version 3 of the License, or +   (at your option) any later version. + +   This program is distributed in the hope that it will be useful, +   but WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +   GNU Affero General Public License for more details. + +   You should have received a copy of the GNU Affero General Public License +   along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +package status_test + +import ( +	"context" +	"testing" + +	"github.com/stretchr/testify/suite" +) + +type StatusBookmarkTestSuite struct { +	StatusStandardTestSuite +} + +func (suite *StatusBookmarkTestSuite) TestBookmark() { +	ctx := context.Background() + +	// bookmark a status +	bookmarkingAccount1 := suite.testAccounts["local_account_1"] +	targetStatus1 := suite.testStatuses["admin_account_status_1"] + +	bookmark1, err := suite.status.Bookmark(ctx, bookmarkingAccount1, targetStatus1.ID) +	suite.NoError(err) +	suite.NotNil(bookmark1) +	suite.True(bookmark1.Bookmarked) +	suite.Equal(targetStatus1.ID, bookmark1.ID) +} + +func TestStatusBookmarkTestSuite(t *testing.T) { +	suite.Run(t, new(StatusBookmarkTestSuite)) +} diff --git a/internal/processing/status/status.go b/internal/processing/status/status.go index c63769c76..d31b69b38 100644 --- a/internal/processing/status/status.go +++ b/internal/processing/status/status.go @@ -54,6 +54,10 @@ type Processor interface {  	Unfave(ctx context.Context, account *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode)  	// Context returns the context (previous and following posts) from the given status ID  	Context(ctx context.Context, account *gtsmodel.Account, targetStatusID string) (*apimodel.Context, gtserror.WithCode) +	// Bookmarks a status +	Bookmark(ctx context.Context, account *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) +	// Removes a bookmark for a status +	Unbookmark(ctx context.Context, account *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode)  	/*  		PROCESSING UTILS diff --git a/internal/processing/status/unbookmark.go b/internal/processing/status/unbookmark.go new file mode 100644 index 000000000..ef5962495 --- /dev/null +++ b/internal/processing/status/unbookmark.go @@ -0,0 +1,69 @@ +/* +   GoToSocial +   Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org + +   This program is free software: you can redistribute it and/or modify +   it under the terms of the GNU Affero General Public License as published by +   the Free Software Foundation, either version 3 of the License, or +   (at your option) any later version. + +   This program is distributed in the hope that it will be useful, +   but WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +   GNU Affero General Public License for more details. + +   You should have received a copy of the GNU Affero General Public License +   along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +package status + +import ( +	"context" +	"errors" +	"fmt" + +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	"github.com/superseriousbusiness/gotosocial/internal/db" +	"github.com/superseriousbusiness/gotosocial/internal/gtserror" +	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +func (p *processor) Unbookmark(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { +	targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID) +	if err != nil { +		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err)) +	} +	if targetStatus.Account == nil { +		return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID)) +	} +	visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount) +	if err != nil { +		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err)) +	} +	if !visible { +		return nil, gtserror.NewErrorNotFound(errors.New("status is not visible")) +	} + +	// first check if the status is already bookmarked +	toUnbookmark := false +	gtsBookmark := >smodel.StatusBookmark{} +	if err := p.db.GetWhere(ctx, []db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: requestingAccount.ID}}, gtsBookmark); err == nil { +		// we already have a bookmark for this status +		toUnbookmark = true +	} + +	if toUnbookmark { +		if err := p.db.DeleteWhere(ctx, []db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: requestingAccount.ID}}, gtsBookmark); err != nil { +			return nil, gtserror.NewErrorInternalError(fmt.Errorf("error unfaveing status: %s", err)) +		} +	} + +	// return the apidon representation of the target status +	apiStatus, err := p.tc.StatusToAPIStatus(ctx, targetStatus, requestingAccount) +	if err != nil { +		return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err)) +	} + +	return apiStatus, nil +} diff --git a/internal/processing/status/unbookmark_test.go b/internal/processing/status/unbookmark_test.go new file mode 100644 index 000000000..38a60d776 --- /dev/null +++ b/internal/processing/status/unbookmark_test.go @@ -0,0 +1,54 @@ +/* +   GoToSocial +   Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org + +   This program is free software: you can redistribute it and/or modify +   it under the terms of the GNU Affero General Public License as published by +   the Free Software Foundation, either version 3 of the License, or +   (at your option) any later version. + +   This program is distributed in the hope that it will be useful, +   but WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +   GNU Affero General Public License for more details. + +   You should have received a copy of the GNU Affero General Public License +   along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +package status_test + +import ( +	"context" +	"testing" + +	"github.com/stretchr/testify/suite" +) + +type StatusUnbookmarkTestSuite struct { +	StatusStandardTestSuite +} + +func (suite *StatusUnbookmarkTestSuite) TestUnbookmark() { +	ctx := context.Background() + +	// bookmark a status +	bookmarkingAccount1 := suite.testAccounts["local_account_1"] +	targetStatus1 := suite.testStatuses["admin_account_status_1"] + +	bookmark1, err := suite.status.Bookmark(ctx, bookmarkingAccount1, targetStatus1.ID) +	suite.NoError(err) +	suite.NotNil(bookmark1) +	suite.True(bookmark1.Bookmarked) +	suite.Equal(targetStatus1.ID, bookmark1.ID) + +	bookmark2, err := suite.status.Unbookmark(ctx, bookmarkingAccount1, targetStatus1.ID) +	suite.NoError(err) +	suite.NotNil(bookmark2) +	suite.False(bookmark2.Bookmarked) +	suite.Equal(targetStatus1.ID, bookmark1.ID) +} + +func TestStatusUnbookmarkTestSuite(t *testing.T) { +	suite.Run(t, new(StatusUnbookmarkTestSuite)) +}  | 
