From 719c0d230c985cde6e22f8c587805b0c3ef19179 Mon Sep 17 00:00:00 2001 From: Ben Visness Date: Thu, 26 Aug 2021 18:33:39 -0500 Subject: [PATCH] Delete stuff on message delete still need to do bulk delete --- src/discord/gateway.go | 75 +++++++++++++++++++ src/discord/payloads.go | 17 +++++ src/discord/todo.txt | 8 +- ...021-08-26T005607Z_FixSnippetConstraints.go | 49 ++++++++++++ 4 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 src/migration/migrations/2021-08-26T005607Z_FixSnippetConstraints.go diff --git a/src/discord/gateway.go b/src/discord/gateway.go index abf91858..a98f2d6f 100644 --- a/src/discord/gateway.go +++ b/src/discord/gateway.go @@ -557,11 +557,14 @@ func (bot *botInstance) processEventMsg(ctx context.Context, msg *GatewayMessage if err != nil { return oops.New(err, "error on updated message") } + case "MESSAGE_DELETE": + bot.messageDelete(ctx, MessageDeleteFromMap(msg.Data)) } return nil } +// TODO: Should this return an error? Or just log errors? func (bot *botInstance) messageCreateOrUpdate(ctx context.Context, msg *Message) error { if msg.OriginalHasFields("author") && msg.Author.ID == config.Config.Discord.BotUserID { // Don't process your own messages @@ -587,6 +590,78 @@ func (bot *botInstance) messageCreateOrUpdate(ctx context.Context, msg *Message) return nil } +func (bot *botInstance) messageDelete(ctx context.Context, msgDelete MessageDelete) { + log := logging.ExtractLogger(ctx) + + tx, err := bot.dbConn.Begin(ctx) + if err != nil { + log.Error().Err(err).Msg("failed to start transaction") + return + } + defer tx.Rollback(ctx) + + type deleteMessageQuery struct { + Message models.DiscordMessage `db:"msg"` + DiscordUser *models.DiscordUser `db:"duser"` + HMNUser *models.User `db:"hmnuser"` + SnippetID *int `db:"snippet.id"` + } + iresult, err := db.QueryOne(ctx, tx, deleteMessageQuery{}, + ` + SELECT $columns + FROM + handmade_discordmessage AS msg + LEFT JOIN handmade_discorduser AS duser ON msg.user_id = duser.userid + LEFT JOIN auth_user AS hmnuser ON duser.hmn_user_id = hmnuser.id + LEFT JOIN handmade_snippet AS snippet ON snippet.discord_message_id = msg.id + WHERE msg.id = $1 AND msg.channel_id = $2 + `, + msgDelete.ID, msgDelete.ChannelID, + ) + if errors.Is(err, db.ErrNoMatchingRows) { + return + } else if err != nil { + log.Error().Err(err).Msg("failed to check for message to delete") + return + } + result := iresult.(*deleteMessageQuery) + + log.Debug().Msg("deleting Discord message") + _, err = tx.Exec(ctx, + ` + DELETE FROM handmade_discordmessage + WHERE id = $1 AND channel_id = $2 + `, + msgDelete.ID, + msgDelete.ChannelID, + ) + + shouldDeleteSnippet := result.HMNUser != nil && result.HMNUser.DiscordDeleteSnippetOnMessageDelete + if result.SnippetID != nil && shouldDeleteSnippet { + log.Debug(). + Int("snippet_id", *result.SnippetID). + Int("user_id", result.HMNUser.ID). + Msg("deleting snippet from Discord message") + _, err = tx.Exec(ctx, + ` + DELETE FROM handmade_snippet + WHERE id = $1 + `, + result.SnippetID, + ) + if err != nil { + log.Error().Err(err).Msg("failed to delete snippet") + return + } + } + + err = tx.Commit(ctx) + if err != nil { + log.Error().Err(err).Msg("failed to delete Discord message") + return + } +} + type MessageToSend struct { ChannelID string Req CreateMessageRequest diff --git a/src/discord/payloads.go b/src/discord/payloads.go index 0b0f786a..0ab88aa6 100644 --- a/src/discord/payloads.go +++ b/src/discord/payloads.go @@ -110,6 +110,23 @@ type Resume struct { SequenceNumber int `json:"seq"` } +// https://discord.com/developers/docs/topics/gateway#message-delete +type MessageDelete struct { + ID string `json:"id"` + ChannelID string `json:"channel_id"` + GuildID string `json:"guild_id"` +} + +func MessageDeleteFromMap(m interface{}) MessageDelete { + mmap := m.(map[string]interface{}) + + return MessageDelete{ + ID: mmap["id"].(string), + ChannelID: mmap["channel_id"].(string), + GuildID: maybeString(mmap, "guild_id"), + } +} + type ChannelType int // https://discord.com/developers/docs/resources/channel#channel-object-channel-types diff --git a/src/discord/todo.txt b/src/discord/todo.txt index ba71d422..0a17cd0f 100644 --- a/src/discord/todo.txt +++ b/src/discord/todo.txt @@ -7,9 +7,9 @@ stuff we need to worry about: - posts that come in while the bot is down - what to do with posts if you unlink your account - what to do with posts if you re-link your account -- what to do if you edit the original discord message +✔ - what to do if you edit the original discord message - what to do if you delete the original discord message -- the user's preferences re: saving content +✔ - the user's preferences re: saving content - we don't want to save content without the user's consent, especially since it may persist after they disable the integration - manually adding content for various reasons - maybe a bug prevented something from saving @@ -17,10 +17,10 @@ stuff we need to worry about: real-time stuff: -- on new showcase message +✔ - on new showcase message - always save the lightweight record - if we have permission, create a snippet -- on edit +✔ - on edit - re-save the lightweight record and content as if it was new - create snippet, unconditionally???? (bug??) - update snippet contents if the edit makes sense diff --git a/src/migration/migrations/2021-08-26T005607Z_FixSnippetConstraints.go b/src/migration/migrations/2021-08-26T005607Z_FixSnippetConstraints.go new file mode 100644 index 00000000..ff5ec55f --- /dev/null +++ b/src/migration/migrations/2021-08-26T005607Z_FixSnippetConstraints.go @@ -0,0 +1,49 @@ +package migrations + +import ( + "context" + "time" + + "git.handmade.network/hmn/hmn/src/migration/types" + "git.handmade.network/hmn/hmn/src/oops" + "github.com/jackc/pgx/v4" +) + +func init() { + registerMigration(FixSnippetConstraints{}) +} + +type FixSnippetConstraints struct{} + +func (m FixSnippetConstraints) Version() types.MigrationVersion { + return types.MigrationVersion(time.Date(2021, 8, 26, 0, 56, 7, 0, time.UTC)) +} + +func (m FixSnippetConstraints) Name() string { + return "FixSnippetConstraints" +} + +func (m FixSnippetConstraints) Description() string { + return "Fix the ON DELETE behaviors of snippets" +} + +func (m FixSnippetConstraints) Up(ctx context.Context, tx pgx.Tx) error { + _, err := tx.Exec(ctx, ` + ALTER TABLE handmade_snippet + DROP CONSTRAINT handmade_snippet_asset_id_c786de4f_fk_handmade_asset_id, + DROP CONSTRAINT handmade_snippet_discord_message_id_d16f1f4e_fk_handmade_, + DROP CONSTRAINT handmade_snippet_owner_id_fcca1783_fk_auth_user_id, + ADD FOREIGN KEY (asset_id) REFERENCES handmade_asset (id) ON DELETE SET NULL, + ADD FOREIGN KEY (discord_message_id) REFERENCES handmade_discordmessage (id) ON DELETE SET NULL, + ADD FOREIGN KEY (owner_id) REFERENCES auth_user (id) ON DELETE CASCADE; + `) + if err != nil { + return oops.New(err, "failed to fix constraints") + } + + return nil +} + +func (m FixSnippetConstraints) Down(ctx context.Context, tx pgx.Tx) error { + panic("Implement me") +}