website/pkg/models/deletion/delete_user.go
Noah 93c13882aa Finish Forums + Likes & Notifications
Finish implementing the basic forum features:
* Pinned threads (admin or board owner only)
* Edit Thread settings when you edit the top-most comment.
* NoReply threads remove all the reply buttons.
* Explicit forums and threads are filtered out unless opted-in (admins
  always see them).
* Count the unique members who participated in each forum.
* Get the most recently updated thread to show on forum list page.
* Contact/Report page: handle receiving a comment ID to report on.

Implement Likes & Notifications
* Like buttons added to Photos and Profile Pages. Implemented via simple
  vanilla JS (likes.js) to make ajax requests to back-end to like/unlike.
* Notifications: for your photo or profile being liked. If you unlike,
  the existing notifications about the like are revoked.
* The notifications appear as an alert number in the nav bar and are read
  on the User Dashboard. Click to mark a notification as "read" or click
  the "mark all as read" button.

Update DeleteUser to scrub likes, notifications, threads, and comments.
2022-08-24 21:17:34 -07:00

202 lines
4.9 KiB
Go

package deletion
import (
"fmt"
"git.kirsle.net/apps/gosocial/pkg/log"
"git.kirsle.net/apps/gosocial/pkg/models"
"git.kirsle.net/apps/gosocial/pkg/photo"
)
// DeleteUser wipes a user and all associated data from the database.
func DeleteUser(user *models.User) error {
log.Error("BEGIN DeleteUser(%d, %s)", user.ID, user.Username)
// Remove all linked tables and assets.
type remover struct {
Step string
Fn func(uint64) error
}
var todo = []remover{
{"Notifications", DeleteNotifications},
{"Likes", DeleteLikes},
{"Threads", DeleteForumThreads},
{"Comments", DeleteComments},
{"Photos", DeleteUserPhotos},
{"Certification Photo", DeleteCertification},
{"Messages", DeleteUserMessages},
{"Friends", DeleteFriends},
{"Profile Fields", DeleteProfile},
}
for _, item := range todo {
if err := item.Fn(user.ID); err != nil {
return fmt.Errorf("%s: %s", item.Step, err)
}
}
// Remove the user itself.
return user.Delete()
}
// DeleteUserPhotos scrubs data for deleting a user.
func DeleteUserPhotos(userID uint64) error {
log.Error("DeleteUser: BEGIN DeleteUserPhotos(%d)", userID)
// Deeply scrub all user photos.
pager := &models.Pagination{
Page: 1,
PerPage: 20,
Sort: "photos.id",
}
for {
photos, err := models.PaginateUserPhotos(
userID,
models.PhotoVisibilityAll,
true,
pager,
)
if err != nil {
return err
}
if len(photos) == 0 {
break
}
for _, item := range photos {
log.Warn("DeleteUserPhotos(%d): remove file %s", userID, item.Filename)
photo.Delete(item.Filename)
if item.CroppedFilename != "" {
log.Warn("DeleteUserPhotos(%d): remove file %s", userID, item.CroppedFilename)
photo.Delete(item.CroppedFilename)
}
if err := item.Delete(); err != nil {
return err
}
}
}
log.Error("DeleteUser: END DeleteUserPhotos(%d)", userID)
return nil
}
// DeleteCertification scrubs data for deleting a user.
func DeleteCertification(userID uint64) error {
log.Error("DeleteUser: DeleteCertification(%d)", userID)
if cert, err := models.GetCertificationPhoto(userID); err == nil {
if cert.Filename != "" {
log.Warn("DeleteCertification(%d): remove file %s", userID, cert.Filename)
photo.Delete(cert.Filename)
}
return cert.Delete()
}
return nil
}
// DeleteUserMessages scrubs data for deleting a user.
func DeleteUserMessages(userID uint64) error {
log.Error("DeleteUser: DeleteUserMessages(%d)", userID)
result := models.DB.Where(
"source_user_id = ? OR target_user_id = ?",
userID, userID,
).Delete(&models.Message{})
return result.Error
}
// DeleteFriends scrubs data for deleting a user.
func DeleteFriends(userID uint64) error {
log.Error("DeleteUser: DeleteUserFriends(%d)", userID)
result := models.DB.Where(
"source_user_id = ? OR target_user_id = ?",
userID, userID,
).Delete(&models.Friend{})
return result.Error
}
// DeleteNotifications scrubs all notifications about a user.
func DeleteNotifications(userID uint64) error {
log.Error("DeleteUser: DeleteNotifications(%d)", userID)
result := models.DB.Where(
"user_id = ? OR about_user_id = ?",
userID, userID,
).Delete(&models.Notification{})
return result.Error
}
// DeleteLikes scrubs all Likes about a user.
func DeleteLikes(userID uint64) error {
log.Error("DeleteUser: DeleteLikes(%d)", userID)
result := models.DB.Where(
"user_id = ? OR (table_name='users' AND table_id=?)",
userID, userID,
).Delete(&models.Like{})
return result.Error
}
// DeleteProfile scrubs data for deleting a user.
func DeleteProfile(userID uint64) error {
log.Error("DeleteUser: DeleteProfile(%d)", userID)
result := models.DB.Where(
"user_id = ?",
userID,
).Delete(&models.ProfileField{})
return result.Error
}
// DeleteForumThreads scrubs all forum threads started by the user.
func DeleteForumThreads(userID uint64) error {
log.Error("DeleteUser: DeleteForumThreads(%d)", userID)
var threadIDs = []uint64{}
result := models.DB.Table(
"threads",
).Joins(
"JOIN comments ON (threads.comment_id = comments.id)",
).Select(
"distinct(threads.id) as id",
).Where(
"comments.user_id = ?",
userID,
).Scan(&threadIDs)
if result.Error != nil {
return fmt.Errorf("Couldn't list thread IDs created by user: %s", result.Error)
}
log.Warn("thread IDs to wipe: %+v", threadIDs)
// Wipe all these threads and their comments.
if len(threadIDs) > 0 {
result = models.DB.Where(
"table_name = ? AND table_id IN ?",
"threads", threadIDs,
).Delete(&models.Comment{})
if result.Error != nil {
return fmt.Errorf("Couldn't wipe threads of comments: %s", result.Error)
}
// And finish the threads off too.
result = models.DB.Where(
"id IN ?",
threadIDs,
).Delete(&models.Thread{})
return result.Error
}
return nil
}
// DeleteComments deletes all comments by the user.
func DeleteComments(userID uint64) error {
log.Error("DeleteUser: DeleteComments(%d)", userID)
result := models.DB.Where(
"user_id = ?",
userID,
).Delete(&models.Comment{})
return result.Error
}