mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 07:12:26 -05:00 
			
		
		
		
	[bugfix] support endless polls, and misskey's' method of inferring expiry in closed polls (#2349)
This commit is contained in:
		
					parent
					
						
							
								ba9d6b467a
							
						
					
				
			
			
				commit
				
					
						deaea100c3
					
				
			
		
					 16 changed files with 212 additions and 52 deletions
				
			
		|  | @ -1125,26 +1125,31 @@ func ExtractPoll(poll Pollable) (*gtsmodel.Poll, error) { | ||||||
| 
 | 
 | ||||||
| 	// Check if counts have been hidden from us. | 	// Check if counts have been hidden from us. | ||||||
| 	hideCounts := len(options) != len(votes) | 	hideCounts := len(options) != len(votes) | ||||||
|  | 
 | ||||||
| 	if hideCounts { | 	if hideCounts { | ||||||
| 
 | 		// Simply provide zeroed slice. | ||||||
| 		// Zero out all votes. | 		votes = make([]int, len(options)) | ||||||
| 		for i := range votes { |  | ||||||
| 			votes[i] = 0 |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Extract the poll end time. | 	// Extract the poll closed time, | ||||||
| 	endTime := GetEndTime(poll) | 	// it's okay for this to be zero. | ||||||
| 	if endTime.IsZero() { |  | ||||||
| 		return nil, errors.New("no poll end time specified") |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Extract the poll closed time. |  | ||||||
| 	closedSlice := GetClosed(poll) | 	closedSlice := GetClosed(poll) | ||||||
| 	if len(closedSlice) == 1 { | 	if len(closedSlice) == 1 { | ||||||
| 		closed = closedSlice[0] | 		closed = closedSlice[0] | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	// Extract the poll end time, again | ||||||
|  | 	// this isn't necessarily set as some | ||||||
|  | 	// servers support "endless" polls. | ||||||
|  | 	endTime := GetEndTime(poll) | ||||||
|  | 
 | ||||||
|  | 	if endTime.IsZero() && !closed.IsZero() { | ||||||
|  | 		// If no endTime is provided, but the | ||||||
|  | 		// poll is marked as closed, infer the | ||||||
|  | 		// endTime from the closed time. | ||||||
|  | 		endTime = closed | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// Extract the number of voters. | 	// Extract the number of voters. | ||||||
| 	voters := GetVotersCount(poll) | 	voters := GetVotersCount(poll) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -180,7 +180,7 @@ func (suite *ReportsGetTestSuite) TestReportsGetAll() { | ||||||
|         "header_static": "http://localhost:8080/assets/default_header.png", |         "header_static": "http://localhost:8080/assets/default_header.png", | ||||||
|         "followers_count": 0, |         "followers_count": 0, | ||||||
|         "following_count": 0, |         "following_count": 0, | ||||||
|         "statuses_count": 2, |         "statuses_count": 3, | ||||||
|         "last_status_at": "2021-09-11T09:40:37.000Z", |         "last_status_at": "2021-09-11T09:40:37.000Z", | ||||||
|         "emojis": [], |         "emojis": [], | ||||||
|         "fields": [] |         "fields": [] | ||||||
|  | @ -438,7 +438,7 @@ func (suite *ReportsGetTestSuite) TestReportsGetAll() { | ||||||
|         "header_static": "http://localhost:8080/assets/default_header.png", |         "header_static": "http://localhost:8080/assets/default_header.png", | ||||||
|         "followers_count": 0, |         "followers_count": 0, | ||||||
|         "following_count": 0, |         "following_count": 0, | ||||||
|         "statuses_count": 2, |         "statuses_count": 3, | ||||||
|         "last_status_at": "2021-09-11T09:40:37.000Z", |         "last_status_at": "2021-09-11T09:40:37.000Z", | ||||||
|         "emojis": [], |         "emojis": [], | ||||||
|         "fields": [] |         "fields": [] | ||||||
|  | @ -485,7 +485,7 @@ func (suite *ReportsGetTestSuite) TestReportsGetAll() { | ||||||
|           "header_static": "http://localhost:8080/assets/default_header.png", |           "header_static": "http://localhost:8080/assets/default_header.png", | ||||||
|           "followers_count": 0, |           "followers_count": 0, | ||||||
|           "following_count": 0, |           "following_count": 0, | ||||||
|           "statuses_count": 2, |           "statuses_count": 3, | ||||||
|           "last_status_at": "2021-09-11T09:40:37.000Z", |           "last_status_at": "2021-09-11T09:40:37.000Z", | ||||||
|           "emojis": [], |           "emojis": [], | ||||||
|           "fields": [] |           "fields": [] | ||||||
|  | @ -659,7 +659,7 @@ func (suite *ReportsGetTestSuite) TestReportsGetCreatedByAccount() { | ||||||
|         "header_static": "http://localhost:8080/assets/default_header.png", |         "header_static": "http://localhost:8080/assets/default_header.png", | ||||||
|         "followers_count": 0, |         "followers_count": 0, | ||||||
|         "following_count": 0, |         "following_count": 0, | ||||||
|         "statuses_count": 2, |         "statuses_count": 3, | ||||||
|         "last_status_at": "2021-09-11T09:40:37.000Z", |         "last_status_at": "2021-09-11T09:40:37.000Z", | ||||||
|         "emojis": [], |         "emojis": [], | ||||||
|         "fields": [] |         "fields": [] | ||||||
|  | @ -706,7 +706,7 @@ func (suite *ReportsGetTestSuite) TestReportsGetCreatedByAccount() { | ||||||
|           "header_static": "http://localhost:8080/assets/default_header.png", |           "header_static": "http://localhost:8080/assets/default_header.png", | ||||||
|           "followers_count": 0, |           "followers_count": 0, | ||||||
|           "following_count": 0, |           "following_count": 0, | ||||||
|           "statuses_count": 2, |           "statuses_count": 3, | ||||||
|           "last_status_at": "2021-09-11T09:40:37.000Z", |           "last_status_at": "2021-09-11T09:40:37.000Z", | ||||||
|           "emojis": [], |           "emojis": [], | ||||||
|           "fields": [] |           "fields": [] | ||||||
|  | @ -880,7 +880,7 @@ func (suite *ReportsGetTestSuite) TestReportsGetTargetAccount() { | ||||||
|         "header_static": "http://localhost:8080/assets/default_header.png", |         "header_static": "http://localhost:8080/assets/default_header.png", | ||||||
|         "followers_count": 0, |         "followers_count": 0, | ||||||
|         "following_count": 0, |         "following_count": 0, | ||||||
|         "statuses_count": 2, |         "statuses_count": 3, | ||||||
|         "last_status_at": "2021-09-11T09:40:37.000Z", |         "last_status_at": "2021-09-11T09:40:37.000Z", | ||||||
|         "emojis": [], |         "emojis": [], | ||||||
|         "fields": [] |         "fields": [] | ||||||
|  | @ -927,7 +927,7 @@ func (suite *ReportsGetTestSuite) TestReportsGetTargetAccount() { | ||||||
|           "header_static": "http://localhost:8080/assets/default_header.png", |           "header_static": "http://localhost:8080/assets/default_header.png", | ||||||
|           "followers_count": 0, |           "followers_count": 0, | ||||||
|           "following_count": 0, |           "following_count": 0, | ||||||
|           "statuses_count": 2, |           "statuses_count": 3, | ||||||
|           "last_status_at": "2021-09-11T09:40:37.000Z", |           "last_status_at": "2021-09-11T09:40:37.000Z", | ||||||
|           "emojis": [], |           "emojis": [], | ||||||
|           "fields": [] |           "fields": [] | ||||||
|  |  | ||||||
|  | @ -129,7 +129,7 @@ func (suite *ReportGetTestSuite) TestGetReport1() { | ||||||
|     "header_static": "http://localhost:8080/assets/default_header.png", |     "header_static": "http://localhost:8080/assets/default_header.png", | ||||||
|     "followers_count": 0, |     "followers_count": 0, | ||||||
|     "following_count": 0, |     "following_count": 0, | ||||||
|     "statuses_count": 2, |     "statuses_count": 3, | ||||||
|     "last_status_at": "2021-09-11T09:40:37.000Z", |     "last_status_at": "2021-09-11T09:40:37.000Z", | ||||||
|     "emojis": [], |     "emojis": [], | ||||||
|     "fields": [] |     "fields": [] | ||||||
|  |  | ||||||
|  | @ -154,7 +154,7 @@ func (suite *ReportsGetTestSuite) TestGetReports() { | ||||||
|       "header_static": "http://localhost:8080/assets/default_header.png", |       "header_static": "http://localhost:8080/assets/default_header.png", | ||||||
|       "followers_count": 0, |       "followers_count": 0, | ||||||
|       "following_count": 0, |       "following_count": 0, | ||||||
|       "statuses_count": 2, |       "statuses_count": 3, | ||||||
|       "last_status_at": "2021-09-11T09:40:37.000Z", |       "last_status_at": "2021-09-11T09:40:37.000Z", | ||||||
|       "emojis": [], |       "emojis": [], | ||||||
|       "fields": [] |       "fields": [] | ||||||
|  | @ -244,7 +244,7 @@ func (suite *ReportsGetTestSuite) TestGetReports4() { | ||||||
|       "header_static": "http://localhost:8080/assets/default_header.png", |       "header_static": "http://localhost:8080/assets/default_header.png", | ||||||
|       "followers_count": 0, |       "followers_count": 0, | ||||||
|       "following_count": 0, |       "following_count": 0, | ||||||
|       "statuses_count": 2, |       "statuses_count": 3, | ||||||
|       "last_status_at": "2021-09-11T09:40:37.000Z", |       "last_status_at": "2021-09-11T09:40:37.000Z", | ||||||
|       "emojis": [], |       "emojis": [], | ||||||
|       "fields": [] |       "fields": [] | ||||||
|  | @ -318,7 +318,7 @@ func (suite *ReportsGetTestSuite) TestGetReports6() { | ||||||
|       "header_static": "http://localhost:8080/assets/default_header.png", |       "header_static": "http://localhost:8080/assets/default_header.png", | ||||||
|       "followers_count": 0, |       "followers_count": 0, | ||||||
|       "following_count": 0, |       "following_count": 0, | ||||||
|       "statuses_count": 2, |       "statuses_count": 3, | ||||||
|       "last_status_at": "2021-09-11T09:40:37.000Z", |       "last_status_at": "2021-09-11T09:40:37.000Z", | ||||||
|       "emojis": [], |       "emojis": [], | ||||||
|       "fields": [] |       "fields": [] | ||||||
|  | @ -376,7 +376,7 @@ func (suite *ReportsGetTestSuite) TestGetReports7() { | ||||||
|       "header_static": "http://localhost:8080/assets/default_header.png", |       "header_static": "http://localhost:8080/assets/default_header.png", | ||||||
|       "followers_count": 0, |       "followers_count": 0, | ||||||
|       "following_count": 0, |       "following_count": 0, | ||||||
|       "statuses_count": 2, |       "statuses_count": 3, | ||||||
|       "last_status_at": "2021-09-11T09:40:37.000Z", |       "last_status_at": "2021-09-11T09:40:37.000Z", | ||||||
|       "emojis": [], |       "emojis": [], | ||||||
|       "fields": [] |       "fields": [] | ||||||
|  |  | ||||||
|  | @ -121,7 +121,7 @@ func (suite *BasicTestSuite) TestGetAllStatuses() { | ||||||
| 	s := []*gtsmodel.Status{} | 	s := []*gtsmodel.Status{} | ||||||
| 	err := suite.db.GetAll(context.Background(), &s) | 	err := suite.db.GetAll(context.Background(), &s) | ||||||
| 	suite.NoError(err) | 	suite.NoError(err) | ||||||
| 	suite.Len(s, 21) | 	suite.Len(s, 22) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *BasicTestSuite) TestGetAllNotNull() { | func (suite *BasicTestSuite) TestGetAllNotNull() { | ||||||
|  |  | ||||||
|  | @ -53,7 +53,7 @@ func (suite *InstanceTestSuite) TestCountInstanceStatuses() { | ||||||
| func (suite *InstanceTestSuite) TestCountInstanceStatusesRemote() { | func (suite *InstanceTestSuite) TestCountInstanceStatusesRemote() { | ||||||
| 	count, err := suite.db.CountInstanceStatuses(context.Background(), "fossbros-anonymous.io") | 	count, err := suite.db.CountInstanceStatuses(context.Background(), "fossbros-anonymous.io") | ||||||
| 	suite.NoError(err) | 	suite.NoError(err) | ||||||
| 	suite.Equal(2, count) | 	suite.Equal(3, count) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *InstanceTestSuite) TestCountInstanceDomains() { | func (suite *InstanceTestSuite) TestCountInstanceDomains() { | ||||||
|  |  | ||||||
|  | @ -0,0 +1,84 @@ | ||||||
|  | // 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 <http://www.gnu.org/licenses/>. | ||||||
|  | 
 | ||||||
|  | package migrations | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 
 | ||||||
|  | 	"github.com/uptrace/bun" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	up := func(ctx context.Context, db *bun.DB) error { | ||||||
|  | 		return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { | ||||||
|  | 			// Add new poll expiry column WITHOUT | ||||||
|  | 			// the previously set NULL constraint. | ||||||
|  | 			if _, err := tx.NewAddColumn(). | ||||||
|  | 				Table("polls"). | ||||||
|  | 				ColumnExpr("? TIMESTAMPTZ", bun.Ident("expires_at_new")). | ||||||
|  | 				Exec(ctx); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// Copy all data from old to new, | ||||||
|  | 			// this won't cause anyn issues as | ||||||
|  | 			// old column is NOT NULL and it's | ||||||
|  | 			// only the new column that drops | ||||||
|  | 			// this constraint to allow NULL. | ||||||
|  | 			if _, err := tx.NewUpdate(). | ||||||
|  | 				Table("polls"). | ||||||
|  | 				Column("expires_at_new"). | ||||||
|  | 				Set("? = ?", bun.Ident("expires_at_new"), bun.Ident("expires_at")). | ||||||
|  | 				Where("1"). // bun gets angry performing update over all rows | ||||||
|  | 				Exec(ctx); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// Drop the old poll expiry column. | ||||||
|  | 			if _, err := tx.NewDropColumn(). | ||||||
|  | 				Table("polls"). | ||||||
|  | 				ColumnExpr("?", bun.Ident("expires_at")). | ||||||
|  | 				Exec(ctx); err != nil { | ||||||
|  | 				return err | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// Rename the new expiry column | ||||||
|  | 			// to the correct name (of the old). | ||||||
|  | 			if _, err := tx.NewRaw( | ||||||
|  | 				"ALTER TABLE ? RENAME COLUMN ? TO ?", | ||||||
|  | 				bun.Ident("polls"), | ||||||
|  | 				bun.Ident("expires_at_new"), | ||||||
|  | 				bun.Ident("expires_at"), | ||||||
|  | 			).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) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -737,27 +737,22 @@ func (d *Dereferencer) fetchStatusPoll(ctx context.Context, existing, status *gt | ||||||
| 		// no previous poll, insert new poll! | 		// no previous poll, insert new poll! | ||||||
| 		return insertStatusPoll(ctx, status) | 		return insertStatusPoll(ctx, status) | ||||||
| 
 | 
 | ||||||
| 	case /*existing.Poll != nil &&*/ status.Poll == nil: | 	case status.Poll == nil: | ||||||
| 		// existing poll has been deleted, remove this. | 		// existing poll has been deleted, remove this. | ||||||
| 		return deleteStatusPoll(ctx, existing.PollID) | 		return deleteStatusPoll(ctx, existing.PollID) | ||||||
| 
 | 
 | ||||||
| 	case /*existing.Poll != nil && status.Poll != nil && */ | 	case pollChanged(existing.Poll, status.Poll): | ||||||
| 		!slices.Equal(existing.Poll.Options, status.Poll.Options) || |  | ||||||
| 			!existing.Poll.ExpiresAt.Equal(status.Poll.ExpiresAt): |  | ||||||
| 		// poll has changed since original, delete and reinsert new. | 		// poll has changed since original, delete and reinsert new. | ||||||
| 		if err := deleteStatusPoll(ctx, existing.PollID); err != nil { | 		if err := deleteStatusPoll(ctx, existing.PollID); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		return insertStatusPoll(ctx, status) | 		return insertStatusPoll(ctx, status) | ||||||
| 
 | 
 | ||||||
| 	case /*existing.Poll != nil && status.Poll != nil && */ | 	case pollUpdated(existing.Poll, status.Poll): | ||||||
| 		!existing.Poll.ClosedAt.Equal(status.Poll.ClosedAt) || |  | ||||||
| 			!slices.Equal(existing.Poll.Votes, status.Poll.Votes) || |  | ||||||
| 			existing.Poll.Voters != status.Poll.Voters: |  | ||||||
| 		// Since we last saw it, the poll has updated! | 		// Since we last saw it, the poll has updated! | ||||||
| 		// Whether that be stats, or close time. | 		// Whether that be stats, or close time. | ||||||
| 		poll := existing.Poll | 		poll := existing.Poll | ||||||
| 		poll.Closing = (!poll.Closed() && status.Poll.Closed()) | 		poll.Closing = pollJustClosed(existing.Poll, status.Poll) | ||||||
| 		poll.ClosedAt = status.Poll.ClosedAt | 		poll.ClosedAt = status.Poll.ClosedAt | ||||||
| 		poll.Voters = status.Poll.Voters | 		poll.Voters = status.Poll.Voters | ||||||
| 		poll.Votes = status.Poll.Votes | 		poll.Votes = status.Poll.Votes | ||||||
|  |  | ||||||
|  | @ -17,6 +17,12 @@ | ||||||
| 
 | 
 | ||||||
| package dereferencing | package dereferencing | ||||||
| 
 | 
 | ||||||
|  | import ( | ||||||
|  | 	"slices" | ||||||
|  | 
 | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
| // doOnce wraps a function to only perform it once. | // doOnce wraps a function to only perform it once. | ||||||
| func doOnce(fn func()) func() { | func doOnce(fn func()) func() { | ||||||
| 	var once int32 | 	var once int32 | ||||||
|  | @ -27,3 +33,24 @@ func doOnce(fn func()) func() { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // pollChanged returns whether a poll has changed in way that | ||||||
|  | // indicates that this should be an entirely new poll. i.e. if | ||||||
|  | // the available options have changed, or the expiry has increased. | ||||||
|  | func pollChanged(existing, latest *gtsmodel.Poll) bool { | ||||||
|  | 	return !slices.Equal(existing.Options, latest.Options) || | ||||||
|  | 		!existing.ExpiresAt.Equal(latest.ExpiresAt) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // pollUpdated returns whether a poll has updated, i.e. if the | ||||||
|  | // vote counts have changed, or if it has expired / been closed. | ||||||
|  | func pollUpdated(existing, latest *gtsmodel.Poll) bool { | ||||||
|  | 	return *existing.Voters != *latest.Voters || | ||||||
|  | 		!slices.Equal(existing.Votes, latest.Votes) || | ||||||
|  | 		!existing.ClosedAt.Equal(latest.ClosedAt) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // pollJustClosed returns whether a poll has *just* closed. | ||||||
|  | func pollJustClosed(existing, latest *gtsmodel.Poll) bool { | ||||||
|  | 	return existing.ClosedAt.IsZero() && latest.Closed() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -32,8 +32,8 @@ type Poll struct { | ||||||
| 	Voters     *int      `bun:",nullzero,notnull"`                        // Total no. voters count. | 	Voters     *int      `bun:",nullzero,notnull"`                        // Total no. voters count. | ||||||
| 	StatusID   string    `bun:"type:CHAR(26),nullzero,notnull,unique"`    // Status ID of which this Poll is attached to. | 	StatusID   string    `bun:"type:CHAR(26),nullzero,notnull,unique"`    // Status ID of which this Poll is attached to. | ||||||
| 	Status     *Status   `bun:"-"`                                        // The related Status for StatusID (not always set). | 	Status     *Status   `bun:"-"`                                        // The related Status for StatusID (not always set). | ||||||
| 	ExpiresAt  time.Time `bun:"type:timestamptz,nullzero,notnull"`        // The expiry date of this Poll. | 	ExpiresAt  time.Time `bun:"type:timestamptz,nullzero"`                // The expiry date of this Poll, will be zerotime until set. (local polls ALWAYS have this set). | ||||||
| 	ClosedAt   time.Time `bun:"type:timestamptz,nullzero"`                // The closure date of this poll, will be zerotime until set. | 	ClosedAt   time.Time `bun:"type:timestamptz,nullzero"`                // The closure date of this poll, anything other than zerotime indicates closed. | ||||||
| 	Closing    bool      `bun:"-"`                                        // An ephemeral field only set on Polls in the middle of closing. | 	Closing    bool      `bun:"-"`                                        // An ephemeral field only set on Polls in the middle of closing. | ||||||
| 	// no creation date, use attached Status.CreatedAt. | 	// no creation date, use attached Status.CreatedAt. | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -77,7 +77,7 @@ func (suite *NotificationTestSuite) TestStreamNotification() { | ||||||
|     "header_static": "http://localhost:8080/assets/default_header.png", |     "header_static": "http://localhost:8080/assets/default_header.png", | ||||||
|     "followers_count": 0, |     "followers_count": 0, | ||||||
|     "following_count": 0, |     "following_count": 0, | ||||||
|     "statuses_count": 2, |     "statuses_count": 3, | ||||||
|     "last_status_at": "2021-09-11T09:40:37.000Z", |     "last_status_at": "2021-09-11T09:40:37.000Z", | ||||||
|     "emojis": [], |     "emojis": [], | ||||||
|     "fields": [] |     "fields": [] | ||||||
|  |  | ||||||
|  | @ -40,7 +40,7 @@ func (suite *PruneTestSuite) TestPrune() { | ||||||
| 
 | 
 | ||||||
| 	pruned, err := suite.state.Timelines.Home.Prune(ctx, testAccountID, desiredPreparedItemsLength, desiredIndexedItemsLength) | 	pruned, err := suite.state.Timelines.Home.Prune(ctx, testAccountID, desiredPreparedItemsLength, desiredIndexedItemsLength) | ||||||
| 	suite.NoError(err) | 	suite.NoError(err) | ||||||
| 	suite.Equal(16, pruned) | 	suite.Equal(17, pruned) | ||||||
| 	suite.Equal(5, suite.state.Timelines.Home.GetIndexedLength(ctx, testAccountID)) | 	suite.Equal(5, suite.state.Timelines.Home.GetIndexedLength(ctx, testAccountID)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -56,7 +56,7 @@ func (suite *PruneTestSuite) TestPruneTwice() { | ||||||
| 
 | 
 | ||||||
| 	pruned, err := suite.state.Timelines.Home.Prune(ctx, testAccountID, desiredPreparedItemsLength, desiredIndexedItemsLength) | 	pruned, err := suite.state.Timelines.Home.Prune(ctx, testAccountID, desiredPreparedItemsLength, desiredIndexedItemsLength) | ||||||
| 	suite.NoError(err) | 	suite.NoError(err) | ||||||
| 	suite.Equal(16, pruned) | 	suite.Equal(17, pruned) | ||||||
| 	suite.Equal(5, suite.state.Timelines.Home.GetIndexedLength(ctx, testAccountID)) | 	suite.Equal(5, suite.state.Timelines.Home.GetIndexedLength(ctx, testAccountID)) | ||||||
| 
 | 
 | ||||||
| 	// Prune same again, nothing should be pruned this time. | 	// Prune same again, nothing should be pruned this time. | ||||||
|  | @ -78,7 +78,7 @@ func (suite *PruneTestSuite) TestPruneTo0() { | ||||||
| 
 | 
 | ||||||
| 	pruned, err := suite.state.Timelines.Home.Prune(ctx, testAccountID, desiredPreparedItemsLength, desiredIndexedItemsLength) | 	pruned, err := suite.state.Timelines.Home.Prune(ctx, testAccountID, desiredPreparedItemsLength, desiredIndexedItemsLength) | ||||||
| 	suite.NoError(err) | 	suite.NoError(err) | ||||||
| 	suite.Equal(21, pruned) | 	suite.Equal(22, pruned) | ||||||
| 	suite.Equal(0, suite.state.Timelines.Home.GetIndexedLength(ctx, testAccountID)) | 	suite.Equal(0, suite.state.Timelines.Home.GetIndexedLength(ctx, testAccountID)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -95,7 +95,7 @@ func (suite *PruneTestSuite) TestPruneToInfinityAndBeyond() { | ||||||
| 	pruned, err := suite.state.Timelines.Home.Prune(ctx, testAccountID, desiredPreparedItemsLength, desiredIndexedItemsLength) | 	pruned, err := suite.state.Timelines.Home.Prune(ctx, testAccountID, desiredPreparedItemsLength, desiredIndexedItemsLength) | ||||||
| 	suite.NoError(err) | 	suite.NoError(err) | ||||||
| 	suite.Equal(0, pruned) | 	suite.Equal(0, pruned) | ||||||
| 	suite.Equal(21, suite.state.Timelines.Home.GetIndexedLength(ctx, testAccountID)) | 	suite.Equal(22, suite.state.Timelines.Home.GetIndexedLength(ctx, testAccountID)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestPruneTestSuite(t *testing.T) { | func TestPruneTestSuite(t *testing.T) { | ||||||
|  |  | ||||||
|  | @ -708,8 +708,10 @@ func (c *Converter) addPollToAS(ctx context.Context, poll *gtsmodel.Poll, dst ap | ||||||
| 		optionsProp.AppendActivityStreamsNote(note) | 		optionsProp.AppendActivityStreamsNote(note) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if !poll.ExpiresAt.IsZero() { | ||||||
| 		// Set poll endTime property. | 		// Set poll endTime property. | ||||||
| 		ap.SetEndTime(dst, poll.ExpiresAt) | 		ap.SetEndTime(dst, poll.ExpiresAt) | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	if !poll.ClosedAt.IsZero() { | 	if !poll.ClosedAt.IsZero() { | ||||||
| 		// Poll is closed, set closed property. | 		// Poll is closed, set closed property. | ||||||
|  |  | ||||||
|  | @ -1367,6 +1367,7 @@ func (c *Converter) PollToAPIPoll(ctx context.Context, requester *gtsmodel.Accou | ||||||
| 		voted       *bool | 		voted       *bool | ||||||
| 		ownChoices  *[]int | 		ownChoices  *[]int | ||||||
| 		isAuthor    bool | 		isAuthor    bool | ||||||
|  | 		expiresAt   string | ||||||
| 		emojis      []apimodel.Emoji | 		emojis      []apimodel.Emoji | ||||||
| 	) | 	) | ||||||
| 
 | 
 | ||||||
|  | @ -1428,6 +1429,11 @@ func (c *Converter) PollToAPIPoll(ctx context.Context, requester *gtsmodel.Accou | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if !poll.ExpiresAt.IsZero() { | ||||||
|  | 		// Calculate poll expiry string (if set). | ||||||
|  | 		expiresAt = util.FormatISO8601(poll.ExpiresAt) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// TODO: emojis used in poll options. | 	// TODO: emojis used in poll options. | ||||||
| 	// For now init to empty slice to serialize as `[]`. | 	// For now init to empty slice to serialize as `[]`. | ||||||
| 	// In future inherit from parent status. | 	// In future inherit from parent status. | ||||||
|  | @ -1435,7 +1441,7 @@ func (c *Converter) PollToAPIPoll(ctx context.Context, requester *gtsmodel.Accou | ||||||
| 
 | 
 | ||||||
| 	return &apimodel.Poll{ | 	return &apimodel.Poll{ | ||||||
| 		ID:          poll.ID, | 		ID:          poll.ID, | ||||||
| 		ExpiresAt:   util.FormatISO8601(poll.ExpiresAt), | 		ExpiresAt:   expiresAt, | ||||||
| 		Expired:     poll.Closed(), | 		Expired:     poll.Closed(), | ||||||
| 		Multiple:    (*poll.Multiple), | 		Multiple:    (*poll.Multiple), | ||||||
| 		VotesCount:  totalVotes, | 		VotesCount:  totalVotes, | ||||||
|  |  | ||||||
|  | @ -1010,7 +1010,7 @@ func (suite *InternalToFrontendTestSuite) TestReportToFrontend1() { | ||||||
|     "header_static": "http://localhost:8080/assets/default_header.png", |     "header_static": "http://localhost:8080/assets/default_header.png", | ||||||
|     "followers_count": 0, |     "followers_count": 0, | ||||||
|     "following_count": 0, |     "following_count": 0, | ||||||
|     "statuses_count": 2, |     "statuses_count": 3, | ||||||
|     "last_status_at": "2021-09-11T09:40:37.000Z", |     "last_status_at": "2021-09-11T09:40:37.000Z", | ||||||
|     "emojis": [], |     "emojis": [], | ||||||
|     "fields": [] |     "fields": [] | ||||||
|  | @ -1127,7 +1127,7 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend1() { | ||||||
|       "header_static": "http://localhost:8080/assets/default_header.png", |       "header_static": "http://localhost:8080/assets/default_header.png", | ||||||
|       "followers_count": 0, |       "followers_count": 0, | ||||||
|       "following_count": 0, |       "following_count": 0, | ||||||
|       "statuses_count": 2, |       "statuses_count": 3, | ||||||
|       "last_status_at": "2021-09-11T09:40:37.000Z", |       "last_status_at": "2021-09-11T09:40:37.000Z", | ||||||
|       "emojis": [], |       "emojis": [], | ||||||
|       "fields": [] |       "fields": [] | ||||||
|  | @ -1395,7 +1395,7 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend2() { | ||||||
|       "header_static": "http://localhost:8080/assets/default_header.png", |       "header_static": "http://localhost:8080/assets/default_header.png", | ||||||
|       "followers_count": 0, |       "followers_count": 0, | ||||||
|       "following_count": 0, |       "following_count": 0, | ||||||
|       "statuses_count": 2, |       "statuses_count": 3, | ||||||
|       "last_status_at": "2021-09-11T09:40:37.000Z", |       "last_status_at": "2021-09-11T09:40:37.000Z", | ||||||
|       "emojis": [], |       "emojis": [], | ||||||
|       "fields": [] |       "fields": [] | ||||||
|  | @ -1442,7 +1442,7 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontend2() { | ||||||
|         "header_static": "http://localhost:8080/assets/default_header.png", |         "header_static": "http://localhost:8080/assets/default_header.png", | ||||||
|         "followers_count": 0, |         "followers_count": 0, | ||||||
|         "following_count": 0, |         "following_count": 0, | ||||||
|         "statuses_count": 2, |         "statuses_count": 3, | ||||||
|         "last_status_at": "2021-09-11T09:40:37.000Z", |         "last_status_at": "2021-09-11T09:40:37.000Z", | ||||||
|         "emojis": [], |         "emojis": [], | ||||||
|         "fields": [] |         "fields": [] | ||||||
|  | @ -1573,7 +1573,7 @@ func (suite *InternalToFrontendTestSuite) TestAdminReportToFrontendSuspendedLoca | ||||||
|       "header_static": "http://localhost:8080/assets/default_header.png", |       "header_static": "http://localhost:8080/assets/default_header.png", | ||||||
|       "followers_count": 0, |       "followers_count": 0, | ||||||
|       "following_count": 0, |       "following_count": 0, | ||||||
|       "statuses_count": 2, |       "statuses_count": 3, | ||||||
|       "last_status_at": "2021-09-11T09:40:37.000Z", |       "last_status_at": "2021-09-11T09:40:37.000Z", | ||||||
|       "emojis": [], |       "emojis": [], | ||||||
|       "fields": [] |       "fields": [] | ||||||
|  |  | ||||||
|  | @ -1916,8 +1916,8 @@ func NewTestStatuses() map[string]*gtsmodel.Status { | ||||||
| 		}, | 		}, | ||||||
| 		"remote_account_1_status_2": { | 		"remote_account_1_status_2": { | ||||||
| 			ID:                       "01HEN2QRFA8H3C6QPN7RD4KSR6", | 			ID:                       "01HEN2QRFA8H3C6QPN7RD4KSR6", | ||||||
| 			URI:                      "http://fossbros-anonymous.io/users/foss_satan/statuses/065TKDN4BX1PC8N19TSY9SD2N4", | 			URI:                      "http://fossbros-anonymous.io/users/foss_satan/statuses/01HEN2QRFA8H3C6QPN7RD4KSR6", | ||||||
| 			URL:                      "http://fossbros-anonymous.io/@foss_satan/statuses/065TKDN4BX1PC8N19TSY9SD2N4", | 			URL:                      "http://fossbros-anonymous.io/@foss_satan/statuses/01HEN2QRFA8H3C6QPN7RD4KSR6", | ||||||
| 			Content:                  "what products should i buy at the grocery store?", | 			Content:                  "what products should i buy at the grocery store?", | ||||||
| 			AttachmentIDs:            []string{"01FVW7RXPQ8YJHTEXYPE7Q8ZY0"}, | 			AttachmentIDs:            []string{"01FVW7RXPQ8YJHTEXYPE7Q8ZY0"}, | ||||||
| 			CreatedAt:                TimeMustParse("2021-09-11T11:40:37+02:00"), | 			CreatedAt:                TimeMustParse("2021-09-11T11:40:37+02:00"), | ||||||
|  | @ -1941,6 +1941,33 @@ func NewTestStatuses() map[string]*gtsmodel.Status { | ||||||
| 			ActivityStreamsType:      ap.ActivityQuestion, | 			ActivityStreamsType:      ap.ActivityQuestion, | ||||||
| 			PollID:                   "01HEN2R65468ZG657C4ZPHJ4EX", | 			PollID:                   "01HEN2R65468ZG657C4ZPHJ4EX", | ||||||
| 		}, | 		}, | ||||||
|  | 		"remote_account_1_status_3": { | ||||||
|  | 			ID:                       "01HEWV37MHV8BAC8ANFGVRRM5D", | ||||||
|  | 			URI:                      "http://fossbros-anonymous.io/users/foss_satan/statuses/01HEWV37MHV8BAC8ANFGVRRM5D", | ||||||
|  | 			URL:                      "http://fossbros-anonymous.io/@foss_satan/statuses/01HEWV37MHV8BAC8ANFGVRRM5D", | ||||||
|  | 			Content:                  "what products should i buy at the grocery store? (now an endless poll!)", | ||||||
|  | 			AttachmentIDs:            []string{"01FVW7RXPQ8YJHTEXYPE7Q8ZY0"}, | ||||||
|  | 			CreatedAt:                TimeMustParse("2021-09-11T11:40:37+02:00"), | ||||||
|  | 			UpdatedAt:                TimeMustParse("2021-09-11T11:40:37+02:00"), | ||||||
|  | 			Local:                    util.Ptr(false), | ||||||
|  | 			AccountURI:               "http://fossbros-anonymous.io/users/foss_satan", | ||||||
|  | 			AccountID:                "01F8MH5ZK5VRH73AKHQM6Y9VNX", | ||||||
|  | 			InReplyToID:              "", | ||||||
|  | 			InReplyToAccountID:       "", | ||||||
|  | 			InReplyToURI:             "", | ||||||
|  | 			BoostOfID:                "", | ||||||
|  | 			ContentWarning:           "", | ||||||
|  | 			Visibility:               gtsmodel.VisibilityUnlocked, | ||||||
|  | 			Sensitive:                util.Ptr(false), | ||||||
|  | 			Language:                 "en", | ||||||
|  | 			CreatedWithApplicationID: "", | ||||||
|  | 			Federated:                util.Ptr(true), | ||||||
|  | 			Boostable:                util.Ptr(true), | ||||||
|  | 			Replyable:                util.Ptr(true), | ||||||
|  | 			Likeable:                 util.Ptr(true), | ||||||
|  | 			ActivityStreamsType:      ap.ActivityQuestion, | ||||||
|  | 			PollID:                   "01HEWV1GW2D49R919NPEDXPTZ5", | ||||||
|  | 		}, | ||||||
| 		"remote_account_2_status_1": { | 		"remote_account_2_status_1": { | ||||||
| 			ID:                       "01HE7XJ1CG84TBKH5V9XKBVGF5", | 			ID:                       "01HE7XJ1CG84TBKH5V9XKBVGF5", | ||||||
| 			URI:                      "http://example.org/users/Some_User/statuses/01HE7XJ1CG84TBKH5V9XKBVGF5", | 			URI:                      "http://example.org/users/Some_User/statuses/01HE7XJ1CG84TBKH5V9XKBVGF5", | ||||||
|  | @ -2012,6 +2039,20 @@ func NewTestPolls() map[string]*gtsmodel.Poll { | ||||||
| 			ClosedAt:   TimeMustParse("2021-09-11T12:40:37+02:00"), | 			ClosedAt:   TimeMustParse("2021-09-11T12:40:37+02:00"), | ||||||
| 			Closing:    false, | 			Closing:    false, | ||||||
| 		}, | 		}, | ||||||
|  | 		"remote_account_1_status_3_poll": { | ||||||
|  | 			ID:         "01HEWV1GW2D49R919NPEDXPTZ5", | ||||||
|  | 			Multiple:   util.Ptr(true), | ||||||
|  | 			HideCounts: util.Ptr(false), | ||||||
|  | 			Options:    []string{"vaseline", "tissues", "financial times"}, | ||||||
|  | 			Votes:      []int{0, 0, 0}, | ||||||
|  | 			Voters:     util.Ptr(0), | ||||||
|  | 			StatusID:   "01HEWV37MHV8BAC8ANFGVRRM5D", | ||||||
|  | 			Status:     nil, | ||||||
|  | 			// nil expiry AND closed date, i.e. no end | ||||||
|  | 			ExpiresAt: time.Time{}, | ||||||
|  | 			ClosedAt:  time.Time{}, | ||||||
|  | 			Closing:   false, | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue