mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 02:32:25 -05:00 
			
		
		
		
	[performance] convert enum strings to ints (#3558)
* convert statuses.visibility and notifications.notification_type columns from type string -> int for performance / space savings * fix test trying to compare string to int * fix instance count query using string literal instead of gtsmodel const type * ensure a default value is always set * also migrate the account settings and sin bin status tables * initialize maps outside loops and place into singular enum mapping creation func * use int16 for enum types * update sinbinstatus creation to be from a snapshot at initial creation * add snapshot of poll type at creation time
This commit is contained in:
		
					parent
					
						
							
								934e895ec0
							
						
					
				
			
			
				commit
				
					
						cac9d65029
					
				
			
		
					 32 changed files with 940 additions and 91 deletions
				
			
		|  | @ -1027,7 +1027,7 @@ func ExtractVisibility(addressable Addressable, actorFollowersURI string) (gtsmo | ||||||
| 	) | 	) | ||||||
| 
 | 
 | ||||||
| 	if len(to) == 0 && len(cc) == 0 { | 	if len(to) == 0 && len(cc) == 0 { | ||||||
| 		return "", gtserror.Newf("message wasn't TO or CC anyone") | 		return 0, gtserror.Newf("message wasn't TO or CC anyone") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Assume most restrictive visibility, | 	// Assume most restrictive visibility, | ||||||
|  |  | ||||||
|  | @ -28,7 +28,6 @@ import ( | ||||||
| 	"github.com/stretchr/testify/suite" | 	"github.com/stretchr/testify/suite" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/api/client/accounts" | 	"github.com/superseriousbusiness/gotosocial/internal/api/client/accounts" | ||||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" |  | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -99,7 +98,7 @@ func (suite *AccountVerifyTestSuite) TestAccountVerifyGet() { | ||||||
| 	suite.Equal(2, apimodelAccount.FollowersCount) | 	suite.Equal(2, apimodelAccount.FollowersCount) | ||||||
| 	suite.Equal(2, apimodelAccount.FollowingCount) | 	suite.Equal(2, apimodelAccount.FollowingCount) | ||||||
| 	suite.Equal(8, apimodelAccount.StatusesCount) | 	suite.Equal(8, apimodelAccount.StatusesCount) | ||||||
| 	suite.EqualValues(gtsmodel.VisibilityPublic, apimodelAccount.Source.Privacy) | 	suite.EqualValues(apimodel.VisibilityPublic, apimodelAccount.Source.Privacy) | ||||||
| 	suite.Equal(testAccount.Settings.Language, apimodelAccount.Source.Language) | 	suite.Equal(testAccount.Settings.Language, apimodelAccount.Source.Language) | ||||||
| 	suite.Equal(testAccount.NoteRaw, apimodelAccount.Source.Note) | 	suite.Equal(testAccount.NoteRaw, apimodelAccount.Source.Note) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -164,6 +164,18 @@ func (m *Module) NotificationsGETHandler(c *gin.Context) { | ||||||
| 		limit = int(i) | 		limit = int(i) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	types, errWithCode := apiutil.ParseNotificationTypes(c.QueryArray(TypesKey)) | ||||||
|  | 	if errWithCode != nil { | ||||||
|  | 		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	exclTypes, errWithCode := apiutil.ParseNotificationTypes(c.QueryArray(ExcludeTypesKey)) | ||||||
|  | 	if errWithCode != nil { | ||||||
|  | 		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	resp, errWithCode := m.processor.Timeline().NotificationsGet( | 	resp, errWithCode := m.processor.Timeline().NotificationsGet( | ||||||
| 		c.Request.Context(), | 		c.Request.Context(), | ||||||
| 		authed, | 		authed, | ||||||
|  | @ -171,8 +183,8 @@ func (m *Module) NotificationsGETHandler(c *gin.Context) { | ||||||
| 		c.Query(SinceIDKey), | 		c.Query(SinceIDKey), | ||||||
| 		c.Query(MinIDKey), | 		c.Query(MinIDKey), | ||||||
| 		limit, | 		limit, | ||||||
| 		c.QueryArray(TypesKey), | 		types, | ||||||
| 		c.QueryArray(ExcludeTypesKey), | 		exclTypes, | ||||||
| 	) | 	) | ||||||
| 	if errWithCode != nil { | 	if errWithCode != nil { | ||||||
| 		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) | 		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) | ||||||
|  |  | ||||||
|  | @ -18,11 +18,13 @@ | ||||||
| package util | package util | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
|  | @ -216,6 +218,51 @@ func ParseInteractionReblogs(value string, defaultValue bool) (bool, gtserror.Wi | ||||||
| 	return parseBool(value, defaultValue, InteractionReblogsKey) | 	return parseBool(value, defaultValue, InteractionReblogsKey) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func ParseNotificationType(value string) (gtsmodel.NotificationType, gtserror.WithCode) { | ||||||
|  | 	switch strings.ToLower(value) { | ||||||
|  | 	case "follow": | ||||||
|  | 		return gtsmodel.NotificationFollow, nil | ||||||
|  | 	case "follow_request": | ||||||
|  | 		return gtsmodel.NotificationFollowRequest, nil | ||||||
|  | 	case "mention": | ||||||
|  | 		return gtsmodel.NotificationMention, nil | ||||||
|  | 	case "reblog": | ||||||
|  | 		return gtsmodel.NotificationReblog, nil | ||||||
|  | 	case "favourite": | ||||||
|  | 		return gtsmodel.NotificationFave, nil | ||||||
|  | 	case "poll": | ||||||
|  | 		return gtsmodel.NotificationPoll, nil | ||||||
|  | 	case "status": | ||||||
|  | 		return gtsmodel.NotificationStatus, nil | ||||||
|  | 	case "admin.sign_up": | ||||||
|  | 		return gtsmodel.NotificationSignup, nil | ||||||
|  | 	case "pending.favourite": | ||||||
|  | 		return gtsmodel.NotificationPendingFave, nil | ||||||
|  | 	case "pending.reply": | ||||||
|  | 		return gtsmodel.NotificationPendingReply, nil | ||||||
|  | 	case "pending.reblog": | ||||||
|  | 		return gtsmodel.NotificationPendingReblog, nil | ||||||
|  | 	default: | ||||||
|  | 		text := fmt.Sprintf("unrecognized notification type %s", value) | ||||||
|  | 		return 0, gtserror.NewErrorBadRequest(errors.New(text), text) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func ParseNotificationTypes(values []string) ([]gtsmodel.NotificationType, gtserror.WithCode) { | ||||||
|  | 	if len(values) == 0 { | ||||||
|  | 		return nil, nil | ||||||
|  | 	} | ||||||
|  | 	ntypes := make([]gtsmodel.NotificationType, len(values)) | ||||||
|  | 	for i, value := range values { | ||||||
|  | 		ntype, errWithCode := ParseNotificationType(value) | ||||||
|  | 		if errWithCode != nil { | ||||||
|  | 			return nil, errWithCode | ||||||
|  | 		} | ||||||
|  | 		ntypes[i] = ntype | ||||||
|  | 	} | ||||||
|  | 	return ntypes, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /* | /* | ||||||
| 	Parse functions for *REQUIRED* parameters. | 	Parse functions for *REQUIRED* parameters. | ||||||
| */ | */ | ||||||
|  |  | ||||||
|  | @ -106,7 +106,7 @@ func (suite *AccountTestSuite) populateTestStatus(testAccountKey string, status | ||||||
| 	status.URI = fmt.Sprintf("http://localhost:8080/users/%s/statuses/%s", testAccount.Username, status.ID) | 	status.URI = fmt.Sprintf("http://localhost:8080/users/%s/statuses/%s", testAccount.Username, status.ID) | ||||||
| 	status.Local = util.Ptr(true) | 	status.Local = util.Ptr(true) | ||||||
| 
 | 
 | ||||||
| 	if status.Visibility == "" { | 	if status.Visibility == 0 { | ||||||
| 		status.Visibility = gtsmodel.VisibilityDefault | 		status.Visibility = gtsmodel.VisibilityDefault | ||||||
| 	} | 	} | ||||||
| 	if status.ActivityStreamsType == "" { | 	if status.ActivityStreamsType == "" { | ||||||
|  |  | ||||||
|  | @ -104,7 +104,7 @@ func (i *instanceDB) CountInstanceStatuses(ctx context.Context, domain string) ( | ||||||
| 	q = q.Where("NOT ? = ?", bun.Ident("status.pending_approval"), true) | 	q = q.Where("NOT ? = ?", bun.Ident("status.pending_approval"), true) | ||||||
| 
 | 
 | ||||||
| 	// Ignore statuses that are direct messages. | 	// Ignore statuses that are direct messages. | ||||||
| 	q = q.Where("NOT ? = ?", bun.Ident("status.visibility"), "direct") | 	q = q.Where("NOT ? = ?", bun.Ident("status.visibility"), gtsmodel.VisibilityDirect) | ||||||
| 
 | 
 | ||||||
| 	count, err := q.Count(ctx) | 	count, err := q.Count(ctx) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  |  | ||||||
|  | @ -21,7 +21,8 @@ import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	gtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20231002153327_add_status_polls" | ||||||
|  | 
 | ||||||
| 	"github.com/uptrace/bun" | 	"github.com/uptrace/bun" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,48 @@ | ||||||
|  | // 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 gtsmodel | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Poll represents an attached (to) Status poll, i.e. a questionaire. Can be remote / local. | ||||||
|  | type Poll struct { | ||||||
|  | 	ID         string    `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // Unique identity string. | ||||||
|  | 	Multiple   *bool     `bun:",nullzero,notnull,default:false"`          // Is this a multiple choice poll? i.e. can you vote on multiple options. | ||||||
|  | 	HideCounts *bool     `bun:",nullzero,notnull,default:false"`          // Hides vote counts until poll ends. | ||||||
|  | 	Options    []string  `bun:",nullzero,notnull"`                        // The available options for this poll. | ||||||
|  | 	Votes      []int     `bun:",nullzero,notnull"`                        // Vote counts per choice. | ||||||
|  | 	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. | ||||||
|  | 	ExpiresAt  time.Time `bun:"type:timestamptz,nullzero,notnull"`        // The expiry date of this Poll. | ||||||
|  | 	ClosedAt   time.Time `bun:"type:timestamptz,nullzero"`                // The closure date of this poll, will be zerotime until set. | ||||||
|  | 	Closing    bool      `bun:"-"`                                        // An ephemeral field only set on Polls in the middle of closing. | ||||||
|  | 	// no creation date, use attached Status.CreatedAt. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // PollVote represents a single instance of vote(s) in a Poll by an account. | ||||||
|  | // If the Poll is single-choice, len(.Choices) = 1, if multiple-choice then | ||||||
|  | // len(.Choices) >= 1. Can be remote or local. | ||||||
|  | type PollVote struct { | ||||||
|  | 	ID        string    `bun:"type:CHAR(26),pk,nullzero,notnull,unique"`                    // Unique identity string. | ||||||
|  | 	Choices   []int     `bun:",nullzero,notnull"`                                           // The Poll's option indices of which these are votes for. | ||||||
|  | 	AccountID string    `bun:"type:CHAR(26),nullzero,notnull,unique:in_poll_by_account"`    // Account ID from which this vote originated. | ||||||
|  | 	PollID    string    `bun:"type:CHAR(26),nullzero,notnull,unique:in_poll_by_account"`    // Poll ID of which this is a vote in. | ||||||
|  | 	CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // The creation date of this PollVote. | ||||||
|  | } | ||||||
|  | @ -0,0 +1,75 @@ | ||||||
|  | // 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 gtsmodel | ||||||
|  | 
 | ||||||
|  | import "time" | ||||||
|  | 
 | ||||||
|  | // Status represents a user-created 'post' or 'status' in the database, either remote or local | ||||||
|  | type Status 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 | ||||||
|  | 	FetchedAt                time.Time  `bun:"type:timestamptz,nullzero"`                                   // when was item (remote) last fetched. | ||||||
|  | 	PinnedAt                 time.Time  `bun:"type:timestamptz,nullzero"`                                   // Status was pinned by owning account at this time. | ||||||
|  | 	URI                      string     `bun:",unique,nullzero,notnull"`                                    // activitypub URI of this status | ||||||
|  | 	URL                      string     `bun:",nullzero"`                                                   // web url for viewing this status | ||||||
|  | 	Content                  string     `bun:""`                                                            // content of this status; likely html-formatted but not guaranteed | ||||||
|  | 	AttachmentIDs            []string   `bun:"attachments,array"`                                           // Database IDs of any media attachments associated with this status | ||||||
|  | 	TagIDs                   []string   `bun:"tags,array"`                                                  // Database IDs of any tags used in this status | ||||||
|  | 	MentionIDs               []string   `bun:"mentions,array"`                                              // Database IDs of any mentions in this status | ||||||
|  | 	EmojiIDs                 []string   `bun:"emojis,array"`                                                // Database IDs of any emojis used in this status | ||||||
|  | 	Local                    *bool      `bun:",nullzero,notnull,default:false"`                             // is this status from a local account? | ||||||
|  | 	AccountID                string     `bun:"type:CHAR(26),nullzero,notnull"`                              // which account posted this status? | ||||||
|  | 	AccountURI               string     `bun:",nullzero,notnull"`                                           // activitypub uri of the owner of this status | ||||||
|  | 	InReplyToID              string     `bun:"type:CHAR(26),nullzero"`                                      // id of the status this status replies to | ||||||
|  | 	InReplyToURI             string     `bun:",nullzero"`                                                   // activitypub uri of the status this status is a reply to | ||||||
|  | 	InReplyToAccountID       string     `bun:"type:CHAR(26),nullzero"`                                      // id of the account that this status replies to | ||||||
|  | 	BoostOfID                string     `bun:"type:CHAR(26),nullzero"`                                      // id of the status this status is a boost of | ||||||
|  | 	BoostOfAccountID         string     `bun:"type:CHAR(26),nullzero"`                                      // id of the account that owns the boosted status | ||||||
|  | 	ThreadID                 string     `bun:"type:CHAR(26),nullzero"`                                      // id of the thread to which this status belongs; only set for remote statuses if a local account is involved at some point in the thread, otherwise null | ||||||
|  | 	PollID                   string     `bun:"type:CHAR(26),nullzero"`                                      // | ||||||
|  | 	ContentWarning           string     `bun:",nullzero"`                                                   // cw string for this status | ||||||
|  | 	Visibility               Visibility `bun:",nullzero,notnull"`                                           // visibility entry for this status | ||||||
|  | 	Sensitive                *bool      `bun:",nullzero,notnull,default:false"`                             // mark the status as sensitive? | ||||||
|  | 	Language                 string     `bun:",nullzero"`                                                   // what language is this status written in? | ||||||
|  | 	CreatedWithApplicationID string     `bun:"type:CHAR(26),nullzero"`                                      // Which application was used to create this status? | ||||||
|  | 	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 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Visibility represents the visibility granularity of a status. | ||||||
|  | type Visibility string | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// VisibilityPublic means this status will be visible to everyone on all timelines. | ||||||
|  | 	VisibilityPublic Visibility = "public" | ||||||
|  | 	// VisibilityUnlocked means this status will be visible to everyone, but will only show on home timeline to followers, and in lists. | ||||||
|  | 	VisibilityUnlocked Visibility = "unlocked" | ||||||
|  | 	// VisibilityFollowersOnly means this status is viewable to followers only. | ||||||
|  | 	VisibilityFollowersOnly Visibility = "followers_only" | ||||||
|  | 	// VisibilityMutualsOnly means this status is visible to mutual followers only. | ||||||
|  | 	VisibilityMutualsOnly Visibility = "mutuals_only" | ||||||
|  | 	// VisibilityDirect means this status is visible only to mentioned recipients. | ||||||
|  | 	VisibilityDirect Visibility = "direct" | ||||||
|  | 	// VisibilityDefault is used when no other setting can be found. | ||||||
|  | 	VisibilityDefault Visibility = VisibilityUnlocked | ||||||
|  | ) | ||||||
|  | @ -161,11 +161,14 @@ func init() { | ||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | 			// Get the mapping of old enum string values to new integer values. | ||||||
|  | 			visibilityMapping := visibilityEnumMapping[oldmodel.Visibility]() | ||||||
|  | 
 | ||||||
| 			// For each status found in this way, update | 			// For each status found in this way, update | ||||||
| 			// to new version of interaction policy. | 			// to new version of interaction policy. | ||||||
| 			for _, oldStatus := range oldStatuses { | 			for _, oldStatus := range oldStatuses { | ||||||
| 				// Start with default policy for this visibility. | 				// Start with default policy for this visibility. | ||||||
| 				v := gtsmodel.Visibility(oldStatus.Visibility) | 				v := visibilityMapping[oldStatus.Visibility] | ||||||
| 				policy := gtsmodel.DefaultInteractionPolicyFor(v) | 				policy := gtsmodel.DefaultInteractionPolicyFor(v) | ||||||
| 
 | 
 | ||||||
| 				if !*oldStatus.Likeable { | 				if !*oldStatus.Likeable { | ||||||
|  |  | ||||||
|  | @ -48,7 +48,7 @@ type Status struct { | ||||||
| 	ThreadID                 string     `bun:"type:CHAR(26),nullzero"` | 	ThreadID                 string     `bun:"type:CHAR(26),nullzero"` | ||||||
| 	PollID                   string     `bun:"type:CHAR(26),nullzero"` | 	PollID                   string     `bun:"type:CHAR(26),nullzero"` | ||||||
| 	ContentWarning           string     `bun:",nullzero"` | 	ContentWarning           string     `bun:",nullzero"` | ||||||
| 	Visibility               string    `bun:",nullzero,notnull"` | 	Visibility               Visibility `bun:",nullzero,notnull"` | ||||||
| 	Sensitive                *bool      `bun:",nullzero,notnull,default:false"` | 	Sensitive                *bool      `bun:",nullzero,notnull,default:false"` | ||||||
| 	Language                 string     `bun:",nullzero"` | 	Language                 string     `bun:",nullzero"` | ||||||
| 	CreatedWithApplicationID string     `bun:"type:CHAR(26),nullzero"` | 	CreatedWithApplicationID string     `bun:"type:CHAR(26),nullzero"` | ||||||
|  | @ -59,3 +59,24 @@ type Status struct { | ||||||
| 	Replyable                *bool      `bun:",notnull"` | 	Replyable                *bool      `bun:",notnull"` | ||||||
| 	Likeable                 *bool      `bun:",notnull"` | 	Likeable                 *bool      `bun:",notnull"` | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // Visibility represents the visibility granularity of a status. | ||||||
|  | type Visibility string | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// VisibilityNone means nobody can see this. | ||||||
|  | 	// It's only used for web status visibility. | ||||||
|  | 	VisibilityNone Visibility = "none" | ||||||
|  | 	// VisibilityPublic means this status will be visible to everyone on all timelines. | ||||||
|  | 	VisibilityPublic Visibility = "public" | ||||||
|  | 	// VisibilityUnlocked means this status will be visible to everyone, but will only show on home timeline to followers, and in lists. | ||||||
|  | 	VisibilityUnlocked Visibility = "unlocked" | ||||||
|  | 	// VisibilityFollowersOnly means this status is viewable to followers only. | ||||||
|  | 	VisibilityFollowersOnly Visibility = "followers_only" | ||||||
|  | 	// VisibilityMutualsOnly means this status is visible to mutual followers only. | ||||||
|  | 	VisibilityMutualsOnly Visibility = "mutuals_only" | ||||||
|  | 	// VisibilityDirect means this status is visible only to mentioned recipients. | ||||||
|  | 	VisibilityDirect Visibility = "direct" | ||||||
|  | 	// VisibilityDefault is used when no other setting can be found. | ||||||
|  | 	VisibilityDefault Visibility = VisibilityUnlocked | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | @ -20,7 +20,7 @@ package migrations | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 
 | 
 | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	gtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20240904084406_fedi_api_reject_interaction" | ||||||
| 	"github.com/uptrace/bun" | 	"github.com/uptrace/bun" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,66 @@ | ||||||
|  | // 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 gtsmodel | ||||||
|  | 
 | ||||||
|  | import "time" | ||||||
|  | 
 | ||||||
|  | // SinBinStatus represents a status that's been rejected and/or reported + quarantined. | ||||||
|  | // | ||||||
|  | // Automatically rejected statuses are not put in the sin bin, only statuses that were | ||||||
|  | // stored on the instance and which someone (local or remote) has subsequently rejected. | ||||||
|  | type SinBinStatus 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"` // Creation time of this item. | ||||||
|  | 	UpdatedAt           time.Time  `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // Last-updated time of this item. | ||||||
|  | 	URI                 string     `bun:",unique,nullzero,notnull"`                                    // ActivityPub URI/ID of this status. | ||||||
|  | 	URL                 string     `bun:",nullzero"`                                                   // Web url for viewing this status. | ||||||
|  | 	Domain              string     `bun:",nullzero"`                                                   // Domain of the status, will be null if this is a local status, otherwise something like `example.org`. | ||||||
|  | 	AccountURI          string     `bun:",nullzero,notnull"`                                           // ActivityPub uri of the author of this status. | ||||||
|  | 	InReplyToURI        string     `bun:",nullzero"`                                                   // ActivityPub uri of the status this status is a reply to. | ||||||
|  | 	Content             string     `bun:",nullzero"`                                                   // Content of this status. | ||||||
|  | 	AttachmentLinks     []string   `bun:",nullzero,array"`                                             // Links to attachments of this status. | ||||||
|  | 	MentionTargetURIs   []string   `bun:",nullzero,array"`                                             // URIs of mentioned accounts. | ||||||
|  | 	EmojiLinks          []string   `bun:",nullzero,array"`                                             // Links to any emoji images used in this status. | ||||||
|  | 	PollOptions         []string   `bun:",nullzero,array"`                                             // String values of any poll options used in this status. | ||||||
|  | 	ContentWarning      string     `bun:",nullzero"`                                                   // CW / subject string for this status. | ||||||
|  | 	Visibility          Visibility `bun:",nullzero,notnull"`                                           // Visibility level of this status. | ||||||
|  | 	Sensitive           *bool      `bun:",nullzero,notnull,default:false"`                             // Mark the status as sensitive. | ||||||
|  | 	Language            string     `bun:",nullzero"`                                                   // Language code for this status. | ||||||
|  | 	ActivityStreamsType string     `bun:",nullzero,notnull"`                                           // ActivityStreams type of this status. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Visibility represents the visibility granularity of a status. | ||||||
|  | type Visibility string | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// VisibilityNone means nobody can see this. | ||||||
|  | 	// It's only used for web status visibility. | ||||||
|  | 	VisibilityNone Visibility = "none" | ||||||
|  | 	// VisibilityPublic means this status will be visible to everyone on all timelines. | ||||||
|  | 	VisibilityPublic Visibility = "public" | ||||||
|  | 	// VisibilityUnlocked means this status will be visible to everyone, but will only show on home timeline to followers, and in lists. | ||||||
|  | 	VisibilityUnlocked Visibility = "unlocked" | ||||||
|  | 	// VisibilityFollowersOnly means this status is viewable to followers only. | ||||||
|  | 	VisibilityFollowersOnly Visibility = "followers_only" | ||||||
|  | 	// VisibilityMutualsOnly means this status is visible to mutual followers only. | ||||||
|  | 	VisibilityMutualsOnly Visibility = "mutuals_only" | ||||||
|  | 	// VisibilityDirect means this status is visible only to mentioned recipients. | ||||||
|  | 	VisibilityDirect Visibility = "direct" | ||||||
|  | 	// VisibilityDefault is used when no other setting can be found. | ||||||
|  | 	VisibilityDefault Visibility = VisibilityUnlocked | ||||||
|  | ) | ||||||
|  | @ -0,0 +1,249 @@ | ||||||
|  | // 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" | ||||||
|  | 	"errors" | ||||||
|  | 
 | ||||||
|  | 	old_gtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
|  | 	new_gtsmodel "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/log" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||||
|  | 
 | ||||||
|  | 	"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 { | ||||||
|  | 
 | ||||||
|  | 			// Tables with visibility types. | ||||||
|  | 			var visTables = []struct { | ||||||
|  | 				Table   string | ||||||
|  | 				Column  string | ||||||
|  | 				Default *new_gtsmodel.Visibility | ||||||
|  | 			}{ | ||||||
|  | 				{Table: "statuses", Column: "visibility"}, | ||||||
|  | 				{Table: "sin_bin_statuses", Column: "visibility"}, | ||||||
|  | 				{Table: "account_settings", Column: "privacy", Default: util.Ptr(new_gtsmodel.VisibilityDefault)}, | ||||||
|  | 				{Table: "account_settings", Column: "web_visibility", Default: util.Ptr(new_gtsmodel.VisibilityDefault)}, | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// Visibility type indices. | ||||||
|  | 			var visIndices = []struct { | ||||||
|  | 				name  string | ||||||
|  | 				cols  []string | ||||||
|  | 				order string | ||||||
|  | 			}{ | ||||||
|  | 				{ | ||||||
|  | 					name:  "statuses_visibility_idx", | ||||||
|  | 					cols:  []string{"visibility"}, | ||||||
|  | 					order: "", | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					name:  "statuses_profile_web_view_idx", | ||||||
|  | 					cols:  []string{"account_id", "visibility"}, | ||||||
|  | 					order: "id DESC", | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					name:  "statuses_public_timeline_idx", | ||||||
|  | 					cols:  []string{"visibility"}, | ||||||
|  | 					order: "id DESC", | ||||||
|  | 				}, | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// Before making changes to the visibility col | ||||||
|  | 			// we must drop all indices that rely on it. | ||||||
|  | 			for _, index := range visIndices { | ||||||
|  | 				if _, err := tx.NewDropIndex(). | ||||||
|  | 					Index(index.name). | ||||||
|  | 					Exec(ctx); err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// Get the mapping of old enum string values to new integer values. | ||||||
|  | 			visibilityMapping := visibilityEnumMapping[old_gtsmodel.Visibility]() | ||||||
|  | 
 | ||||||
|  | 			// Convert all visibility tables. | ||||||
|  | 			for _, table := range visTables { | ||||||
|  | 				if err := convertEnums(ctx, tx, table.Table, table.Column, | ||||||
|  | 					visibilityMapping, table.Default); err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// Recreate the visibility indices. | ||||||
|  | 			for _, index := range visIndices { | ||||||
|  | 				q := tx.NewCreateIndex(). | ||||||
|  | 					Table("statuses"). | ||||||
|  | 					Index(index.name). | ||||||
|  | 					Column(index.cols...) | ||||||
|  | 				if index.order != "" { | ||||||
|  | 					q = q.ColumnExpr(index.order) | ||||||
|  | 				} | ||||||
|  | 				if _, err := q.Exec(ctx); err != nil { | ||||||
|  | 					return err | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// Get the mapping of old enum string values to the new integer value types. | ||||||
|  | 			notificationMapping := notificationEnumMapping[old_gtsmodel.NotificationType]() | ||||||
|  | 
 | ||||||
|  | 			// Migrate over old notifications table column over to new column type. | ||||||
|  | 			if err := convertEnums(ctx, tx, "notifications", "notification_type", //nolint:revive | ||||||
|  | 				notificationMapping, nil); 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) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // convertEnums performs a transaction that converts | ||||||
|  | // a table's column of our old-style enums (strings) to | ||||||
|  | // more performant and space-saving integer types. | ||||||
|  | func convertEnums[OldType ~string, NewType ~int16]( | ||||||
|  | 	ctx context.Context, | ||||||
|  | 	tx bun.Tx, | ||||||
|  | 	table string, | ||||||
|  | 	column string, | ||||||
|  | 	mapping map[OldType]NewType, | ||||||
|  | 	defaultValue *NewType, | ||||||
|  | ) error { | ||||||
|  | 	if len(mapping) == 0 { | ||||||
|  | 		return errors.New("empty mapping") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Generate new column name. | ||||||
|  | 	newColumn := column + "_new" | ||||||
|  | 
 | ||||||
|  | 	log.Infof(ctx, "converting %s.%s enums; "+ | ||||||
|  | 		"this may take a while, please don't interrupt!", | ||||||
|  | 		table, column, | ||||||
|  | 	) | ||||||
|  | 
 | ||||||
|  | 	// Ensure a default value. | ||||||
|  | 	if defaultValue == nil { | ||||||
|  | 		var zero NewType | ||||||
|  | 		defaultValue = &zero | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Add new column to database. | ||||||
|  | 	if _, err := tx.NewAddColumn(). | ||||||
|  | 		Table(table). | ||||||
|  | 		ColumnExpr("? SMALLINT NOT NULL DEFAULT ?", | ||||||
|  | 			bun.Ident(newColumn), | ||||||
|  | 			*defaultValue). | ||||||
|  | 		Exec(ctx); err != nil { | ||||||
|  | 		return gtserror.Newf("error adding new column: %w", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Get a count of all in table. | ||||||
|  | 	total, err := tx.NewSelect(). | ||||||
|  | 		Table(table). | ||||||
|  | 		Count(ctx) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return gtserror.Newf("error selecting total count: %w", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var updated int | ||||||
|  | 	for old, new := range mapping { | ||||||
|  | 
 | ||||||
|  | 		// Update old to new values. | ||||||
|  | 		res, err := tx.NewUpdate(). | ||||||
|  | 			Table(table). | ||||||
|  | 			Where("? = ?", bun.Ident(column), old). | ||||||
|  | 			Set("? = ?", bun.Ident(newColumn), new). | ||||||
|  | 			Exec(ctx) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return gtserror.Newf("error updating old column values: %w", err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Count number items updated. | ||||||
|  | 		n, _ := res.RowsAffected() | ||||||
|  | 		updated += int(n) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Check total updated. | ||||||
|  | 	if total != updated { | ||||||
|  | 		log.Warnf(ctx, "total=%d does not match updated=%d", total, updated) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Drop the old column from table. | ||||||
|  | 	if _, err := tx.NewDropColumn(). | ||||||
|  | 		Table(table). | ||||||
|  | 		ColumnExpr("?", bun.Ident(column)). | ||||||
|  | 		Exec(ctx); err != nil { | ||||||
|  | 		return gtserror.Newf("error dropping old column: %w", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Rename new to old name. | ||||||
|  | 	if _, err := tx.NewRaw( | ||||||
|  | 		"ALTER TABLE ? RENAME COLUMN ? TO ?", | ||||||
|  | 		bun.Ident(table), | ||||||
|  | 		bun.Ident(newColumn), | ||||||
|  | 		bun.Ident(column), | ||||||
|  | 	).Exec(ctx); err != nil { | ||||||
|  | 		return gtserror.Newf("error renaming new column: %w", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // visibilityEnumMapping maps old Visibility enum values to their newer integer type. | ||||||
|  | func visibilityEnumMapping[T ~string]() map[T]new_gtsmodel.Visibility { | ||||||
|  | 	return map[T]new_gtsmodel.Visibility{ | ||||||
|  | 		T(old_gtsmodel.VisibilityNone):          new_gtsmodel.VisibilityNone, | ||||||
|  | 		T(old_gtsmodel.VisibilityPublic):        new_gtsmodel.VisibilityPublic, | ||||||
|  | 		T(old_gtsmodel.VisibilityUnlocked):      new_gtsmodel.VisibilityUnlocked, | ||||||
|  | 		T(old_gtsmodel.VisibilityFollowersOnly): new_gtsmodel.VisibilityFollowersOnly, | ||||||
|  | 		T(old_gtsmodel.VisibilityMutualsOnly):   new_gtsmodel.VisibilityMutualsOnly, | ||||||
|  | 		T(old_gtsmodel.VisibilityDirect):        new_gtsmodel.VisibilityDirect, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // notificationEnumMapping maps old NotificationType enum values to their newer integer type. | ||||||
|  | func notificationEnumMapping[T ~string]() map[T]new_gtsmodel.NotificationType { | ||||||
|  | 	return map[T]new_gtsmodel.NotificationType{ | ||||||
|  | 		T(old_gtsmodel.NotificationFollow):        new_gtsmodel.NotificationFollow, | ||||||
|  | 		T(old_gtsmodel.NotificationFollowRequest): new_gtsmodel.NotificationFollowRequest, | ||||||
|  | 		T(old_gtsmodel.NotificationMention):       new_gtsmodel.NotificationMention, | ||||||
|  | 		T(old_gtsmodel.NotificationReblog):        new_gtsmodel.NotificationReblog, | ||||||
|  | 		T(old_gtsmodel.NotificationFave):          new_gtsmodel.NotificationFave, | ||||||
|  | 		T(old_gtsmodel.NotificationPoll):          new_gtsmodel.NotificationPoll, | ||||||
|  | 		T(old_gtsmodel.NotificationStatus):        new_gtsmodel.NotificationStatus, | ||||||
|  | 		T(old_gtsmodel.NotificationSignup):        new_gtsmodel.NotificationSignup, | ||||||
|  | 		T(old_gtsmodel.NotificationPendingFave):   new_gtsmodel.NotificationPendingFave, | ||||||
|  | 		T(old_gtsmodel.NotificationPendingReply):  new_gtsmodel.NotificationPendingReply, | ||||||
|  | 		T(old_gtsmodel.NotificationPendingReblog): new_gtsmodel.NotificationPendingReblog, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -0,0 +1,45 @@ | ||||||
|  | // 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 gtsmodel | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // 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:3"`                                         // 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. | ||||||
|  | 	WebVisibility                  Visibility                  `bun:",nullzero,notnull,default:3"`                                 // Visibility level of statuses that visitors can view via the web profile. | ||||||
|  | 	InteractionPolicyDirect        *gtsmodel.InteractionPolicy `bun:""`                                                            // Interaction policy to use for new direct visibility statuses by this account. If null, assume default policy. | ||||||
|  | 	InteractionPolicyMutualsOnly   *gtsmodel.InteractionPolicy `bun:""`                                                            // Interaction policy to use for new mutuals only visibility statuses. If null, assume default policy. | ||||||
|  | 	InteractionPolicyFollowersOnly *gtsmodel.InteractionPolicy `bun:""`                                                            // Interaction policy to use for new followers only visibility statuses. If null, assume default policy. | ||||||
|  | 	InteractionPolicyUnlocked      *gtsmodel.InteractionPolicy `bun:""`                                                            // Interaction policy to use for new unlocked visibility statuses. If null, assume default policy. | ||||||
|  | 	InteractionPolicyPublic        *gtsmodel.InteractionPolicy `bun:""`                                                            // Interaction policy to use for new public visibility statuses. If null, assume default policy. | ||||||
|  | } | ||||||
|  | @ -0,0 +1,57 @@ | ||||||
|  | // 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 gtsmodel | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Notification models an alert/notification sent to an account about something like a reblog, like, new follow request, etc. | ||||||
|  | type Notification 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 | ||||||
|  | 	NotificationType NotificationType  `bun:",nullzero,notnull"`                                           // Type of this notification | ||||||
|  | 	TargetAccountID  string            `bun:"type:CHAR(26),nullzero,notnull"`                              // ID of the account targeted by the notification (ie., who will receive the notification?) | ||||||
|  | 	TargetAccount    *gtsmodel.Account `bun:"-"`                                                           // Account corresponding to TargetAccountID. Can be nil, always check first + select using ID if necessary. | ||||||
|  | 	OriginAccountID  string            `bun:"type:CHAR(26),nullzero,notnull"`                              // ID of the account that performed the action that created the notification. | ||||||
|  | 	OriginAccount    *gtsmodel.Account `bun:"-"`                                                           // Account corresponding to OriginAccountID. Can be nil, always check first + select using ID if necessary. | ||||||
|  | 	StatusID         string            `bun:"type:CHAR(26),nullzero"`                                      // If the notification pertains to a status, what is the database ID of that status? | ||||||
|  | 	Status           *Status           `bun:"-"`                                                           // Status corresponding to StatusID. Can be nil, always check first + select using ID if necessary. | ||||||
|  | 	Read             *bool             `bun:",nullzero,notnull,default:false"`                             // Notification has been seen/read | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NotificationType describes the reason/type of this notification. | ||||||
|  | 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. | ||||||
|  | 	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. | ||||||
|  | ) | ||||||
|  | @ -0,0 +1,45 @@ | ||||||
|  | // 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 gtsmodel | ||||||
|  | 
 | ||||||
|  | import "time" | ||||||
|  | 
 | ||||||
|  | // SinBinStatus represents a status that's been rejected and/or reported + quarantined. | ||||||
|  | // | ||||||
|  | // Automatically rejected statuses are not put in the sin bin, only statuses that were | ||||||
|  | // stored on the instance and which someone (local or remote) has subsequently rejected. | ||||||
|  | type SinBinStatus 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"` // Creation time of this item. | ||||||
|  | 	UpdatedAt           time.Time  `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // Last-updated time of this item. | ||||||
|  | 	URI                 string     `bun:",unique,nullzero,notnull"`                                    // ActivityPub URI/ID of this status. | ||||||
|  | 	URL                 string     `bun:",nullzero"`                                                   // Web url for viewing this status. | ||||||
|  | 	Domain              string     `bun:",nullzero"`                                                   // Domain of the status, will be null if this is a local status, otherwise something like `example.org`. | ||||||
|  | 	AccountURI          string     `bun:",nullzero,notnull"`                                           // ActivityPub uri of the author of this status. | ||||||
|  | 	InReplyToURI        string     `bun:",nullzero"`                                                   // ActivityPub uri of the status this status is a reply to. | ||||||
|  | 	Content             string     `bun:",nullzero"`                                                   // Content of this status. | ||||||
|  | 	AttachmentLinks     []string   `bun:",nullzero,array"`                                             // Links to attachments of this status. | ||||||
|  | 	MentionTargetURIs   []string   `bun:",nullzero,array"`                                             // URIs of mentioned accounts. | ||||||
|  | 	EmojiLinks          []string   `bun:",nullzero,array"`                                             // Links to any emoji images used in this status. | ||||||
|  | 	PollOptions         []string   `bun:",nullzero,array"`                                             // String values of any poll options used in this status. | ||||||
|  | 	ContentWarning      string     `bun:",nullzero"`                                                   // CW / subject string for this status. | ||||||
|  | 	Visibility          Visibility `bun:",nullzero,notnull"`                                           // Visibility level of this status. | ||||||
|  | 	Sensitive           *bool      `bun:",nullzero,notnull,default:false"`                             // Mark the status as sensitive. | ||||||
|  | 	Language            string     `bun:",nullzero"`                                                   // Language code for this status. | ||||||
|  | 	ActivityStreamsType string     `bun:",nullzero,notnull"`                                           // ActivityStreams type of this status. | ||||||
|  | } | ||||||
|  | @ -0,0 +1,95 @@ | ||||||
|  | // 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 gtsmodel | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Status represents a user-created 'post' or 'status' in the database, either remote or local | ||||||
|  | type Status 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 | ||||||
|  | 	FetchedAt                time.Time                   `bun:"type:timestamptz,nullzero"`                                   // when was item (remote) last fetched. | ||||||
|  | 	PinnedAt                 time.Time                   `bun:"type:timestamptz,nullzero"`                                   // Status was pinned by owning account at this time. | ||||||
|  | 	URI                      string                      `bun:",unique,nullzero,notnull"`                                    // activitypub URI of this status | ||||||
|  | 	URL                      string                      `bun:",nullzero"`                                                   // web url for viewing this status | ||||||
|  | 	Content                  string                      `bun:""`                                                            // content of this status; likely html-formatted but not guaranteed | ||||||
|  | 	AttachmentIDs            []string                    `bun:"attachments,array"`                                           // Database IDs of any media attachments associated with this status | ||||||
|  | 	Attachments              []*gtsmodel.MediaAttachment `bun:"attached_media,rel:has-many"`                                 // Attachments corresponding to attachmentIDs | ||||||
|  | 	TagIDs                   []string                    `bun:"tags,array"`                                                  // Database IDs of any tags used in this status | ||||||
|  | 	Tags                     []*gtsmodel.Tag             `bun:"attached_tags,m2m:status_to_tags"`                            // Tags corresponding to tagIDs. https://bun.uptrace.dev/guide/relations.html#many-to-many-relation | ||||||
|  | 	MentionIDs               []string                    `bun:"mentions,array"`                                              // Database IDs of any mentions in this status | ||||||
|  | 	Mentions                 []*gtsmodel.Mention         `bun:"attached_mentions,rel:has-many"`                              // Mentions corresponding to mentionIDs | ||||||
|  | 	EmojiIDs                 []string                    `bun:"emojis,array"`                                                // Database IDs of any emojis used in this status | ||||||
|  | 	Emojis                   []*gtsmodel.Emoji           `bun:"attached_emojis,m2m:status_to_emojis"`                        // Emojis corresponding to emojiIDs. https://bun.uptrace.dev/guide/relations.html#many-to-many-relation | ||||||
|  | 	Local                    *bool                       `bun:",nullzero,notnull,default:false"`                             // is this status from a local account? | ||||||
|  | 	AccountID                string                      `bun:"type:CHAR(26),nullzero,notnull"`                              // which account posted this status? | ||||||
|  | 	Account                  *gtsmodel.Account           `bun:"rel:belongs-to"`                                              // account corresponding to accountID | ||||||
|  | 	AccountURI               string                      `bun:",nullzero,notnull"`                                           // activitypub uri of the owner of this status | ||||||
|  | 	InReplyToID              string                      `bun:"type:CHAR(26),nullzero"`                                      // id of the status this status replies to | ||||||
|  | 	InReplyToURI             string                      `bun:",nullzero"`                                                   // activitypub uri of the status this status is a reply to | ||||||
|  | 	InReplyToAccountID       string                      `bun:"type:CHAR(26),nullzero"`                                      // id of the account that this status replies to | ||||||
|  | 	InReplyTo                *Status                     `bun:"-"`                                                           // status corresponding to inReplyToID | ||||||
|  | 	InReplyToAccount         *gtsmodel.Account           `bun:"rel:belongs-to"`                                              // account corresponding to inReplyToAccountID | ||||||
|  | 	BoostOfID                string                      `bun:"type:CHAR(26),nullzero"`                                      // id of the status this status is a boost of | ||||||
|  | 	BoostOfURI               string                      `bun:"-"`                                                           // URI of the status this status is a boost of; field not inserted in the db, just for dereferencing purposes. | ||||||
|  | 	BoostOfAccountID         string                      `bun:"type:CHAR(26),nullzero"`                                      // id of the account that owns the boosted status | ||||||
|  | 	BoostOf                  *Status                     `bun:"-"`                                                           // status that corresponds to boostOfID | ||||||
|  | 	BoostOfAccount           *gtsmodel.Account           `bun:"rel:belongs-to"`                                              // account that corresponds to boostOfAccountID | ||||||
|  | 	ThreadID                 string                      `bun:"type:CHAR(26),nullzero"`                                      // id of the thread to which this status belongs; only set for remote statuses if a local account is involved at some point in the thread, otherwise null | ||||||
|  | 	PollID                   string                      `bun:"type:CHAR(26),nullzero"`                                      // | ||||||
|  | 	Poll                     *gtsmodel.Poll              `bun:"-"`                                                           // | ||||||
|  | 	ContentWarning           string                      `bun:",nullzero"`                                                   // cw string for this status | ||||||
|  | 	Visibility               Visibility                  `bun:",nullzero,notnull"`                                           // visibility entry for this status | ||||||
|  | 	Sensitive                *bool                       `bun:",nullzero,notnull,default:false"`                             // mark the status as sensitive? | ||||||
|  | 	Language                 string                      `bun:",nullzero"`                                                   // what language is this status written in? | ||||||
|  | 	CreatedWithApplicationID string                      `bun:"type:CHAR(26),nullzero"`                                      // Which application was used to create this status? | ||||||
|  | 	CreatedWithApplication   *gtsmodel.Application       `bun:"rel:belongs-to"`                                              // application corresponding to createdWithApplicationID | ||||||
|  | 	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) | ||||||
|  | 	InteractionPolicy        *gtsmodel.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. | ||||||
|  | 	PreApproved              bool                        `bun:"-"`                                                           // If true, then status is a reply to or boost wrapper of a status on our instance, has permission to do the interaction, and an Accept should be sent out for it immediately. Field not stored in the DB. | ||||||
|  | 	ApprovedByURI            string                      `bun:",nullzero"`                                                   // URI of an Accept Activity that approves the Announce or Create Activity that this status was/will be attached to. | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Visibility represents the visibility granularity of a status. | ||||||
|  | type Visibility string | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	// VisibilityNone means nobody can see this. | ||||||
|  | 	// It's only used for web status visibility. | ||||||
|  | 	VisibilityNone Visibility = "none" | ||||||
|  | 	// VisibilityPublic means this status will be visible to everyone on all timelines. | ||||||
|  | 	VisibilityPublic Visibility = "public" | ||||||
|  | 	// VisibilityUnlocked means this status will be visible to everyone, but will only show on home timeline to followers, and in lists. | ||||||
|  | 	VisibilityUnlocked Visibility = "unlocked" | ||||||
|  | 	// VisibilityFollowersOnly means this status is viewable to followers only. | ||||||
|  | 	VisibilityFollowersOnly Visibility = "followers_only" | ||||||
|  | 	// VisibilityMutualsOnly means this status is visible to mutual followers only. | ||||||
|  | 	VisibilityMutualsOnly Visibility = "mutuals_only" | ||||||
|  | 	// VisibilityDirect means this status is visible only to mentioned recipients. | ||||||
|  | 	VisibilityDirect Visibility = "direct" | ||||||
|  | 	// VisibilityDefault is used when no other setting can be found. | ||||||
|  | 	VisibilityDefault Visibility = VisibilityUnlocked | ||||||
|  | ) | ||||||
|  | @ -196,8 +196,8 @@ func (n *notificationDB) GetAccountNotifications( | ||||||
| 	sinceID string, | 	sinceID string, | ||||||
| 	minID string, | 	minID string, | ||||||
| 	limit int, | 	limit int, | ||||||
| 	types []string, | 	types []gtsmodel.NotificationType, | ||||||
| 	excludeTypes []string, | 	excludeTypes []gtsmodel.NotificationType, | ||||||
| ) ([]*gtsmodel.Notification, error) { | ) ([]*gtsmodel.Notification, error) { | ||||||
| 	// Ensure reasonable | 	// Ensure reasonable | ||||||
| 	if limit < 0 { | 	if limit < 0 { | ||||||
|  | @ -303,7 +303,7 @@ func (n *notificationDB) DeleteNotificationByID(ctx context.Context, id string) | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (n *notificationDB) DeleteNotifications(ctx context.Context, types []string, targetAccountID string, originAccountID string) error { | func (n *notificationDB) DeleteNotifications(ctx context.Context, types []gtsmodel.NotificationType, targetAccountID string, originAccountID string) error { | ||||||
| 	if targetAccountID == "" && originAccountID == "" { | 	if targetAccountID == "" && originAccountID == "" { | ||||||
| 		return gtserror.New("one of targetAccountID or originAccountID must be set") | 		return gtserror.New("one of targetAccountID or originAccountID must be set") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -265,8 +265,8 @@ func (r *relationshipDB) AcceptFollowRequest(ctx context.Context, sourceAccountI | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Delete original follow request notification | 	// Delete original follow request notification | ||||||
| 	if err := r.state.DB.DeleteNotifications(ctx, []string{ | 	if err := r.state.DB.DeleteNotifications(ctx, []gtsmodel.NotificationType{ | ||||||
| 		string(gtsmodel.NotificationFollowRequest), | 		gtsmodel.NotificationFollowRequest, | ||||||
| 	}, targetAccountID, sourceAccountID); err != nil { | 	}, targetAccountID, sourceAccountID); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  | @ -281,8 +281,8 @@ func (r *relationshipDB) RejectFollowRequest(ctx context.Context, sourceAccountI | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Delete follow request notification | 	// Delete follow request notification | ||||||
| 	return r.state.DB.DeleteNotifications(ctx, []string{ | 	return r.state.DB.DeleteNotifications(ctx, []gtsmodel.NotificationType{ | ||||||
| 		string(gtsmodel.NotificationFollowRequest), | 		gtsmodel.NotificationFollowRequest, | ||||||
| 	}, targetAccountID, sourceAccountID) | 	}, targetAccountID, sourceAccountID) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -29,7 +29,7 @@ type Notification interface { | ||||||
| 	// | 	// | ||||||
| 	// Returned notifications will be ordered ID descending (ie., highest/newest to lowest/oldest). | 	// Returned notifications will be ordered ID descending (ie., highest/newest to lowest/oldest). | ||||||
| 	// If types is empty, *all* notification types will be included. | 	// If types is empty, *all* notification types will be included. | ||||||
| 	GetAccountNotifications(ctx context.Context, accountID string, maxID string, sinceID string, minID string, limit int, types []string, excludeTypes []string) ([]*gtsmodel.Notification, error) | 	GetAccountNotifications(ctx context.Context, accountID string, maxID string, sinceID string, minID string, limit int, types []gtsmodel.NotificationType, excludeTypes []gtsmodel.NotificationType) ([]*gtsmodel.Notification, error) | ||||||
| 
 | 
 | ||||||
| 	// GetNotificationByID returns one notification according to its id. | 	// GetNotificationByID returns one notification according to its id. | ||||||
| 	GetNotificationByID(ctx context.Context, id string) (*gtsmodel.Notification, error) | 	GetNotificationByID(ctx context.Context, id string) (*gtsmodel.Notification, error) | ||||||
|  | @ -64,7 +64,7 @@ type Notification interface { | ||||||
| 	// originate from originAccountID will be deleted. | 	// originate from originAccountID will be deleted. | ||||||
| 	// | 	// | ||||||
| 	// At least one parameter must not be an empty string. | 	// At least one parameter must not be an empty string. | ||||||
| 	DeleteNotifications(ctx context.Context, types []string, targetAccountID string, originAccountID string) error | 	DeleteNotifications(ctx context.Context, types []gtsmodel.NotificationType, targetAccountID string, originAccountID string) error | ||||||
| 
 | 
 | ||||||
| 	// DeleteNotificationsForStatus deletes all notifications that relate to | 	// DeleteNotificationsForStatus deletes all notifications that relate to | ||||||
| 	// the given statusID. This function is useful when a status has been deleted, | 	// the given statusID. This function is useful when a status has been deleted, | ||||||
|  |  | ||||||
|  | @ -26,7 +26,7 @@ type AccountSettings struct { | ||||||
| 	AccountID                      string             `bun:"type:CHAR(26),pk,nullzero,notnull,unique"`                    // AccountID that owns this settings. | 	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. | 	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. | 	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 | 	Privacy                        Visibility         `bun:",nullzero,default:3"`                                         // Default post privacy for this account | ||||||
| 	Sensitive                      *bool              `bun:",nullzero,notnull,default:false"`                             // Set posts from this account to sensitive by default? | 	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? | 	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). | 	StatusContentType              string             `bun:",nullzero"`                                                   // What is the default format for statuses posted by this account (only for local accounts). | ||||||
|  | @ -34,7 +34,7 @@ type AccountSettings struct { | ||||||
| 	CustomCSS                      string             `bun:",nullzero"`                                                   // Custom CSS that should be displayed for this Account's profile and statuses. | 	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 | 	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. | 	HideCollections                *bool              `bun:",nullzero,notnull,default:false"`                             // Hide this account's followers/following collections. | ||||||
| 	WebVisibility                  Visibility         `bun:",nullzero,notnull,default:public"`                            // Visibility level of statuses that visitors can view via the web profile. | 	WebVisibility                  Visibility         `bun:",nullzero,notnull,default:3"`                                 // Visibility level of statuses that visitors can view via the web profile. | ||||||
| 	InteractionPolicyDirect        *InteractionPolicy `bun:""`                                                            // Interaction policy to use for new direct visibility statuses by this account. If null, assume default policy. | 	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. | 	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. | 	InteractionPolicyFollowersOnly *InteractionPolicy `bun:""`                                                            // Interaction policy to use for new followers only visibility statuses. If null, assume default policy. | ||||||
|  |  | ||||||
							
								
								
									
										24
									
								
								internal/gtsmodel/common.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								internal/gtsmodel/common.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,24 @@ | ||||||
|  | // 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 gtsmodel | ||||||
|  | 
 | ||||||
|  | // enumType is the type we (at least, should) use | ||||||
|  | // for database enum types. it is the largest size | ||||||
|  | // supported by a PostgreSQL SMALLINT, since an | ||||||
|  | // SQLite SMALLINT is actually variable in size. | ||||||
|  | type enumType int16 | ||||||
|  | @ -224,7 +224,7 @@ func DefaultInteractionPolicyFor(v Visibility) *InteractionPolicy { | ||||||
| 	case VisibilityDirect: | 	case VisibilityDirect: | ||||||
| 		return DefaultInteractionPolicyDirect() | 		return DefaultInteractionPolicyDirect() | ||||||
| 	default: | 	default: | ||||||
| 		panic("visibility " + v + " not recognized") | 		panic("invalid visibility") | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -34,20 +34,51 @@ type Notification struct { | ||||||
| 	Read             *bool            `bun:",nullzero,notnull,default:false"`                             // Notification has been seen/read | 	Read             *bool            `bun:",nullzero,notnull,default:false"`                             // Notification has been seen/read | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NotificationType describes the reason/type of this notification. | // NotificationType describes the | ||||||
| type NotificationType string | // reason/type of this notification. | ||||||
|  | type NotificationType enumType | ||||||
| 
 | 
 | ||||||
| // Notification Types |  | ||||||
| const ( | const ( | ||||||
| 	NotificationFollow        NotificationType = "follow"            // NotificationFollow -- someone followed you | 	// Notification Types | ||||||
| 	NotificationFollowRequest NotificationType = "follow_request"    // NotificationFollowRequest -- someone requested to follow you | 	NotificationFollow        NotificationType = 1  // NotificationFollow -- someone followed you | ||||||
| 	NotificationMention       NotificationType = "mention"           // NotificationMention -- someone mentioned you in their status | 	NotificationFollowRequest NotificationType = 2  // NotificationFollowRequest -- someone requested to follow you | ||||||
| 	NotificationReblog        NotificationType = "reblog"            // NotificationReblog -- someone boosted one of your statuses | 	NotificationMention       NotificationType = 3  // NotificationMention -- someone mentioned you in their status | ||||||
| 	NotificationFave          NotificationType = "favourite"         // NotificationFave -- someone faved/liked one of your statuses | 	NotificationReblog        NotificationType = 4  // NotificationReblog -- someone boosted one of your statuses | ||||||
| 	NotificationPoll          NotificationType = "poll"              // NotificationPoll -- a poll you voted in or created has ended | 	NotificationFave          NotificationType = 5  // NotificationFave -- someone faved/liked one of your statuses | ||||||
| 	NotificationStatus        NotificationType = "status"            // NotificationStatus -- someone you enabled notifications for has posted a status. | 	NotificationPoll          NotificationType = 6  // NotificationPoll -- a poll you voted in or created has ended | ||||||
| 	NotificationSignup        NotificationType = "admin.sign_up"     // NotificationSignup -- someone has submitted a new account sign-up to the instance. | 	NotificationStatus        NotificationType = 7  // NotificationStatus -- someone you enabled notifications for has posted a status. | ||||||
| 	NotificationPendingFave   NotificationType = "pending.favourite" // Someone has faved a status of yours, which requires approval by you. | 	NotificationSignup        NotificationType = 8  // NotificationSignup -- someone has submitted a new account sign-up to the instance. | ||||||
| 	NotificationPendingReply  NotificationType = "pending.reply"     // Someone has replied to a status of yours, which requires approval by you. | 	NotificationPendingFave   NotificationType = 9  // Someone has faved 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. | 	NotificationPendingReply  NotificationType = 10 // Someone has replied to a status of yours, which requires approval by you. | ||||||
|  | 	NotificationPendingReblog NotificationType = 11 // Someone has boosted a status of yours, which requires approval by you. | ||||||
| ) | ) | ||||||
|  | 
 | ||||||
|  | // String returns a stringified, frontend API compatible form of NotificationType. | ||||||
|  | func (t NotificationType) String() string { | ||||||
|  | 	switch t { | ||||||
|  | 	case NotificationFollow: | ||||||
|  | 		return "follow" | ||||||
|  | 	case NotificationFollowRequest: | ||||||
|  | 		return "follow_request" | ||||||
|  | 	case NotificationMention: | ||||||
|  | 		return "mention" | ||||||
|  | 	case NotificationReblog: | ||||||
|  | 		return "reblog" | ||||||
|  | 	case NotificationFave: | ||||||
|  | 		return "favourite" | ||||||
|  | 	case NotificationPoll: | ||||||
|  | 		return "poll" | ||||||
|  | 	case NotificationStatus: | ||||||
|  | 		return "status" | ||||||
|  | 	case NotificationSignup: | ||||||
|  | 		return "admin.sign_up" | ||||||
|  | 	case NotificationPendingFave: | ||||||
|  | 		return "pending.favourite" | ||||||
|  | 	case NotificationPendingReply: | ||||||
|  | 		return "pending.reply" | ||||||
|  | 	case NotificationPendingReblog: | ||||||
|  | 		return "pending.reblog" | ||||||
|  | 	default: | ||||||
|  | 		panic("invalid notification type") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -263,27 +263,58 @@ type StatusToEmoji struct { | ||||||
| 	Emoji    *Emoji  `bun:"rel:belongs-to"` | 	Emoji    *Emoji  `bun:"rel:belongs-to"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Visibility represents the visibility granularity of a status. | // Visibility represents the | ||||||
| type Visibility string | // visibility granularity of a status. | ||||||
|  | type Visibility enumType | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
| 	// VisibilityNone means nobody can see this. | 	// VisibilityNone means nobody can see this. | ||||||
| 	// It's only used for web status visibility. | 	// It's only used for web status visibility. | ||||||
| 	VisibilityNone Visibility = "none" | 	VisibilityNone Visibility = 1 | ||||||
| 	// VisibilityPublic means this status will be visible to everyone on all timelines. | 
 | ||||||
| 	VisibilityPublic Visibility = "public" | 	// VisibilityPublic means this status will | ||||||
| 	// VisibilityUnlocked means this status will be visible to everyone, but will only show on home timeline to followers, and in lists. | 	// be visible to everyone on all timelines. | ||||||
| 	VisibilityUnlocked Visibility = "unlocked" | 	VisibilityPublic Visibility = 2 | ||||||
|  | 
 | ||||||
|  | 	// VisibilityUnlocked means this status will be visible to everyone, | ||||||
|  | 	// but will only show on home timeline to followers, and in lists. | ||||||
|  | 	VisibilityUnlocked Visibility = 3 | ||||||
|  | 
 | ||||||
| 	// VisibilityFollowersOnly means this status is viewable to followers only. | 	// VisibilityFollowersOnly means this status is viewable to followers only. | ||||||
| 	VisibilityFollowersOnly Visibility = "followers_only" | 	VisibilityFollowersOnly Visibility = 4 | ||||||
| 	// VisibilityMutualsOnly means this status is visible to mutual followers only. | 
 | ||||||
| 	VisibilityMutualsOnly Visibility = "mutuals_only" | 	// VisibilityMutualsOnly means this status | ||||||
| 	// VisibilityDirect means this status is visible only to mentioned recipients. | 	// is visible to mutual followers only. | ||||||
| 	VisibilityDirect Visibility = "direct" | 	VisibilityMutualsOnly Visibility = 5 | ||||||
|  | 
 | ||||||
|  | 	// VisibilityDirect means this status is | ||||||
|  | 	// visible only to mentioned recipients. | ||||||
|  | 	VisibilityDirect Visibility = 6 | ||||||
|  | 
 | ||||||
| 	// VisibilityDefault is used when no other setting can be found. | 	// VisibilityDefault is used when no other setting can be found. | ||||||
| 	VisibilityDefault Visibility = VisibilityUnlocked | 	VisibilityDefault Visibility = VisibilityUnlocked | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | // String returns a stringified, frontend API compatible form of Visibility. | ||||||
|  | func (v Visibility) String() string { | ||||||
|  | 	switch v { | ||||||
|  | 	case VisibilityNone: | ||||||
|  | 		return "none" | ||||||
|  | 	case VisibilityPublic: | ||||||
|  | 		return "public" | ||||||
|  | 	case VisibilityUnlocked: | ||||||
|  | 		return "unlocked" | ||||||
|  | 	case VisibilityFollowersOnly: | ||||||
|  | 		return "followers_only" | ||||||
|  | 	case VisibilityMutualsOnly: | ||||||
|  | 		return "mutuals_only" | ||||||
|  | 	case VisibilityDirect: | ||||||
|  | 		return "direct" | ||||||
|  | 	default: | ||||||
|  | 		panic("invalid visibility") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // Content models the simple string content | // Content models the simple string content | ||||||
| // of a status along with its ContentMap, | // of a status along with its ContentMap, | ||||||
| // which contains content entries keyed by | // which contains content entries keyed by | ||||||
|  |  | ||||||
|  | @ -46,7 +46,7 @@ func (p *Processor) PreferencesGet(ctx context.Context, accountID string) (*apim | ||||||
| func mastoPrefVisibility(vis gtsmodel.Visibility) string { | func mastoPrefVisibility(vis gtsmodel.Visibility) string { | ||||||
| 	switch vis { | 	switch vis { | ||||||
| 	case gtsmodel.VisibilityPublic, gtsmodel.VisibilityDirect: | 	case gtsmodel.VisibilityPublic, gtsmodel.VisibilityDirect: | ||||||
| 		return string(vis) | 		return vis.String() | ||||||
| 	case gtsmodel.VisibilityUnlocked: | 	case gtsmodel.VisibilityUnlocked: | ||||||
| 		return "unlisted" | 		return "unlisted" | ||||||
| 	default: | 	default: | ||||||
|  |  | ||||||
|  | @ -372,7 +372,7 @@ func (p *Processor) processVisibility( | ||||||
| 
 | 
 | ||||||
| 	// Fall back to account default, set | 	// Fall back to account default, set | ||||||
| 	// this back on the form for later use. | 	// this back on the form for later use. | ||||||
| 	case accountDefaultVis != "": | 	case accountDefaultVis != 0: | ||||||
| 		status.Visibility = accountDefaultVis | 		status.Visibility = accountDefaultVis | ||||||
| 		form.Visibility = p.converter.VisToAPIVis(ctx, accountDefaultVis) | 		form.Visibility = p.converter.VisToAPIVis(ctx, accountDefaultVis) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -41,8 +41,8 @@ func (p *Processor) NotificationsGet( | ||||||
| 	sinceID string, | 	sinceID string, | ||||||
| 	minID string, | 	minID string, | ||||||
| 	limit int, | 	limit int, | ||||||
| 	types []string, | 	types []gtsmodel.NotificationType, | ||||||
| 	excludeTypes []string, | 	excludeTypes []gtsmodel.NotificationType, | ||||||
| ) (*apimodel.PageableResponse, gtserror.WithCode) { | ) (*apimodel.PageableResponse, gtserror.WithCode) { | ||||||
| 	notifs, err := p.state.DB.GetAccountNotifications( | 	notifs, err := p.state.DB.GetAccountNotifications( | ||||||
| 		ctx, | 		ctx, | ||||||
|  |  | ||||||
|  | @ -542,7 +542,7 @@ func getNotifyLockURI( | ||||||
| ) string { | ) string { | ||||||
| 	builder := strings.Builder{} | 	builder := strings.Builder{} | ||||||
| 	builder.WriteString("notification:?") | 	builder.WriteString("notification:?") | ||||||
| 	builder.WriteString("type=" + string(notificationType)) | 	builder.WriteString("type=" + notificationType.String()) | ||||||
| 	builder.WriteString("&target=" + targetAccount.URI) | 	builder.WriteString("&target=" + targetAccount.URI) | ||||||
| 	builder.WriteString("&origin=" + originAccount.URI) | 	builder.WriteString("&origin=" + originAccount.URI) | ||||||
| 	if statusID != "" { | 	if statusID != "" { | ||||||
|  |  | ||||||
|  | @ -41,7 +41,7 @@ func APIVisToVis(m apimodel.Visibility) gtsmodel.Visibility { | ||||||
| 	case apimodel.VisibilityNone: | 	case apimodel.VisibilityNone: | ||||||
| 		return gtsmodel.VisibilityNone | 		return gtsmodel.VisibilityNone | ||||||
| 	} | 	} | ||||||
| 	return "" | 	return 0 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func APIMarkerNameToMarkerName(m apimodel.MarkerName) gtsmodel.MarkerName { | func APIMarkerNameToMarkerName(m apimodel.MarkerName) gtsmodel.MarkerName { | ||||||
|  |  | ||||||
|  | @ -1862,7 +1862,7 @@ func (c *Converter) NotificationToAPINotification( | ||||||
| 
 | 
 | ||||||
| 	return &apimodel.Notification{ | 	return &apimodel.Notification{ | ||||||
| 		ID:        n.ID, | 		ID:        n.ID, | ||||||
| 		Type:      string(n.NotificationType), | 		Type:      n.NotificationType.String(), | ||||||
| 		CreatedAt: util.FormatISO8601(n.CreatedAt), | 		CreatedAt: util.FormatISO8601(n.CreatedAt), | ||||||
| 		Account:   apiAccount, | 		Account:   apiAccount, | ||||||
| 		Status:    apiStatus, | 		Status:    apiStatus, | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue