diff options
author | 2025-01-09 00:02:22 +0000 | |
---|---|---|
committer | 2025-01-09 00:02:22 +0000 | |
commit | 8cfae010a9e1852a4d7c76dbdc4e7e750ab0d03b (patch) | |
tree | a1c503547c82e7f65e51ca91b0dd940ea62a000e /internal | |
parent | [bugfix] More permissive CSV parsing for perm subs, text parse fix (#3638) (diff) | |
download | gotosocial-8cfae010a9e1852a4d7c76dbdc4e7e750ab0d03b.tar.xz |
[bugfix] migration to cleanup dropped status edits (#3637)
* add migration to cleanup dropped status edits from previous bug, relinking with statuses
* don't use bun.Ident where not needed
* fix join statement
* fix query to work for both postgres and sqlite
* actually update by the currently set status ID field :facepalm:
Diffstat (limited to 'internal')
-rw-r--r-- | internal/db/bundb/migrations/20250106165529_cleanup_dropped_edits.go | 140 |
1 files changed, 140 insertions, 0 deletions
diff --git a/internal/db/bundb/migrations/20250106165529_cleanup_dropped_edits.go b/internal/db/bundb/migrations/20250106165529_cleanup_dropped_edits.go new file mode 100644 index 000000000..cdc5db632 --- /dev/null +++ b/internal/db/bundb/migrations/20250106165529_cleanup_dropped_edits.go @@ -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) + } +} |