mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-12-07 13:08:09 -06:00
Merge remote-tracking branch 'origin/main' into HEAD
This commit is contained in:
commit
0e137c0f2d
1759 changed files with 864109 additions and 314186 deletions
|
|
@ -77,7 +77,7 @@ func init() {
|
|||
UpdatedAt: oldAction.UpdatedAt,
|
||||
TargetCategory: gtsmodel.AdminActionCategoryAccount,
|
||||
TargetID: oldAction.TargetAccountID,
|
||||
Type: gtsmodel.NewAdminActionType(string(oldAction.Type)),
|
||||
Type: gtsmodel.ParseAdminActionType(string(oldAction.Type)),
|
||||
AccountID: oldAction.AccountID,
|
||||
Text: oldAction.Text,
|
||||
SendEmail: util.Ptr(oldAction.SendEmail),
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ import (
|
|||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
gtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20231002153327_add_status_polls"
|
||||
|
||||
"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
|
||||
)
|
||||
|
|
@ -20,7 +20,7 @@ package migrations
|
|||
import (
|
||||
"context"
|
||||
|
||||
gtsmodel "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
gtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20240126064004_add_filters"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
// 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 (
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Filter stores a filter created by a local account.
|
||||
type Filter 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
|
||||
ExpiresAt time.Time `bun:"type:timestamptz,nullzero"` // Time filter should expire. If null, should not expire.
|
||||
AccountID string `bun:"type:CHAR(26),notnull,nullzero"` // ID of the local account that created the filter.
|
||||
Title string `bun:",nullzero,notnull,unique"` // The name of the filter.
|
||||
Action string `bun:",nullzero,notnull"` // The action to take.
|
||||
Keywords []*FilterKeyword `bun:"-"` // Keywords for this filter.
|
||||
Statuses []*FilterStatus `bun:"-"` // Statuses for this filter.
|
||||
ContextHome *bool `bun:",nullzero,notnull,default:false"` // Apply filter to home timeline and lists.
|
||||
ContextNotifications *bool `bun:",nullzero,notnull,default:false"` // Apply filter to notifications.
|
||||
ContextPublic *bool `bun:",nullzero,notnull,default:false"` // Apply filter to home timeline and lists.
|
||||
ContextThread *bool `bun:",nullzero,notnull,default:false"` // Apply filter when viewing a status's associated thread.
|
||||
ContextAccount *bool `bun:",nullzero,notnull,default:false"` // Apply filter when viewing an account profile.
|
||||
}
|
||||
|
||||
// FilterKeyword stores a single keyword to filter statuses against.
|
||||
type FilterKeyword 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
|
||||
AccountID string `bun:"type:CHAR(26),notnull,nullzero"` // ID of the local account that created the filter keyword.
|
||||
FilterID string `bun:"type:CHAR(26),notnull,nullzero,unique:filter_keywords_filter_id_keyword_uniq"` // ID of the filter that this keyword belongs to.
|
||||
Filter *Filter `bun:"-"` // Filter corresponding to FilterID
|
||||
Keyword string `bun:",nullzero,notnull,unique:filter_keywords_filter_id_keyword_uniq"` // The keyword or phrase to filter against.
|
||||
WholeWord *bool `bun:",nullzero,notnull,default:false"` // Should the filter consider word boundaries?
|
||||
Regexp *regexp.Regexp `bun:"-"` // pre-prepared regular expression
|
||||
}
|
||||
|
||||
// FilterStatus stores a single status to filter.
|
||||
type FilterStatus 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
|
||||
AccountID string `bun:"type:CHAR(26),notnull,nullzero"` // ID of the local account that created the filter keyword.
|
||||
FilterID string `bun:"type:CHAR(26),notnull,nullzero,unique:filter_statuses_filter_id_status_id_uniq"` // ID of the filter that this keyword belongs to.
|
||||
Filter *Filter `bun:"-"` // Filter corresponding to FilterID
|
||||
StatusID string `bun:"type:CHAR(26),notnull,nullzero,unique:filter_statuses_filter_id_status_id_uniq"` // ID of the status to filter.
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@ func init() {
|
|||
table string
|
||||
column string
|
||||
columnType string
|
||||
defaultVal string
|
||||
extra string
|
||||
}
|
||||
for _, spec := range []spec{
|
||||
// Statuses.
|
||||
|
|
@ -48,19 +48,19 @@ func init() {
|
|||
table: "statuses",
|
||||
column: "interaction_policy",
|
||||
columnType: "JSONB",
|
||||
defaultVal: "",
|
||||
extra: "",
|
||||
},
|
||||
{
|
||||
table: "statuses",
|
||||
column: "pending_approval",
|
||||
columnType: "BOOLEAN",
|
||||
defaultVal: "DEFAULT false",
|
||||
extra: "NOT NULL DEFAULT false",
|
||||
},
|
||||
{
|
||||
table: "statuses",
|
||||
column: "approved_by_uri",
|
||||
columnType: "varchar",
|
||||
defaultVal: "",
|
||||
extra: "",
|
||||
},
|
||||
|
||||
// Status faves.
|
||||
|
|
@ -68,13 +68,13 @@ func init() {
|
|||
table: "status_faves",
|
||||
column: "pending_approval",
|
||||
columnType: "BOOLEAN",
|
||||
defaultVal: "DEFAULT false",
|
||||
extra: "NOT NULL DEFAULT false",
|
||||
},
|
||||
{
|
||||
table: "status_faves",
|
||||
column: "approved_by_uri",
|
||||
columnType: "varchar",
|
||||
defaultVal: "",
|
||||
extra: "",
|
||||
},
|
||||
|
||||
// Columns that must be added to the
|
||||
|
|
@ -85,31 +85,31 @@ func init() {
|
|||
table: "account_settings",
|
||||
column: "interaction_policy_direct",
|
||||
columnType: "JSONB",
|
||||
defaultVal: "",
|
||||
extra: "",
|
||||
},
|
||||
{
|
||||
table: "account_settings",
|
||||
column: "interaction_policy_mutuals_only",
|
||||
columnType: "JSONB",
|
||||
defaultVal: "",
|
||||
extra: "",
|
||||
},
|
||||
{
|
||||
table: "account_settings",
|
||||
column: "interaction_policy_followers_only",
|
||||
columnType: "JSONB",
|
||||
defaultVal: "",
|
||||
extra: "",
|
||||
},
|
||||
{
|
||||
table: "account_settings",
|
||||
column: "interaction_policy_unlocked",
|
||||
columnType: "JSONB",
|
||||
defaultVal: "",
|
||||
extra: "",
|
||||
},
|
||||
{
|
||||
table: "account_settings",
|
||||
column: "interaction_policy_public",
|
||||
columnType: "JSONB",
|
||||
defaultVal: "",
|
||||
extra: "",
|
||||
},
|
||||
} {
|
||||
exists, err := doesColumnExist(ctx, tx,
|
||||
|
|
@ -130,9 +130,9 @@ func init() {
|
|||
}
|
||||
|
||||
qStr := "ALTER TABLE ? ADD COLUMN ? ?"
|
||||
if spec.defaultVal != "" {
|
||||
if spec.extra != "" {
|
||||
qStr += " ?"
|
||||
args = append(args, bun.Safe(spec.defaultVal))
|
||||
args = append(args, bun.Safe(spec.extra))
|
||||
}
|
||||
|
||||
log.Infof(ctx, "adding column '%s' to '%s'...", spec.column, spec.table)
|
||||
|
|
@ -161,11 +161,14 @@ func init() {
|
|||
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
|
||||
// to new version of interaction policy.
|
||||
for _, oldStatus := range oldStatuses {
|
||||
// Start with default policy for this visibility.
|
||||
v := gtsmodel.Visibility(oldStatus.Visibility)
|
||||
v := visibilityMapping[oldStatus.Visibility]
|
||||
policy := gtsmodel.DefaultInteractionPolicyFor(v)
|
||||
|
||||
if !*oldStatus.Likeable {
|
||||
|
|
|
|||
|
|
@ -22,40 +22,61 @@ import (
|
|||
)
|
||||
|
||||
type Status struct {
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"`
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`
|
||||
FetchedAt time.Time `bun:"type:timestamptz,nullzero"`
|
||||
PinnedAt time.Time `bun:"type:timestamptz,nullzero"`
|
||||
URI string `bun:",unique,nullzero,notnull"`
|
||||
URL string `bun:",nullzero"`
|
||||
Content string `bun:""`
|
||||
AttachmentIDs []string `bun:"attachments,array"`
|
||||
TagIDs []string `bun:"tags,array"`
|
||||
MentionIDs []string `bun:"mentions,array"`
|
||||
EmojiIDs []string `bun:"emojis,array"`
|
||||
Local *bool `bun:",nullzero,notnull,default:false"`
|
||||
AccountID string `bun:"type:CHAR(26),nullzero,notnull"`
|
||||
AccountURI string `bun:",nullzero,notnull"`
|
||||
InReplyToID string `bun:"type:CHAR(26),nullzero"`
|
||||
InReplyToURI string `bun:",nullzero"`
|
||||
InReplyToAccountID string `bun:"type:CHAR(26),nullzero"`
|
||||
InReplyTo *Status `bun:"-"`
|
||||
BoostOfID string `bun:"type:CHAR(26),nullzero"`
|
||||
BoostOfURI string `bun:"-"`
|
||||
BoostOfAccountID string `bun:"type:CHAR(26),nullzero"`
|
||||
BoostOf *Status `bun:"-"`
|
||||
ThreadID string `bun:"type:CHAR(26),nullzero"`
|
||||
PollID string `bun:"type:CHAR(26),nullzero"`
|
||||
ContentWarning string `bun:",nullzero"`
|
||||
Visibility string `bun:",nullzero,notnull"`
|
||||
Sensitive *bool `bun:",nullzero,notnull,default:false"`
|
||||
Language string `bun:",nullzero"`
|
||||
CreatedWithApplicationID string `bun:"type:CHAR(26),nullzero"`
|
||||
ActivityStreamsType string `bun:",nullzero,notnull"`
|
||||
Text string `bun:""`
|
||||
Federated *bool `bun:",notnull"`
|
||||
Boostable *bool `bun:",notnull"`
|
||||
Replyable *bool `bun:",notnull"`
|
||||
Likeable *bool `bun:",notnull"`
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"`
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`
|
||||
FetchedAt time.Time `bun:"type:timestamptz,nullzero"`
|
||||
PinnedAt time.Time `bun:"type:timestamptz,nullzero"`
|
||||
URI string `bun:",unique,nullzero,notnull"`
|
||||
URL string `bun:",nullzero"`
|
||||
Content string `bun:""`
|
||||
AttachmentIDs []string `bun:"attachments,array"`
|
||||
TagIDs []string `bun:"tags,array"`
|
||||
MentionIDs []string `bun:"mentions,array"`
|
||||
EmojiIDs []string `bun:"emojis,array"`
|
||||
Local *bool `bun:",nullzero,notnull,default:false"`
|
||||
AccountID string `bun:"type:CHAR(26),nullzero,notnull"`
|
||||
AccountURI string `bun:",nullzero,notnull"`
|
||||
InReplyToID string `bun:"type:CHAR(26),nullzero"`
|
||||
InReplyToURI string `bun:",nullzero"`
|
||||
InReplyToAccountID string `bun:"type:CHAR(26),nullzero"`
|
||||
InReplyTo *Status `bun:"-"`
|
||||
BoostOfID string `bun:"type:CHAR(26),nullzero"`
|
||||
BoostOfURI string `bun:"-"`
|
||||
BoostOfAccountID string `bun:"type:CHAR(26),nullzero"`
|
||||
BoostOf *Status `bun:"-"`
|
||||
ThreadID string `bun:"type:CHAR(26),nullzero"`
|
||||
PollID string `bun:"type:CHAR(26),nullzero"`
|
||||
ContentWarning string `bun:",nullzero"`
|
||||
Visibility Visibility `bun:",nullzero,notnull"`
|
||||
Sensitive *bool `bun:",nullzero,notnull,default:false"`
|
||||
Language string `bun:",nullzero"`
|
||||
CreatedWithApplicationID string `bun:"type:CHAR(26),nullzero"`
|
||||
ActivityStreamsType string `bun:",nullzero,notnull"`
|
||||
Text string `bun:""`
|
||||
Federated *bool `bun:",notnull"`
|
||||
Boostable *bool `bun:",notnull"`
|
||||
Replyable *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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -93,11 +93,7 @@ func init() {
|
|||
// For each currently pending status, check whether it's a reply or
|
||||
// a boost, and insert a corresponding interaction request into the db.
|
||||
for _, pendingStatus := range pendingStatuses {
|
||||
req, err := typeutils.StatusToInteractionRequest(ctx, pendingStatus)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := typeutils.StatusToInteractionRequest(pendingStatus)
|
||||
if _, err := tx.
|
||||
NewInsert().
|
||||
Model(req).
|
||||
|
|
@ -125,10 +121,7 @@ func init() {
|
|||
}
|
||||
|
||||
for _, pendingFave := range pendingFaves {
|
||||
req, err := typeutils.StatusFaveToInteractionRequest(ctx, pendingFave)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req := typeutils.StatusFaveToInteractionRequest(pendingFave)
|
||||
|
||||
if _, err := tx.
|
||||
NewInsert().
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ package migrations
|
|||
import (
|
||||
"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"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,44 @@
|
|||
// 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"
|
||||
"strings"
|
||||
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func init() {
|
||||
up := func(ctx context.Context, db *bun.DB) error {
|
||||
_, err := db.ExecContext(ctx, "ALTER TABLE ? ADD COLUMN ? TEXT", bun.Ident("instances"), bun.Ident("custom_css"))
|
||||
if err != nil && !(strings.Contains(err.Error(), "already exists") || strings.Contains(err.Error(), "duplicate column name") || strings.Contains(err.Error(), "SQLSTATE 42701")) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
down := func(ctx context.Context, db *bun.DB) error {
|
||||
_, err := db.ExecContext(ctx, "ALTER TABLE ? DROP COLUMN ?", bun.Ident("instances"), bun.Ident("custom_css"))
|
||||
return err
|
||||
}
|
||||
|
||||
if err := Migrations.Register(up, down); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func init() {
|
||||
up := func(ctx context.Context, db *bun.DB) error {
|
||||
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
for idx, col := range map[string]string{
|
||||
"interaction_requests_accepted_at_idx": "accepted_at",
|
||||
"interaction_requests_rejected_at_idx": "rejected_at",
|
||||
} {
|
||||
if _, err := tx.
|
||||
NewCreateIndex().
|
||||
Table("interaction_requests").
|
||||
Index(idx).
|
||||
Column(col).
|
||||
IfNotExists().
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
down := func(ctx context.Context, db *bun.DB) error {
|
||||
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if err := Migrations.Register(up, down); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"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 {
|
||||
// Previous versions of 20240620074530_interaction_policy.go
|
||||
// didn't set NOT NULL on gtsmodel.Status.PendingApproval and
|
||||
// gtsmodel.StatusFave.PendingApproval, resulting in NULL being
|
||||
// set for that column for some statuses. Correct for this.
|
||||
|
||||
log.Info(ctx, "correcting pending_approval on statuses table...")
|
||||
res, err := tx.
|
||||
NewUpdate().
|
||||
Table("statuses").
|
||||
Set("? = ?", bun.Ident("pending_approval"), false).
|
||||
Where("? IS NULL", bun.Ident("pending_approval")).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rows, err := res.RowsAffected()
|
||||
if err == nil {
|
||||
log.Infof(ctx, "corrected %d entries", rows)
|
||||
}
|
||||
|
||||
log.Info(ctx, "correcting pending_approval on status_faves table...")
|
||||
res, err = tx.
|
||||
NewUpdate().
|
||||
Table("status_faves").
|
||||
Set("? = ?", bun.Ident("pending_approval"), false).
|
||||
Where("? IS NULL", bun.Ident("pending_approval")).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rows, err = res.RowsAffected()
|
||||
if err == nil {
|
||||
log.Infof(ctx, "corrected %d entries", rows)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
131
internal/db/bundb/migrations/20241018151036_filter_unique_fix.go
Normal file
131
internal/db/bundb/migrations/20241018151036_filter_unique_fix.go
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dialect"
|
||||
)
|
||||
|
||||
func init() {
|
||||
up := func(ctx context.Context, db *bun.DB) error {
|
||||
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
|
||||
// Create the new filters table
|
||||
// with the unique constraint
|
||||
// set on AccountID + Title.
|
||||
if _, err := tx.
|
||||
NewCreateTable().
|
||||
ModelTableExpr("new_filters").
|
||||
Model((*gtsmodel.Filter)(nil)).
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Explicitly specify columns to bring
|
||||
// from old table to new, to avoid any
|
||||
// potential Postgres shenanigans.
|
||||
columns := []string{
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"expires_at",
|
||||
"account_id",
|
||||
"title",
|
||||
"action",
|
||||
"context_home",
|
||||
"context_notifications",
|
||||
"context_public",
|
||||
"context_thread",
|
||||
"context_account",
|
||||
}
|
||||
|
||||
// Copy all data for existing
|
||||
// filters to the new table.
|
||||
if _, err := tx.
|
||||
NewInsert().
|
||||
Table("new_filters").
|
||||
Table("filters").
|
||||
Column(columns...).
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Drop the old table.
|
||||
if _, err := tx.
|
||||
NewDropTable().
|
||||
Table("filters").
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rename new table to old table.
|
||||
if _, err := tx.
|
||||
ExecContext(
|
||||
ctx,
|
||||
"ALTER TABLE ? RENAME TO ?",
|
||||
bun.Ident("new_filters"),
|
||||
bun.Ident("filters"),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Index the new version
|
||||
// of the filters table.
|
||||
if _, err := tx.
|
||||
NewCreateIndex().
|
||||
Table("filters").
|
||||
Index("filters_account_id_idx").
|
||||
Column("account_id").
|
||||
IfNotExists().
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if db.Dialect().Name() == dialect.PG {
|
||||
// Rename "new_filters_pkey" from the
|
||||
// new table to just "filters_pkey".
|
||||
// This is only necessary on Postgres.
|
||||
if _, err := tx.ExecContext(
|
||||
ctx,
|
||||
"ALTER TABLE ? RENAME CONSTRAINT ? TO ?",
|
||||
bun.Ident("public.filters"),
|
||||
bun.Safe("new_filters_pkey"),
|
||||
bun.Safe("filters_pkey"),
|
||||
); 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
// 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"
|
||||
|
||||
gtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20241022153016_domain_permission_draft_exclude"
|
||||
"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 {
|
||||
|
||||
// Create `domain_permission_drafts`.
|
||||
if _, err := tx.
|
||||
NewCreateTable().
|
||||
Model((*gtsmodel.DomainPermissionDraft)(nil)).
|
||||
IfNotExists().
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create `domain_permission_ignores`.
|
||||
if _, err := tx.
|
||||
NewCreateTable().
|
||||
Model((*gtsmodel.DomainPermissionExclude)(nil)).
|
||||
IfNotExists().
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create indexes. Indices. Indie sexes.
|
||||
for table, indexes := range map[string]map[string][]string{
|
||||
"domain_permission_drafts": {
|
||||
"domain_permission_drafts_domain_idx": {"domain"},
|
||||
"domain_permission_drafts_subscription_id_idx": {"subscription_id"},
|
||||
},
|
||||
} {
|
||||
for index, columns := range indexes {
|
||||
if _, err := tx.
|
||||
NewCreateIndex().
|
||||
Table(table).
|
||||
Index(index).
|
||||
Column(columns...).
|
||||
IfNotExists().
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
down := func(ctx context.Context, db *bun.DB) error {
|
||||
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if err := Migrations.Register(up, down); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
// 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"
|
||||
|
||||
type DomainPermissionDraft struct {
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"`
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`
|
||||
PermissionType uint8 `bun:",notnull,unique:domain_permission_drafts_permission_type_domain_subscription_id_uniq"`
|
||||
Domain string `bun:",nullzero,notnull,unique:domain_permission_drafts_permission_type_domain_subscription_id_uniq"`
|
||||
CreatedByAccountID string `bun:"type:CHAR(26),nullzero,notnull"`
|
||||
PrivateComment string `bun:",nullzero"`
|
||||
PublicComment string `bun:",nullzero"`
|
||||
Obfuscate *bool `bun:",nullzero,notnull,default:false"`
|
||||
SubscriptionID string `bun:"type:CHAR(26),unique:domain_permission_drafts_permission_type_domain_subscription_id_uniq"`
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// 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"
|
||||
)
|
||||
|
||||
type DomainPermissionExclude struct {
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"`
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`
|
||||
Domain string `bun:",nullzero,notnull,unique"`
|
||||
CreatedByAccountID string `bun:"type:CHAR(26),nullzero,notnull"`
|
||||
PrivateComment string `bun:",nullzero"`
|
||||
}
|
||||
|
|
@ -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 migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
gtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20241022153016_domain_permission_subscriptions"
|
||||
"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 {
|
||||
// Create `domain_permission_subscriptions`.
|
||||
if _, err := tx.
|
||||
NewCreateTable().
|
||||
Model((*gtsmodel.DomainPermissionSubscription)(nil)).
|
||||
IfNotExists().
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create indexes. Indices. Indie sexes.
|
||||
if _, err := tx.
|
||||
NewCreateIndex().
|
||||
Table("domain_permission_subscriptions").
|
||||
// Filter on permission type.
|
||||
Index("domain_permission_subscriptions_permission_type_idx").
|
||||
Column("permission_type").
|
||||
IfNotExists().
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tx.
|
||||
NewCreateIndex().
|
||||
Table("domain_permission_subscriptions").
|
||||
// Sort by priority DESC.
|
||||
Index("domain_permission_subscriptions_priority_order_idx").
|
||||
ColumnExpr("? DESC", bun.Ident("priority")).
|
||||
IfNotExists().
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
down := func(ctx context.Context, db *bun.DB) error {
|
||||
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
if err := Migrations.Register(up, down); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
// 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"
|
||||
|
||||
type DomainPermissionSubscription struct {
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"`
|
||||
Priority uint8 `bun:""`
|
||||
Title string `bun:",nullzero,unique"`
|
||||
PermissionType uint8 `bun:",nullzero,notnull"`
|
||||
AsDraft *bool `bun:",nullzero,notnull,default:true"`
|
||||
AdoptOrphans *bool `bun:",nullzero,notnull,default:false"`
|
||||
CreatedByAccountID string `bun:"type:CHAR(26),nullzero,notnull"`
|
||||
URI string `bun:",nullzero,notnull,unique"`
|
||||
ContentType uint16 `bun:",nullzero,notnull"`
|
||||
FetchUsername string `bun:",nullzero"`
|
||||
FetchPassword string `bun:",nullzero"`
|
||||
FetchedAt time.Time `bun:"type:timestamptz,nullzero"`
|
||||
SuccessfullyFetchedAt time.Time `bun:"type:timestamptz,nullzero"`
|
||||
ETag string `bun:"etag,nullzero"`
|
||||
Error string `bun:",nullzero"`
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"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 {
|
||||
|
||||
// Check for 'updated_at' column on mentions table, else return.
|
||||
exists, err := doesColumnExist(ctx, tx, "mentions", "updated_at")
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove 'updated_at' column.
|
||||
log.Info(ctx, "removing unused updated_at column from mentions to save space, please wait...")
|
||||
_, err = tx.NewDropColumn().
|
||||
Model((*gtsmodel.Mention)(nil)).
|
||||
Column("updated_at").
|
||||
Exec(ctx)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
// 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"
|
||||
"reflect"
|
||||
|
||||
gtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20241113152126_add_status_edits"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
|
||||
"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 {
|
||||
statusType := reflect.TypeOf((*gtsmodel.Status)(nil))
|
||||
|
||||
// Generate new Status.EditIDs column definition from bun.
|
||||
colDef, err := getBunColumnDef(tx, statusType, "EditIDs")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add EditIDs column to Status table.
|
||||
log.Info(ctx, "adding edits column to statuses table...")
|
||||
_, err = tx.NewAddColumn().
|
||||
Model((*gtsmodel.Status)(nil)).
|
||||
ColumnExpr(colDef).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create the main StatusEdits table.
|
||||
_, err = tx.NewCreateTable().
|
||||
IfNotExists().
|
||||
Model((*gtsmodel.StatusEdit)(nil)).
|
||||
Exec(ctx)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
// 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
|
||||
EditIDs []string `bun:"edits,array"` //
|
||||
Edits []*StatusEdit `bun:"-"` //
|
||||
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
|
||||
)
|
||||
|
|
@ -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"
|
||||
)
|
||||
|
||||
// StatusEdit represents a **historical** view of a Status
|
||||
// after a received edit. The Status itself will always
|
||||
// contain the latest up-to-date information.
|
||||
//
|
||||
// Note that stored status edits may not exactly match that
|
||||
// of the origin server, they are a best-effort by receiver
|
||||
// to store version history. There is no AP history endpoint.
|
||||
type StatusEdit struct {
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // ID of this item in the database.
|
||||
Content string `bun:""` // Content of status at time of edit; likely html-formatted but not guaranteed.
|
||||
ContentWarning string `bun:",nullzero"` // Content warning of status at time of edit.
|
||||
Text string `bun:""` // Original status text, without formatting, at time of edit.
|
||||
Language string `bun:",nullzero"` // Status language at time of edit.
|
||||
Sensitive *bool `bun:",nullzero,notnull,default:false"` // Status sensitive flag at time of edit.
|
||||
AttachmentIDs []string `bun:"attachments,array"` // Database IDs of media attachments associated with status at time of edit.
|
||||
AttachmentDescriptions []string `bun:",array"` // Previous media descriptions of media attachments associated with status at time of edit.
|
||||
PollOptions []string `bun:",array"` // Poll options of status at time of edit, only set if status contains a poll.
|
||||
PollVotes []int `bun:",array"` // Poll vote count at time of status edit, only set if poll votes were reset.
|
||||
StatusID string `bun:"type:CHAR(26),nullzero,notnull"` // The originating status ID this is a historical edit of.
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // The creation time of this version of the status content (according to receiving server).
|
||||
|
||||
// We don't bother having a *gtsmodel.Status model here
|
||||
// as the StatusEdit is always just attached to a Status,
|
||||
// so it doesn't need a self-reference back to it.
|
||||
}
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
// 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"
|
||||
|
||||
old_gtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20241121121623_enum_strings_to_ints"
|
||||
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.
|
||||
log.Info(ctx, "dropping old visibility indexes...")
|
||||
for _, index := range visIndices {
|
||||
log.Info(ctx, "dropping old index %s...", index.name)
|
||||
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.
|
||||
log.Info(ctx, "creating new visibility indexes...")
|
||||
for _, index := range visIndices {
|
||||
log.Info(ctx, "creating new index %s...", index.name)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
)
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"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 {
|
||||
|
||||
// Check for 'updated_at' column on media attachments table, else return.
|
||||
exists, err := doesColumnExist(ctx, tx, "media_attachments", "updated_at")
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove 'updated_at' column.
|
||||
log.Info(ctx, "removing unused updated_at column from media attachments to save space, please wait...")
|
||||
_, err = tx.NewDropColumn().
|
||||
Model((*gtsmodel.MediaAttachment)(nil)).
|
||||
Column("updated_at").
|
||||
Exec(ctx)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
// 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"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
oldmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20250106114512_replace_statuses_updatedat_with_editedat"
|
||||
newmodel "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dialect"
|
||||
)
|
||||
|
||||
func init() {
|
||||
up := func(ctx context.Context, db *bun.DB) error {
|
||||
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
|
||||
var newStatus *newmodel.Status
|
||||
newStatusType := reflect.TypeOf(newStatus)
|
||||
|
||||
// Generate new Status.EditedAt column definition from bun.
|
||||
colDef, err := getBunColumnDef(tx, newStatusType, "EditedAt")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error making column def: %w", err)
|
||||
}
|
||||
|
||||
log.Info(ctx, "adding statuses.edited_at column...")
|
||||
_, err = tx.NewAddColumn().Model(newStatus).
|
||||
ColumnExpr(colDef).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error adding column: %w", err)
|
||||
}
|
||||
|
||||
var whereSQL string
|
||||
var whereArg []any
|
||||
|
||||
// Check for an empty length
|
||||
// EditIDs JSON array, with different
|
||||
// SQL depending on connected database.
|
||||
switch tx.Dialect().Name() {
|
||||
case dialect.SQLite:
|
||||
whereSQL = "NOT (json_array_length(?) = 0 OR ? IS NULL)"
|
||||
whereArg = []any{bun.Ident("edits"), bun.Ident("edits")}
|
||||
case dialect.PG:
|
||||
whereSQL = "NOT (CARDINALITY(?) = 0 OR ? IS NULL)"
|
||||
whereArg = []any{bun.Ident("edits"), bun.Ident("edits")}
|
||||
default:
|
||||
panic("unsupported db type")
|
||||
}
|
||||
|
||||
log.Info(ctx, "setting edited_at = updated_at where not empty(edits)...")
|
||||
res, err := tx.NewUpdate().Model(newStatus).Where(whereSQL, whereArg...).
|
||||
Set("? = ?",
|
||||
bun.Ident("edited_at"),
|
||||
bun.Ident("updated_at"),
|
||||
).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error updating columns: %w", err)
|
||||
}
|
||||
|
||||
count, _ := res.RowsAffected()
|
||||
log.Infof(ctx, "updated %d statuses", count)
|
||||
|
||||
log.Info(ctx, "removing statuses.updated_at column...")
|
||||
_, err = tx.NewDropColumn().Model((*oldmodel.Status)(nil)).
|
||||
Column("updated_at").
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error dropping column: %w", 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
// 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
|
||||
|
||||
// A policy URI is GoToSocial's internal representation of
|
||||
// one ActivityPub URI for an Actor or a Collection of Actors,
|
||||
// specific to the domain of enforcing interaction policies.
|
||||
//
|
||||
// A PolicyValue can be stored in the database either as one
|
||||
// of the Value constants defined below (to save space), OR as
|
||||
// a full-fledged ActivityPub URI.
|
||||
//
|
||||
// A PolicyValue should be translated to the canonical string
|
||||
// value of the represented URI when federating an item, or
|
||||
// from the canonical string value of the URI when receiving
|
||||
// or retrieving an item.
|
||||
//
|
||||
// For example, if the PolicyValue `followers` was being
|
||||
// federated outwards in an interaction policy attached to an
|
||||
// item created by the actor `https://example.org/users/someone`,
|
||||
// then it should be translated to their followers URI when sent,
|
||||
// eg., `https://example.org/users/someone/followers`.
|
||||
//
|
||||
// Likewise, if GoToSocial receives an item with an interaction
|
||||
// policy containing `https://example.org/users/someone/followers`,
|
||||
// and the item was created by `https://example.org/users/someone`,
|
||||
// then the followers URI would be converted to `followers`
|
||||
// for internal storage.
|
||||
type PolicyValue string
|
||||
|
||||
const (
|
||||
// Stand-in for ActivityPub magic public URI,
|
||||
// which encompasses every possible Actor URI.
|
||||
PolicyValuePublic PolicyValue = "public"
|
||||
// Stand-in for the Followers Collection of
|
||||
// the item owner's Actor.
|
||||
PolicyValueFollowers PolicyValue = "followers"
|
||||
// Stand-in for the Following Collection of
|
||||
// the item owner's Actor.
|
||||
PolicyValueFollowing PolicyValue = "following"
|
||||
// Stand-in for the Mutuals Collection of
|
||||
// the item owner's Actor.
|
||||
//
|
||||
// (TODO: Reserved, currently unused).
|
||||
PolicyValueMutuals PolicyValue = "mutuals"
|
||||
// Stand-in for Actor URIs tagged in the item.
|
||||
PolicyValueMentioned PolicyValue = "mentioned"
|
||||
// Stand-in for the Actor URI of the item owner.
|
||||
PolicyValueAuthor PolicyValue = "author"
|
||||
)
|
||||
|
||||
type PolicyValues []PolicyValue
|
||||
|
||||
// An InteractionPolicy determines which
|
||||
// interactions will be accepted for an
|
||||
// item, and according to what rules.
|
||||
type InteractionPolicy struct {
|
||||
// Conditions in which a Like
|
||||
// interaction will be accepted
|
||||
// for an item with this policy.
|
||||
CanLike PolicyRules
|
||||
// Conditions in which a Reply
|
||||
// interaction will be accepted
|
||||
// for an item with this policy.
|
||||
CanReply PolicyRules
|
||||
// Conditions in which an Announce
|
||||
// interaction will be accepted
|
||||
// for an item with this policy.
|
||||
CanAnnounce PolicyRules
|
||||
}
|
||||
|
||||
// PolicyRules represents the rules according
|
||||
// to which a certain interaction is permitted
|
||||
// to various Actor and Actor Collection URIs.
|
||||
type PolicyRules struct {
|
||||
// Always is for PolicyValues who are
|
||||
// permitted to do an interaction
|
||||
// without requiring approval.
|
||||
Always PolicyValues
|
||||
// WithApproval is for PolicyValues who
|
||||
// are conditionally permitted to do
|
||||
// an interaction, pending approval.
|
||||
WithApproval PolicyValues
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
// 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
|
||||
InReplyTo *Status `bun:"-"` // status corresponding to inReplyToID
|
||||
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
|
||||
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
|
||||
EditIDs []string `bun:"edits,array"` //
|
||||
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)
|
||||
InteractionPolicy *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.
|
||||
}
|
||||
|
||||
// GetID implements timeline.Timelineable{}.
|
||||
func (s *Status) GetID() string {
|
||||
return s.ID
|
||||
}
|
||||
|
||||
// IsLocal returns true if this is a local
|
||||
// status (ie., originating from this instance).
|
||||
func (s *Status) IsLocal() bool {
|
||||
return s.Local != nil && *s.Local
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// Visibility represents the
|
||||
// visibility granularity of a status.
|
||||
type Visibility enumType
|
||||
|
||||
const (
|
||||
// VisibilityNone means nobody can see this.
|
||||
// It's only used for web status visibility.
|
||||
VisibilityNone Visibility = 1
|
||||
|
||||
// VisibilityPublic means this status will
|
||||
// be visible to everyone on all timelines.
|
||||
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 Visibility = 4
|
||||
|
||||
// VisibilityMutualsOnly means this status
|
||||
// is visible to mutual followers only.
|
||||
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 Visibility = VisibilityUnlocked
|
||||
)
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
// 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"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util/xslices"
|
||||
"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 {
|
||||
var edits []*gtsmodel.StatusEdit
|
||||
|
||||
// Select all status edits that
|
||||
// are not actually connected to
|
||||
// the status they reference.
|
||||
if err := tx.NewSelect().
|
||||
Model(&edits).
|
||||
Join("JOIN ? AS ? ON ? = ?",
|
||||
bun.Ident("statuses"),
|
||||
bun.Ident("status"),
|
||||
bun.Ident("status.id"),
|
||||
bun.Ident("status_edit.status_id"),
|
||||
).
|
||||
Where("CAST(? AS TEXT) NOT LIKE CONCAT(?, ?, ?)",
|
||||
bun.Ident("status.edits"),
|
||||
"%", bun.Ident("status_edit.id"), "%",
|
||||
).
|
||||
Column("id", "status_id", "created_at").
|
||||
Scan(ctx, &edits); err != nil {
|
||||
return fmt.Errorf("error selecting unlinked edits: %w", err)
|
||||
}
|
||||
|
||||
log.Infof(ctx, "relinking %d unlinked status edits", len(edits))
|
||||
|
||||
for _, edit := range edits {
|
||||
var status gtsmodel.Status
|
||||
|
||||
// Select the list of edits
|
||||
// CURRENTLY attached to the
|
||||
// status that edit references.
|
||||
if err := tx.NewSelect().
|
||||
Model(&status).
|
||||
Column("edits").
|
||||
Where("? = ?",
|
||||
bun.Ident("id"),
|
||||
edit.StatusID,
|
||||
).
|
||||
Scan(ctx); err != nil {
|
||||
return fmt.Errorf("error selecting status.edits: %w", err)
|
||||
}
|
||||
|
||||
// Select only the ID and creation
|
||||
// dates of all the other edits that
|
||||
// are attached to referenced status.
|
||||
if err := tx.NewSelect().
|
||||
Model(&status.Edits).
|
||||
Column("id", "created_at").
|
||||
Where("? IN (?)",
|
||||
bun.Ident("id"),
|
||||
bun.In(status.EditIDs),
|
||||
).
|
||||
Scan(ctx); err != nil {
|
||||
return fmt.Errorf("error selecting other status edits: %w", err)
|
||||
}
|
||||
|
||||
editID := func(e *gtsmodel.StatusEdit) string { return e.ID }
|
||||
|
||||
// Append this unlinked edit to status' list
|
||||
// of edits and then sort edits by creation.
|
||||
//
|
||||
// On tiny off-chance we end up with dupes,
|
||||
// we still deduplicate these status edits.
|
||||
status.Edits = append(status.Edits, edit)
|
||||
status.Edits = xslices.DeduplicateFunc(status.Edits, editID)
|
||||
slices.SortFunc(status.Edits, func(e1, e2 *gtsmodel.StatusEdit) int {
|
||||
const k = -1 // oldest at 0th, newest at nth
|
||||
switch c1, c2 := e1.CreatedAt, e2.CreatedAt; {
|
||||
case c1.Before(c2):
|
||||
return +k
|
||||
case c2.Before(c1):
|
||||
return -k
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
})
|
||||
|
||||
// Extract the IDs from edits to update the status edit IDs.
|
||||
status.EditIDs = xslices.Gather(nil, status.Edits, editID)
|
||||
|
||||
// Update the relevant status
|
||||
// edit IDs column in database.
|
||||
if _, err := tx.NewUpdate().
|
||||
Model(&status).
|
||||
Column("edits").
|
||||
Where("? = ?",
|
||||
bun.Ident("id"),
|
||||
edit.StatusID,
|
||||
).
|
||||
Exec(ctx); err != nil {
|
||||
return fmt.Errorf("error updating status.edits: %w", 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -19,11 +19,209 @@ package migrations
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"codeberg.org/gruf/go-byteutil"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dialect"
|
||||
"github.com/uptrace/bun/dialect/feature"
|
||||
"github.com/uptrace/bun/dialect/sqltype"
|
||||
"github.com/uptrace/bun/schema"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// getBunColumnDef generates a column definition string for the SQL table represented by
|
||||
// Go type, with the SQL column represented by the given Go field name. This ensures when
|
||||
// adding a new column for table by migration that it will end up as bun would create it.
|
||||
//
|
||||
// NOTE: this function must stay in sync with (*bun.CreateTableQuery{}).AppendQuery(),
|
||||
// specifically where it loops over table fields appending each column definition.
|
||||
func getBunColumnDef(db bun.IDB, rtype reflect.Type, fieldName string) (string, error) {
|
||||
d := db.Dialect()
|
||||
f := d.Features()
|
||||
|
||||
// Get bun schema definitions for Go type and its field.
|
||||
field, table, err := getModelField(db, rtype, fieldName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Start with reasonable buf.
|
||||
buf := make([]byte, 0, 64)
|
||||
|
||||
// Start with the SQL column name.
|
||||
buf = append(buf, field.SQLName...)
|
||||
buf = append(buf, " "...)
|
||||
|
||||
// Append the SQL
|
||||
// type information.
|
||||
switch {
|
||||
|
||||
// Most of the time these two will match, but for the cases where DiscoveredSQLType is dialect-specific,
|
||||
// e.g. pgdialect would change sqltype.SmallInt to pgTypeSmallSerial for columns that have `bun:",autoincrement"`
|
||||
case !strings.EqualFold(field.CreateTableSQLType, field.DiscoveredSQLType):
|
||||
buf = append(buf, field.CreateTableSQLType...)
|
||||
|
||||
// For all common SQL types except VARCHAR, both UserDefinedSQLType and DiscoveredSQLType specify the correct type,
|
||||
// and we needn't modify it. For VARCHAR columns, we will stop to check if a valid length has been set in .Varchar(int).
|
||||
case !strings.EqualFold(field.CreateTableSQLType, sqltype.VarChar):
|
||||
buf = append(buf, field.CreateTableSQLType...)
|
||||
|
||||
// All else falls back
|
||||
// to a default varchar.
|
||||
default:
|
||||
if d.Name() == dialect.Oracle {
|
||||
buf = append(buf, "VARCHAR2"...)
|
||||
} else {
|
||||
buf = append(buf, sqltype.VarChar...)
|
||||
}
|
||||
buf = append(buf, "("...)
|
||||
buf = strconv.AppendInt(buf, int64(d.DefaultVarcharLen()), 10)
|
||||
buf = append(buf, ")"...)
|
||||
}
|
||||
|
||||
// Append not null definition if field requires.
|
||||
if field.NotNull && d.Name() != dialect.Oracle {
|
||||
buf = append(buf, " NOT NULL"...)
|
||||
}
|
||||
|
||||
// Append autoincrement definition if field requires.
|
||||
if field.Identity && f.Has(feature.GeneratedIdentity) ||
|
||||
(field.AutoIncrement && (f.Has(feature.AutoIncrement) || f.Has(feature.Identity))) {
|
||||
buf = d.AppendSequence(buf, table, field)
|
||||
}
|
||||
|
||||
// Append any default value.
|
||||
if field.SQLDefault != "" {
|
||||
buf = append(buf, " DEFAULT "...)
|
||||
buf = append(buf, field.SQLDefault...)
|
||||
}
|
||||
|
||||
return byteutil.B2S(buf), nil
|
||||
}
|
||||
|
||||
// getModelField returns the uptrace/bun schema details for given Go type and field name.
|
||||
func getModelField(db bun.IDB, rtype reflect.Type, fieldName string) (*schema.Field, *schema.Table, error) {
|
||||
|
||||
// Get the associated table for Go type.
|
||||
table := db.Dialect().Tables().Get(rtype)
|
||||
if table == nil {
|
||||
return nil, nil, fmt.Errorf("no table found for type: %s", rtype)
|
||||
}
|
||||
|
||||
var field *schema.Field
|
||||
|
||||
// Look for field matching Go name.
|
||||
for i := range table.Fields {
|
||||
if table.Fields[i].GoName == fieldName {
|
||||
field = table.Fields[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if field == nil {
|
||||
return nil, nil, fmt.Errorf("no bun field found on %s with name: %s", rtype, fieldName)
|
||||
}
|
||||
|
||||
return field, table, nil
|
||||
}
|
||||
|
||||
// doesColumnExist safely checks whether given column exists on table, handling both SQLite and PostgreSQL appropriately.
|
||||
func doesColumnExist(ctx context.Context, tx bun.Tx, table, col string) (bool, error) {
|
||||
var n int
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue