Enhance embed functionality across scheduler and welcome components

- Updated SchedulerManager and WelcomeForm to integrate DiscordEmbedBuilder for managing embeds, replacing JSON text areas with a more user-friendly interface.
- Modified data handling in the scheduler and welcome configurations to support embed structures, ensuring proper parsing and validation.
- Updated localization files to reflect changes in embed handling, improving user guidance and clarity in both English and German.
- Enhanced validation schemas to require embed titles or descriptions where necessary, ensuring robust data integrity.
This commit is contained in:
TheOnlyMace
2026-07-22 21:49:38 +02:00
parent 6a223892dd
commit 55a4f7bf09
10 changed files with 392 additions and 82 deletions

View File

@@ -1,5 +1,5 @@
import type { ScheduledMessageDashboard, ScheduledMessageDashboardCreate } from '@nexumi/shared';
import type { ScheduledMessage } from '@prisma/client';
import type { ScheduledMessageDashboard, ScheduledMessageDashboardCreate, WelcomeEmbed } from '@nexumi/shared';
import { Prisma, type ScheduledMessage } from '@prisma/client';
import { prisma } from '../prisma';
import { getScheduleQueue } from '../queues';
@@ -9,12 +9,27 @@ export class SchedulerValidationError extends Error {
}
}
function parseEmbed(raw: unknown): WelcomeEmbed | null {
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
return null;
}
const record = raw as Record<string, unknown>;
const title = typeof record.title === 'string' ? record.title : undefined;
const description = typeof record.description === 'string' ? record.description : undefined;
const color = typeof record.color === 'number' ? record.color : undefined;
if (!title && !description && color === undefined) {
return null;
}
return { title, description, color };
}
function toDashboard(schedule: ScheduledMessage): ScheduledMessageDashboard {
return {
id: schedule.id,
channelId: schedule.channelId,
creatorId: schedule.creatorId,
content: schedule.content,
embed: parseEmbed(schedule.embed),
rolePingId: schedule.rolePingId,
cron: schedule.cron,
runAt: schedule.runAt?.toISOString() ?? null,
@@ -69,6 +84,10 @@ export async function createScheduledMessageDashboard(
channelId: input.channelId,
creatorId,
content: input.content?.trim() || null,
embed:
input.embed === undefined || input.embed === null
? Prisma.JsonNull
: (input.embed as Prisma.InputJsonValue),
rolePingId: input.rolePingId || null,
cron,
runAt

View File

@@ -1,5 +1,5 @@
import type { TagDashboard, TagDashboardCreate, TagDashboardUpdate } from '@nexumi/shared';
import type { Tag } from '@prisma/client';
import type { TagDashboard, TagDashboardCreate, TagDashboardUpdate, WelcomeEmbed } from '@nexumi/shared';
import { Prisma, type Tag } from '@prisma/client';
import { prisma } from '../prisma';
import { assertPremiumLimit, PremiumLimitError } from '../premium';
@@ -15,11 +15,26 @@ export class TagPremiumError extends Error {
}
}
function parseEmbed(raw: unknown): WelcomeEmbed | null {
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
return null;
}
const record = raw as Record<string, unknown>;
const title = typeof record.title === 'string' ? record.title : undefined;
const description = typeof record.description === 'string' ? record.description : undefined;
const color = typeof record.color === 'number' ? record.color : undefined;
if (!title && !description && color === undefined) {
return null;
}
return { title, description, color };
}
function toDashboard(tag: Tag): TagDashboard {
return {
id: tag.id,
name: tag.name,
content: tag.content,
embed: parseEmbed(tag.embed),
responseType: tag.responseType as TagDashboard['responseType'],
triggerWord: tag.triggerWord,
allowedRoleIds: tag.allowedRoleIds,
@@ -53,6 +68,10 @@ export async function createTag(
guildId,
name: input.name,
content: input.content ?? null,
embed:
input.embed === undefined || input.embed === null
? Prisma.JsonNull
: (input.embed as Prisma.InputJsonValue),
responseType: input.responseType,
triggerWord: input.triggerWord ?? null,
allowedRoleIds: input.allowedRoleIds,
@@ -85,6 +104,9 @@ export async function updateTag(
data: {
...(patch.name !== undefined ? { name: patch.name } : {}),
...(patch.content !== undefined ? { content: patch.content } : {}),
...(patch.embed !== undefined
? { embed: patch.embed === null ? Prisma.JsonNull : (patch.embed as Prisma.InputJsonValue) }
: {}),
...(patch.responseType !== undefined ? { responseType: patch.responseType } : {}),
...(patch.triggerWord !== undefined ? { triggerWord: patch.triggerWord } : {}),
...(patch.allowedRoleIds !== undefined ? { allowedRoleIds: patch.allowedRoleIds } : {}),

View File

@@ -1,4 +1,4 @@
import type { WelcomeConfigDashboard, WelcomeConfigDashboardPatch } from '@nexumi/shared';
import type { WelcomeConfigDashboard, WelcomeConfigDashboardPatch, WelcomeEmbed } from '@nexumi/shared';
import { Prisma } from '@prisma/client';
import { prisma } from '../prisma';
@@ -11,6 +11,20 @@ async function ensureWelcomeConfig(guildId: string) {
});
}
function parseEmbed(raw: unknown): WelcomeEmbed | null {
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
return null;
}
const record = raw as Record<string, unknown>;
const title = typeof record.title === 'string' ? record.title : undefined;
const description = typeof record.description === 'string' ? record.description : undefined;
const color = typeof record.color === 'number' ? record.color : undefined;
if (!title && !description && color === undefined) {
return null;
}
return { title, description, color };
}
function toDashboard(config: Awaited<ReturnType<typeof ensureWelcomeConfig>>): WelcomeConfigDashboard {
return {
welcomeEnabled: config.welcomeEnabled,
@@ -19,9 +33,9 @@ function toDashboard(config: Awaited<ReturnType<typeof ensureWelcomeConfig>>): W
leaveChannelId: config.leaveChannelId ?? '',
welcomeType: config.welcomeType as WelcomeConfigDashboard['welcomeType'],
welcomeContent: config.welcomeContent,
welcomeEmbed: (config.welcomeEmbed as Record<string, unknown> | null) ?? null,
welcomeEmbed: parseEmbed(config.welcomeEmbed),
leaveContent: config.leaveContent,
leaveEmbed: (config.leaveEmbed as Record<string, unknown> | null) ?? null,
leaveEmbed: parseEmbed(config.leaveEmbed),
welcomeDmEnabled: config.welcomeDmEnabled,
welcomeDmContent: config.welcomeDmContent,
userAutoroleIds: config.userAutoroleIds,