177 lines
5.9 KiB
TypeScript
177 lines
5.9 KiB
TypeScript
import { createCommand } from "@shared/lib/utils";
|
|
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags, ComponentType } from "discord.js";
|
|
import { UpdateService } from "@shared/modules/admin/update.service";
|
|
import {
|
|
getCheckingEmbed,
|
|
getNoUpdatesEmbed,
|
|
getUpdatesAvailableMessage,
|
|
getPreparingEmbed,
|
|
getUpdatingEmbed,
|
|
getCancelledEmbed,
|
|
getTimeoutEmbed,
|
|
getErrorEmbed,
|
|
getRollbackSuccessEmbed,
|
|
getRollbackFailedEmbed
|
|
} from "@/modules/admin/update.view";
|
|
|
|
export const update = createCommand({
|
|
data: new SlashCommandBuilder()
|
|
.setName("update")
|
|
.setDescription("Check for updates and restart the bot")
|
|
.addSubcommand(sub =>
|
|
sub.setName("check")
|
|
.setDescription("Check for and apply available updates")
|
|
.addBooleanOption(option =>
|
|
option.setName("force")
|
|
.setDescription("Force update even if no changes detected")
|
|
.setRequired(false)
|
|
)
|
|
)
|
|
.addSubcommand(sub =>
|
|
sub.setName("rollback")
|
|
.setDescription("Rollback to the previous version")
|
|
)
|
|
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
|
|
|
execute: async (interaction) => {
|
|
const subcommand = interaction.options.getSubcommand();
|
|
|
|
if (subcommand === "rollback") {
|
|
await handleRollback(interaction);
|
|
} else {
|
|
await handleUpdate(interaction);
|
|
}
|
|
}
|
|
});
|
|
|
|
async function handleUpdate(interaction: any) {
|
|
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
|
const force = interaction.options.getBoolean("force") || false;
|
|
|
|
try {
|
|
// 1. Check for updates
|
|
await interaction.editReply({ embeds: [getCheckingEmbed()] });
|
|
const updateInfo = await UpdateService.checkForUpdates();
|
|
|
|
if (!updateInfo.hasUpdates && !force) {
|
|
await interaction.editReply({
|
|
embeds: [getNoUpdatesEmbed(updateInfo.currentCommit)]
|
|
});
|
|
return;
|
|
}
|
|
|
|
// 2. Analyze requirements
|
|
const requirements = await UpdateService.checkUpdateRequirements(updateInfo.branch);
|
|
const categories = UpdateService.categorizeChanges(requirements.changedFiles);
|
|
|
|
// 3. Show confirmation with details
|
|
const { embeds, components } = getUpdatesAvailableMessage(
|
|
updateInfo,
|
|
requirements,
|
|
categories,
|
|
force
|
|
);
|
|
const response = await interaction.editReply({ embeds, components });
|
|
|
|
// 4. Wait for confirmation
|
|
try {
|
|
const confirmation = await response.awaitMessageComponent({
|
|
filter: (i: any) => i.user.id === interaction.user.id,
|
|
componentType: ComponentType.Button,
|
|
time: 30000
|
|
});
|
|
|
|
if (confirmation.customId === "confirm_update") {
|
|
await confirmation.update({
|
|
embeds: [getPreparingEmbed()],
|
|
components: []
|
|
});
|
|
|
|
// 5. Save rollback point
|
|
const previousCommit = await UpdateService.saveRollbackPoint();
|
|
|
|
// 6. Prepare restart context
|
|
await UpdateService.prepareRestartContext({
|
|
channelId: interaction.channelId,
|
|
userId: interaction.user.id,
|
|
timestamp: Date.now(),
|
|
runMigrations: requirements.needsMigrations,
|
|
installDependencies: requirements.needsRootInstall || requirements.needsWebInstall,
|
|
previousCommit: previousCommit.substring(0, 7),
|
|
newCommit: updateInfo.latestCommit
|
|
});
|
|
|
|
// 7. Show updating status
|
|
await interaction.editReply({
|
|
embeds: [getUpdatingEmbed(requirements)]
|
|
});
|
|
|
|
// 8. Perform update
|
|
await UpdateService.performUpdate(updateInfo.branch);
|
|
|
|
// 9. Trigger restart
|
|
await UpdateService.triggerRestart();
|
|
|
|
} else {
|
|
await confirmation.update({
|
|
embeds: [getCancelledEmbed()],
|
|
components: []
|
|
});
|
|
}
|
|
|
|
} catch (e) {
|
|
if (e instanceof Error && e.message.includes("time")) {
|
|
await interaction.editReply({
|
|
embeds: [getTimeoutEmbed()],
|
|
components: []
|
|
});
|
|
} else {
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error("Update failed:", error);
|
|
await interaction.editReply({
|
|
embeds: [getErrorEmbed(error)],
|
|
components: []
|
|
});
|
|
}
|
|
}
|
|
|
|
async function handleRollback(interaction: any) {
|
|
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
|
|
|
try {
|
|
const hasRollback = await UpdateService.hasRollbackPoint();
|
|
|
|
if (!hasRollback) {
|
|
await interaction.editReply({
|
|
embeds: [getRollbackFailedEmbed("No rollback point available. Rollback is only possible after a recent update.")]
|
|
});
|
|
return;
|
|
}
|
|
|
|
const result = await UpdateService.rollback();
|
|
|
|
if (result.success) {
|
|
await interaction.editReply({
|
|
embeds: [getRollbackSuccessEmbed(result.message.split(" ").pop() || "unknown")]
|
|
});
|
|
|
|
// Restart after rollback
|
|
setTimeout(() => UpdateService.triggerRestart(), 1000);
|
|
} else {
|
|
await interaction.editReply({
|
|
embeds: [getRollbackFailedEmbed(result.message)]
|
|
});
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error("Rollback failed:", error);
|
|
await interaction.editReply({
|
|
embeds: [getErrorEmbed(error)]
|
|
});
|
|
}
|
|
}
|