mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-03 18:52:24 -06:00 
			
		
		
		
	This pull request tidies up some previous migrations by making sure there's a proper snapshot in the migrations folder of what interaction policies looked like at the time the migration was written, rather than using the moving target `internal/gtsmodel`. Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4171 Co-authored-by: tobi <tobi.smethurst@protonmail.com> Co-committed-by: tobi <tobi.smethurst@protonmail.com>
		
			
				
	
	
		
			270 lines
		
	
	
	
		
			7.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			270 lines
		
	
	
	
		
			7.4 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// 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"
 | 
						|
 | 
						|
	"code.superseriousbusiness.org/gotosocial/internal/log"
 | 
						|
 | 
						|
	newmodel "code.superseriousbusiness.org/gotosocial/internal/db/bundb/migrations/20240620074530_interaction_policy/new"
 | 
						|
	oldmodel "code.superseriousbusiness.org/gotosocial/internal/db/bundb/migrations/20240620074530_interaction_policy/old"
 | 
						|
 | 
						|
	"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...")
 | 
						|
		log.Warn(ctx, "**WITH A LARGE DATABASE / LOWER SPEC MACHINE, THIS MIGRATION MAY TAKE A VERY LONG TIME (an hour or even longer); DO NOT INTERRUPT IT!**")
 | 
						|
		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
 | 
						|
				extra      string
 | 
						|
			}
 | 
						|
			for _, spec := range []spec{
 | 
						|
				// Statuses.
 | 
						|
				{
 | 
						|
					table:      "statuses",
 | 
						|
					column:     "interaction_policy",
 | 
						|
					columnType: "JSONB",
 | 
						|
					extra:      "",
 | 
						|
				},
 | 
						|
				{
 | 
						|
					table:      "statuses",
 | 
						|
					column:     "pending_approval",
 | 
						|
					columnType: "BOOLEAN",
 | 
						|
					extra:      "NOT NULL DEFAULT false",
 | 
						|
				},
 | 
						|
				{
 | 
						|
					table:      "statuses",
 | 
						|
					column:     "approved_by_uri",
 | 
						|
					columnType: "varchar",
 | 
						|
					extra:      "",
 | 
						|
				},
 | 
						|
 | 
						|
				// Status faves.
 | 
						|
				{
 | 
						|
					table:      "status_faves",
 | 
						|
					column:     "pending_approval",
 | 
						|
					columnType: "BOOLEAN",
 | 
						|
					extra:      "NOT NULL DEFAULT false",
 | 
						|
				},
 | 
						|
				{
 | 
						|
					table:      "status_faves",
 | 
						|
					column:     "approved_by_uri",
 | 
						|
					columnType: "varchar",
 | 
						|
					extra:      "",
 | 
						|
				},
 | 
						|
 | 
						|
				// 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",
 | 
						|
					extra:      "",
 | 
						|
				},
 | 
						|
				{
 | 
						|
					table:      "account_settings",
 | 
						|
					column:     "interaction_policy_mutuals_only",
 | 
						|
					columnType: "JSONB",
 | 
						|
					extra:      "",
 | 
						|
				},
 | 
						|
				{
 | 
						|
					table:      "account_settings",
 | 
						|
					column:     "interaction_policy_followers_only",
 | 
						|
					columnType: "JSONB",
 | 
						|
					extra:      "",
 | 
						|
				},
 | 
						|
				{
 | 
						|
					table:      "account_settings",
 | 
						|
					column:     "interaction_policy_unlocked",
 | 
						|
					columnType: "JSONB",
 | 
						|
					extra:      "",
 | 
						|
				},
 | 
						|
				{
 | 
						|
					table:      "account_settings",
 | 
						|
					column:     "interaction_policy_public",
 | 
						|
					columnType: "JSONB",
 | 
						|
					extra:      "",
 | 
						|
				},
 | 
						|
			} {
 | 
						|
				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.extra != "" {
 | 
						|
					qStr += " ?"
 | 
						|
					args = append(args, bun.Safe(spec.extra))
 | 
						|
				}
 | 
						|
 | 
						|
				log.Infof(ctx, "adding column '%s' to '%s'...", spec.column, spec.table)
 | 
						|
				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{}
 | 
						|
 | 
						|
			log.Info(ctx, "migrating existing statuses to new visibility model...")
 | 
						|
			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.
 | 
						|
				policy := newmodel.DefaultInteractionPolicyFor(newmodel.Visibility(oldStatus.Visibility))
 | 
						|
 | 
						|
				if !*oldStatus.Likeable {
 | 
						|
					// Only author can like.
 | 
						|
					policy.CanLike = newmodel.PolicyRules{
 | 
						|
						Always: newmodel.PolicyValues{
 | 
						|
							newmodel.PolicyValueAuthor,
 | 
						|
						},
 | 
						|
						WithApproval: make(newmodel.PolicyValues, 0),
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				if !*oldStatus.Replyable {
 | 
						|
					// Only author + mentioned can Reply.
 | 
						|
					policy.CanReply = newmodel.PolicyRules{
 | 
						|
						Always: newmodel.PolicyValues{
 | 
						|
							newmodel.PolicyValueAuthor,
 | 
						|
							newmodel.PolicyValueMentioned,
 | 
						|
						},
 | 
						|
						WithApproval: make(newmodel.PolicyValues, 0),
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				if !*oldStatus.Boostable {
 | 
						|
					// Only author can Announce.
 | 
						|
					policy.CanAnnounce = newmodel.PolicyRules{
 | 
						|
						Always: newmodel.PolicyValues{
 | 
						|
							newmodel.PolicyValueAuthor,
 | 
						|
						},
 | 
						|
						WithApproval: make(newmodel.PolicyValues, 0),
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				// Update status with the new interaction policy.
 | 
						|
				newStatus := &newmodel.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 {
 | 
						|
				log.Infof(ctx, "dropping now-unused status column '%s'; this may take a while if you have lots of statuses in your database...", column)
 | 
						|
				if _, err := tx.
 | 
						|
					NewDropColumn().
 | 
						|
					Table("statuses").
 | 
						|
					Column(column).
 | 
						|
					Exec(ctx); err != nil {
 | 
						|
					return err
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			// Add new indexes.
 | 
						|
			log.Info(ctx, "adding new index 'statuses_pending_approval_idx' to 'statuses'...")
 | 
						|
			if _, err := tx.
 | 
						|
				NewCreateIndex().
 | 
						|
				Table("statuses").
 | 
						|
				Index("statuses_pending_approval_idx").
 | 
						|
				Column("pending_approval").
 | 
						|
				IfNotExists().
 | 
						|
				Exec(ctx); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
 | 
						|
			log.Info(ctx, "adding new index 'status_faves_pending_approval_idx' to 'status_faves'...")
 | 
						|
			if _, err := tx.
 | 
						|
				NewCreateIndex().
 | 
						|
				Table("status_faves").
 | 
						|
				Index("status_faves_pending_approval_idx").
 | 
						|
				Column("pending_approval").
 | 
						|
				IfNotExists().
 | 
						|
				Exec(ctx); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
 | 
						|
			log.Info(ctx, "committing transaction, almost done...")
 | 
						|
			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)
 | 
						|
	}
 | 
						|
}
 |