Implement maintenance mode and presence updates in bot and WebUI

- Added maintenance mode functionality in the bot, allowing for a custom message and DND status when enabled.
- Updated the OwnerPresencePage to include read-only access for VIEWER roles, with save permissions restricted to ADMIN roles.
- Enhanced the PresenceForm component to support immediate application of changes and added a confirmation for maintenance mode activation.
- Introduced localization updates for presence-related messages in both English and German, improving user guidance.
- Implemented a presence refresh queue to handle updates efficiently after configuration changes.
This commit is contained in:
smueller
2026-07-24 11:11:58 +02:00
parent 211403d54d
commit e45642b6f9
13 changed files with 414 additions and 153 deletions

View File

@@ -5,6 +5,7 @@ import {
type BotPresenceConfig
} from '@nexumi/shared';
import { prisma } from './prisma';
import { getPresenceQueue } from './queues';
import { redis } from './redis';
function mapRow(row: {
@@ -63,5 +64,13 @@ export async function updatePresenceConfig(patch: unknown): Promise<BotPresenceC
});
const mapped = mapRow(row);
await redis.set(PRESENCE_REDIS_KEY, JSON.stringify(mapped));
await getPresenceQueue().add(
'presenceRefresh',
{},
{
removeOnComplete: 5,
removeOnFail: 5
}
);
return mapped;
}

View File

@@ -21,6 +21,7 @@ export const suggestionsQueueName = 'suggestions';
export const messagesQueueName = 'messages';
export const verificationQueueName = 'verification';
export const automodQueueName = 'automod';
export const presenceQueueName = 'presence';
const globalForQueues = globalThis as unknown as {
__nexumiGiveawayQueue?: Queue;
@@ -30,6 +31,7 @@ const globalForQueues = globalThis as unknown as {
__nexumiMessagesQueue?: Queue;
__nexumiVerificationQueue?: Queue;
__nexumiAutomodQueue?: Queue;
__nexumiPresenceQueue?: Queue;
__nexumiGiveawayQueueEvents?: QueueEvents;
__nexumiGuildBackupQueueEvents?: QueueEvents;
__nexumiSuggestionsQueueEvents?: QueueEvents;
@@ -94,6 +96,13 @@ export function getAutomodQueue(): Queue {
return globalForQueues.__nexumiAutomodQueue;
}
export function getPresenceQueue(): Queue {
globalForQueues.__nexumiPresenceQueue ??= new Queue(presenceQueueName, {
connection: queueConnection()
});
return globalForQueues.__nexumiPresenceQueue;
}
/**
* QueueEvents instances are only needed for jobs where the dashboard needs
* to wait for the bot to finish (e.g. ending a giveaway so the winners list

View File

@@ -0,0 +1,40 @@
import type { DashboardGuild, SessionUser } from '@nexumi/shared';
function discordDefaultAvatarUrl(userId: string): string {
try {
const index = Number((BigInt(userId) >> 22n) % 6n);
return `https://cdn.discordapp.com/embed/avatars/${index}.png`;
} catch {
return 'https://cdn.discordapp.com/embed/avatars/0.png';
}
}
function userAvatarUrl(user: SessionUser): string {
return user.avatar
? `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png?size=256`
: discordDefaultAvatarUrl(user.id);
}
function guildIconUrl(guild: DashboardGuild): string {
return guild.icon
? `https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.png?size=128`
: discordDefaultAvatarUrl(guild.id);
}
/** Preview placeholders from the logged-in dashboard user and current guild. */
export function buildWelcomePreviewVars(
user: SessionUser,
guild: DashboardGuild
): Record<string, string> {
const displayName = user.globalName?.trim() || user.username;
return {
user: `@${displayName}`,
'user.name': displayName,
'user.tag': user.username,
'user.id': user.id,
'user.avatar': userAvatarUrl(user),
server: guild.name,
'server.icon': guildIconUrl(guild),
memberCount: '—'
};
}