forked from Xeio/IdleCodeRedeemer
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathdeleteaccount.ts
More file actions
165 lines (150 loc) · 6.06 KB
/
deleteaccount.ts
File metadata and controls
165 lines (150 loc) · 6.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import {
SlashCommandBuilder,
ChatInputCommandInteraction,
EmbedBuilder,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
ComponentType,
MessageFlags,
} from 'discord.js';
import { userManager } from '../database/userManager';
import { codeManager } from '../database/codeManager';
import { auditManager } from '../database/auditManager';
import { backfillManager } from '../database/backfillManager';
import logger from '../utils/logger';
export const data = new SlashCommandBuilder()
.setName('deleteaccount')
.setDescription('Permanently delete all your data from this bot (credentials, code history, audit log)');
export async function execute(interaction: ChatInputCommandInteraction) {
try {
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
const hasCredentials = await userManager.hasCredentials(interaction.user.id);
const hasBackfillOps = await backfillManager.hasUserBackfillOperations(interaction.user.id);
if (!hasCredentials && !hasBackfillOps) {
const embed = new EmbedBuilder()
.setColor(0xffaa00)
.setTitle('⚠️ No Account Found')
.setDescription('You have no stored data in this bot — nothing to delete.');
await interaction.editReply({ embeds: [embed] });
return;
}
const confirmEmbed = new EmbedBuilder()
.setColor(0xff0000)
.setTitle('⚠️ Delete Account — Are you sure?')
.setDescription(
'This will **permanently and irreversibly** delete all of your data:\n\n' +
'• Your Idle Champions credentials\n' +
'• Your full code redemption history\n' +
'• Your audit log entries\n' +
'• Your backfill operation history\n\n' +
'You will need to run `/setup` again to use the bot after this.'
)
.setFooter({ text: 'This action cannot be undone. Confirmation expires in 30 seconds.' });
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId('deleteaccount_confirm')
.setLabel('Yes, delete everything')
.setStyle(ButtonStyle.Danger),
new ButtonBuilder()
.setCustomId('deleteaccount_cancel')
.setLabel('Cancel')
.setStyle(ButtonStyle.Secondary)
);
const reply = await interaction.editReply({ embeds: [confirmEmbed], components: [row] });
let buttonInteraction;
try {
buttonInteraction = await reply.awaitMessageComponent({
componentType: ComponentType.Button,
filter: (i) => i.user.id === interaction.user.id,
time: 30_000,
});
} catch {
// Timed out — disable the buttons
const disabledRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId('deleteaccount_confirm')
.setLabel('Yes, delete everything')
.setStyle(ButtonStyle.Danger)
.setDisabled(true),
new ButtonBuilder()
.setCustomId('deleteaccount_cancel')
.setLabel('Cancel')
.setStyle(ButtonStyle.Secondary)
.setDisabled(true)
);
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(0x888888)
.setTitle('⏱️ Confirmation Timed Out')
.setDescription('No response received within 30 seconds. Account deletion cancelled.'),
],
components: [disabledRow],
});
return;
}
await buttonInteraction.deferUpdate();
if (buttonInteraction.customId === 'deleteaccount_cancel') {
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(0x00aa00)
.setTitle('✅ Cancelled')
.setDescription('Account deletion cancelled. Your data has not been changed.'),
],
components: [],
});
return;
}
// Refuse deletion if the user has an active backfill — completing it would
// try to write to rows that no longer exist after account deletion.
if (await backfillManager.hasUserActiveBackfill(interaction.user.id)) {
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(0xffaa00)
.setTitle('⚠️ Backfill In Progress')
.setDescription(
'A backfill operation you initiated is currently running. ' +
'Please wait for it to complete before deleting your account.'
),
],
components: [],
});
return;
}
// Perform deletion — order matters for FK constraints:
// pending_codes.discord_id → users.discord_id (no cascade), so clear it first
await codeManager.clearPendingCodes(interaction.user.id);
const deletedCodesCount = await codeManager.deleteUserRedeemedCodes(interaction.user.id);
await auditManager.deleteUserAuditLog(interaction.user.id);
const deletedBackfillCount = await backfillManager.deleteUserBackfillOperations(interaction.user.id);
await userManager.deleteCredentials(interaction.user.id);
// Log a non-identifying event — the user's credentials and ID are now gone
logger.info(
`[DELETE ACCOUNT] Account deletion completed. Removed ${deletedCodesCount} redeemed code record(s) and ${deletedBackfillCount} backfill operation record(s).`
);
await interaction.editReply({
embeds: [
new EmbedBuilder()
.setColor(0x00aa00)
.setTitle('✅ Account Deleted')
.setDescription(
'All your data has been permanently removed:\n\n' +
`• Credentials deleted\n` +
`• ${deletedCodesCount} code record(s) deleted\n` +
'• Audit log entries deleted\n' +
`• ${deletedBackfillCount} backfill operation record(s) deleted\n\n` +
'If you want to use the bot again in the future, simply run `/setup`.'
),
],
components: [],
});
} catch (error) {
logger.error('[DELETE ACCOUNT] Error:', error);
await interaction.editReply({
content: '❌ An error occurred while deleting your account. Please try again later.',
});
}
}