diff options
Diffstat (limited to 'internal/db/bundb/migrations')
-rw-r--r-- | internal/db/bundb/migrations/20240620074530_interaction_policy.go | 264 | ||||
-rw-r--r-- | internal/db/bundb/migrations/20240620074530_interaction_policy/status.go | 61 |
2 files changed, 325 insertions, 0 deletions
diff --git a/internal/db/bundb/migrations/20240620074530_interaction_policy.go b/internal/db/bundb/migrations/20240620074530_interaction_policy.go new file mode 100644 index 000000000..424039a52 --- /dev/null +++ b/internal/db/bundb/migrations/20240620074530_interaction_policy.go @@ -0,0 +1,264 @@ +// 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" + + oldmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20240620074530_interaction_policy" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + + "github.com/uptrace/bun" +) + +func init() { + up := func(ctx context.Context, db *bun.DB) error { + log.Info(ctx, "migrating statuses and account settings to interaction policy model, please wait...") + return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { + + // Add new columns for interaction + // policies + related fields. + type spec struct { + table string + column string + columnType string + defaultVal string + } + for _, spec := range []spec{ + // Statuses. + { + table: "statuses", + column: "interaction_policy", + columnType: "JSONB", + defaultVal: "", + }, + { + table: "statuses", + column: "pending_approval", + columnType: "BOOLEAN", + defaultVal: "DEFAULT false", + }, + { + table: "statuses", + column: "approved_by_uri", + columnType: "varchar", + defaultVal: "", + }, + + // Status faves. + { + table: "status_faves", + column: "pending_approval", + columnType: "BOOLEAN", + defaultVal: "DEFAULT false", + }, + { + table: "status_faves", + column: "approved_by_uri", + columnType: "varchar", + defaultVal: "", + }, + + // Columns that must be added to the + // `account_settings` table to populate + // default interaction policies for + // different status visibilities. + { + table: "account_settings", + column: "interaction_policy_direct", + columnType: "JSONB", + defaultVal: "", + }, + { + table: "account_settings", + column: "interaction_policy_mutuals_only", + columnType: "JSONB", + defaultVal: "", + }, + { + table: "account_settings", + column: "interaction_policy_followers_only", + columnType: "JSONB", + defaultVal: "", + }, + { + table: "account_settings", + column: "interaction_policy_unlocked", + columnType: "JSONB", + defaultVal: "", + }, + { + table: "account_settings", + column: "interaction_policy_public", + columnType: "JSONB", + defaultVal: "", + }, + } { + exists, err := doesColumnExist(ctx, tx, + spec.table, spec.column, + ) + if err != nil { + // Real error. + return err + } else if exists { + // Already created. + continue + } + + args := []any{ + bun.Ident(spec.table), + bun.Ident(spec.column), + bun.Safe(spec.columnType), + } + + qStr := "ALTER TABLE ? ADD COLUMN ? ?" + if spec.defaultVal != "" { + qStr += " ?" + args = append(args, bun.Safe(spec.defaultVal)) + } + + if _, err := tx.ExecContext(ctx, qStr, args...); err != nil { + return err + } + } + + // Select each locally-created status + // with non-default old flags set. + oldStatuses := []oldmodel.Status{} + + if err := tx. + NewSelect(). + Model(&oldStatuses). + Column("id", "likeable", "replyable", "boostable", "visibility"). + Where("? = ?", bun.Ident("local"), true). + WhereGroup(" AND ", func(sq *bun.SelectQuery) *bun.SelectQuery { + return sq. + Where("? = ?", bun.Ident("likeable"), false). + WhereOr("? = ?", bun.Ident("replyable"), false). + WhereOr("? = ?", bun.Ident("boostable"), false) + }). + Scan(ctx); err != nil { + return err + } + + // 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) + policy := gtsmodel.DefaultInteractionPolicyFor(v) + + if !*oldStatus.Likeable { + // Only author can like. + policy.CanLike = gtsmodel.PolicyRules{ + Always: gtsmodel.PolicyValues{ + gtsmodel.PolicyValueAuthor, + }, + WithApproval: make(gtsmodel.PolicyValues, 0), + } + } + + if !*oldStatus.Replyable { + // Only author + mentioned can Reply. + policy.CanReply = gtsmodel.PolicyRules{ + Always: gtsmodel.PolicyValues{ + gtsmodel.PolicyValueAuthor, + gtsmodel.PolicyValueMentioned, + }, + WithApproval: make(gtsmodel.PolicyValues, 0), + } + } + + if !*oldStatus.Boostable { + // Only author can Announce. + policy.CanAnnounce = gtsmodel.PolicyRules{ + Always: gtsmodel.PolicyValues{ + gtsmodel.PolicyValueAuthor, + }, + WithApproval: make(gtsmodel.PolicyValues, 0), + } + } + + // Update status with the new interaction policy. + newStatus := >smodel.Status{ + ID: oldStatus.ID, + InteractionPolicy: policy, + } + if _, err := tx. + NewUpdate(). + Model(newStatus). + Column("interaction_policy"). + Where("? = ?", bun.Ident("id"), newStatus.ID). + Exec(ctx); err != nil { + return err + } + } + + // Drop now unused columns from statuses table. + oldColumns := []string{ + "likeable", + "replyable", + "boostable", + } + for _, column := range oldColumns { + if _, err := tx. + NewDropColumn(). + Table("statuses"). + Column(column). + Exec(ctx); err != nil { + return err + } + } + + // Add new indexes. + if _, err := tx. + NewCreateIndex(). + Table("statuses"). + Index("statuses_pending_approval_idx"). + Column("pending_approval"). + IfNotExists(). + Exec(ctx); err != nil { + return err + } + + if _, err := tx. + NewCreateIndex(). + Table("status_faves"). + Index("status_faves_pending_approval_idx"). + Column("pending_approval"). + 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) + } +} diff --git a/internal/db/bundb/migrations/20240620074530_interaction_policy/status.go b/internal/db/bundb/migrations/20240620074530_interaction_policy/status.go new file mode 100644 index 000000000..ae96d047d --- /dev/null +++ b/internal/db/bundb/migrations/20240620074530_interaction_policy/status.go @@ -0,0 +1,61 @@ +// 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 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"` +} |