website/pkg/worker/vacuum.go
Noah Petherbridge 06ae20cb3e Adopt a Forum
* Forums are disowned on user account deletion (their owner_id=0)
* A forum without an owner shows a notice at the bottom with a link to petition
  to adopt the forum. It goes to the Contact form with a special subject.
  * Note: there is no easy way to re-assign ownership yet other than a direct
    database query.
* Code cleanup
  * Alphabetize the DB.AutoMigrate tables.
  * Delete more things on user deletion: forum_memberships, admin_group_users
  * Vacuum worker to clean up orphaned polls after the threads are removed
2024-08-23 21:21:42 -07:00

111 lines
2.7 KiB
Go

package worker
import (
"fmt"
"code.nonshy.com/nonshy/website/pkg/log"
"code.nonshy.com/nonshy/website/pkg/models"
"code.nonshy.com/nonshy/website/pkg/photo"
)
// Vacuum runs database cleanup tasks for data consistency. Run it like `nonshy vacuum` from the CLI.
func Vacuum(dryrun bool) error {
var steps = []struct {
Label string
Fn func(bool) (int64, error)
}{
{"Comment Photos", VacuumOrphanedCommentPhotos},
{"Photos", VacuumOrphanedPhotos},
{"Polls", VacuumOrphanedPolls},
}
for _, step := range steps {
log.Warn("Vacuum: %s", step.Label)
if total, err := step.Fn(dryrun); err != nil {
log.Error("%s: %s", step.Label, err)
} else {
log.Info("Removed %d rows", total)
}
}
return nil
}
// VacuumOrphanedPolls removes any polls with forum threads no longer pointing to them.
func VacuumOrphanedPolls(dryrun bool) (int64, error) {
polls, count, err := models.GetOrphanedPolls()
if err != nil {
return count, err
}
if dryrun {
return count, nil
}
for _, row := range polls {
log.Info(" #%d: %s", row.ID, row.Choices)
if err := row.Delete(); err != nil {
return count, fmt.Errorf("deleting orphaned poll (%d): %s", row.ID, err)
}
}
return count, nil
}
// VacuumOrphanedPhotos removes any lingering photo from failed account deletion.
func VacuumOrphanedPhotos(dryrun bool) (int64, error) {
photos, count, err := models.GetOrphanedPhotos()
if err != nil {
return count, err
}
if dryrun {
return count, nil
}
for _, row := range photos {
log.Info(" #%d: %s", row.ID, row.Filename)
if err := photo.Delete(row.Filename); err != nil {
return count, fmt.Errorf("photo ID %d: removing file %s: %s", row.ID, row.Filename, err)
}
if row.CroppedFilename != "" {
if err := photo.Delete(row.CroppedFilename); err != nil {
return count, fmt.Errorf("photo ID %d: removing file %s: %s", row.ID, row.Filename, err)
}
}
if err := row.Delete(); err != nil {
return count, fmt.Errorf("deleting orphaned photo (%d): %s", row.ID, err)
}
}
return count, nil
}
// VacuumOrphanedCommentPhotos cleans up comment photos that weren't associated to a post, returning the count removed.
func VacuumOrphanedCommentPhotos(dryrun bool) (int64, error) {
// Do the needful.
photos, total, err := models.GetOrphanedCommentPhotos()
if err != nil {
return total, err
}
if dryrun {
return total, nil
}
for _, row := range photos {
if err := photo.Delete(row.Filename); err != nil {
return total, fmt.Errorf("photo ID %d: removing file %s: %s", row.ID, row.Filename, err)
}
if err := row.Delete(); err != nil {
return total, fmt.Errorf("deleting orphaned comment photo (%d): %s", row.ID, err)
}
}
return total, nil
}