[deploy] add register module backend
Some checks failed
Deploy Discord Bot / deploy (push) Failing after 20s
Some checks failed
Deploy Discord Bot / deploy (push) Failing after 20s
This commit is contained in:
@@ -26,8 +26,7 @@ model GuildSettings {
|
|||||||
birthdayConfig Json?
|
birthdayConfig Json?
|
||||||
reactionRolesEnabled Boolean?
|
reactionRolesEnabled Boolean?
|
||||||
reactionRolesConfig Json?
|
reactionRolesConfig Json?
|
||||||
eventsEnabled Boolean?
|
eventsEnabled Boolean?\n registerEnabled Boolean?\n registerConfig Json?\n supportRoleId String?
|
||||||
supportRoleId String?
|
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
}
|
}
|
||||||
@@ -161,3 +160,55 @@ model EventSignup {
|
|||||||
@@unique([eventId, userId])
|
@@unique([eventId, userId])
|
||||||
@@index([guildId, eventId])
|
@@index([guildId, eventId])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model RegisterForm {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
guildId String
|
||||||
|
name String
|
||||||
|
description String?
|
||||||
|
reviewChannelId String?
|
||||||
|
notifyRoleIds String[]
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
fields RegisterFormField[]
|
||||||
|
|
||||||
|
@@index([guildId, isActive])
|
||||||
|
}
|
||||||
|
|
||||||
|
model RegisterFormField {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
formId String
|
||||||
|
label String
|
||||||
|
type String
|
||||||
|
required Boolean @default(false)
|
||||||
|
"order" Int @default(0)
|
||||||
|
|
||||||
|
form RegisterForm @relation(fields: [formId], references: [id], onDelete: Cascade)
|
||||||
|
}
|
||||||
|
|
||||||
|
model RegisterApplication {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
guildId String
|
||||||
|
userId String
|
||||||
|
formId String
|
||||||
|
status String @default("pending")
|
||||||
|
reviewedBy String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
answers RegisterApplicationAnswer[]
|
||||||
|
|
||||||
|
form RegisterForm @relation(fields: [formId], references: [id])
|
||||||
|
|
||||||
|
@@index([guildId, formId, status])
|
||||||
|
}
|
||||||
|
|
||||||
|
model RegisterApplicationAnswer {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
applicationId String
|
||||||
|
fieldId String
|
||||||
|
value String
|
||||||
|
|
||||||
|
application RegisterApplication @relation(fields: [applicationId], references: [id], onDelete: Cascade)
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { ReactionRoleService } from '../services/reactionRoleService';
|
|||||||
import { EventService } from '../services/eventService';
|
import { EventService } from '../services/eventService';
|
||||||
import { TicketAutomationService } from '../services/ticketAutomationService';
|
import { TicketAutomationService } from '../services/ticketAutomationService';
|
||||||
import { KnowledgeBaseService } from '../services/knowledgeBaseService';
|
import { KnowledgeBaseService } from '../services/knowledgeBaseService';
|
||||||
|
import { RegisterService } from '../services/registerService';
|
||||||
|
|
||||||
export const context = {
|
export const context = {
|
||||||
client: null as Client | null,
|
client: null as Client | null,
|
||||||
@@ -31,7 +32,8 @@ export const context = {
|
|||||||
reactionRoles: new ReactionRoleService(),
|
reactionRoles: new ReactionRoleService(),
|
||||||
events: new EventService(),
|
events: new EventService(),
|
||||||
ticketAutomation: new TicketAutomationService(),
|
ticketAutomation: new TicketAutomationService(),
|
||||||
knowledgeBase: new KnowledgeBaseService()
|
knowledgeBase: new KnowledgeBaseService(),
|
||||||
|
register: new RegisterService()
|
||||||
};
|
};
|
||||||
|
|
||||||
context.modules.setHooks({
|
context.modules.setHooks({
|
||||||
|
|||||||
@@ -60,6 +60,11 @@ export interface GuildSettings {
|
|||||||
};
|
};
|
||||||
reactionRolesEnabled?: boolean;
|
reactionRolesEnabled?: boolean;
|
||||||
reactionRolesConfig?: any;
|
reactionRolesConfig?: any;
|
||||||
|
registerEnabled?: boolean;
|
||||||
|
registerConfig?: {
|
||||||
|
reviewChannelId?: string;
|
||||||
|
notifyRoleIds?: string[];
|
||||||
|
};
|
||||||
supportRoleId?: string;
|
supportRoleId?: string;
|
||||||
welcomeEnabled?: boolean;
|
welcomeEnabled?: boolean;
|
||||||
}
|
}
|
||||||
@@ -80,7 +85,8 @@ class SettingsStore {
|
|||||||
'statuspageEnabled',
|
'statuspageEnabled',
|
||||||
'birthdayEnabled',
|
'birthdayEnabled',
|
||||||
'reactionRolesEnabled',
|
'reactionRolesEnabled',
|
||||||
'eventsEnabled'
|
'eventsEnabled',
|
||||||
|
'registerEnabled'
|
||||||
] as const
|
] as const
|
||||||
).forEach((key) => {
|
).forEach((key) => {
|
||||||
if (normalized[key] === undefined) normalized[key] = true;
|
if (normalized[key] === undefined) normalized[key] = true;
|
||||||
@@ -116,6 +122,8 @@ class SettingsStore {
|
|||||||
birthdayConfig: (row as any).birthdayConfig ?? undefined,
|
birthdayConfig: (row as any).birthdayConfig ?? undefined,
|
||||||
reactionRolesEnabled: (row as any).reactionRolesEnabled ?? undefined,
|
reactionRolesEnabled: (row as any).reactionRolesEnabled ?? undefined,
|
||||||
reactionRolesConfig: (row as any).reactionRolesConfig ?? undefined,
|
reactionRolesConfig: (row as any).reactionRolesConfig ?? undefined,
|
||||||
|
registerEnabled: (row as any).registerEnabled ?? undefined,
|
||||||
|
registerConfig: (row as any).registerConfig ?? undefined,
|
||||||
supportRoleId: row.supportRoleId ?? undefined
|
supportRoleId: row.supportRoleId ?? undefined
|
||||||
} satisfies GuildSettings;
|
} satisfies GuildSettings;
|
||||||
this.cache.set(row.guildId, this.applyModuleDefaults(cfg));
|
this.cache.set(row.guildId, this.applyModuleDefaults(cfg));
|
||||||
@@ -147,6 +155,9 @@ class SettingsStore {
|
|||||||
} else if (partial.reactionRolesConfig?.enabled !== undefined) {
|
} else if (partial.reactionRolesConfig?.enabled !== undefined) {
|
||||||
partial.reactionRolesEnabled = partial.reactionRolesConfig.enabled;
|
partial.reactionRolesEnabled = partial.reactionRolesConfig.enabled;
|
||||||
}
|
}
|
||||||
|
if (!partial.registerConfig && partial.registerEnabled !== undefined) {
|
||||||
|
partial.registerConfig = { ...(partial.registerConfig ?? {}) };
|
||||||
|
}
|
||||||
const merged: GuildSettings = this.applyModuleDefaults({ ...(this.cache.get(guildId) ?? {}), ...partial });
|
const merged: GuildSettings = this.applyModuleDefaults({ ...(this.cache.get(guildId) ?? {}), ...partial });
|
||||||
const mergedAutomod = {
|
const mergedAutomod = {
|
||||||
...(merged.automodConfig ?? {}),
|
...(merged.automodConfig ?? {}),
|
||||||
@@ -193,6 +204,8 @@ class SettingsStore {
|
|||||||
birthdayConfig: merged.birthdayConfig ?? null,
|
birthdayConfig: merged.birthdayConfig ?? null,
|
||||||
reactionRolesEnabled: merged.reactionRolesEnabled ?? null,
|
reactionRolesEnabled: merged.reactionRolesEnabled ?? null,
|
||||||
reactionRolesConfig: merged.reactionRolesConfig ?? null,
|
reactionRolesConfig: merged.reactionRolesConfig ?? null,
|
||||||
|
registerEnabled: merged.registerEnabled ?? null,
|
||||||
|
registerConfig: merged.registerConfig ?? null,
|
||||||
supportRoleId: merged.supportRoleId ?? null
|
supportRoleId: merged.supportRoleId ?? null
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
@@ -213,6 +226,8 @@ class SettingsStore {
|
|||||||
birthdayConfig: merged.birthdayConfig ?? null,
|
birthdayConfig: merged.birthdayConfig ?? null,
|
||||||
reactionRolesEnabled: merged.reactionRolesEnabled ?? null,
|
reactionRolesEnabled: merged.reactionRolesEnabled ?? null,
|
||||||
reactionRolesConfig: merged.reactionRolesConfig ?? null,
|
reactionRolesConfig: merged.reactionRolesConfig ?? null,
|
||||||
|
registerEnabled: merged.registerEnabled ?? null,
|
||||||
|
registerConfig: merged.registerConfig ?? null,
|
||||||
supportRoleId: merged.supportRoleId ?? null
|
supportRoleId: merged.supportRoleId ?? null
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ model GuildSettings {
|
|||||||
reactionRolesEnabled Boolean?
|
reactionRolesEnabled Boolean?
|
||||||
reactionRolesConfig Json?
|
reactionRolesConfig Json?
|
||||||
eventsEnabled Boolean?
|
eventsEnabled Boolean?
|
||||||
|
registerEnabled Boolean?
|
||||||
|
registerConfig Json?
|
||||||
supportRoleId String?
|
supportRoleId String?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
@@ -160,3 +162,54 @@ model EventSignup {
|
|||||||
@@unique([eventId, userId])
|
@@unique([eventId, userId])
|
||||||
@@index([guildId, eventId])
|
@@index([guildId, eventId])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model RegisterForm {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
guildId String
|
||||||
|
name String
|
||||||
|
description String?
|
||||||
|
reviewChannelId String?
|
||||||
|
notifyRoleIds String[]
|
||||||
|
isActive Boolean @default(true)
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
fields RegisterFormField[]
|
||||||
|
|
||||||
|
@@index([guildId, isActive])
|
||||||
|
}
|
||||||
|
|
||||||
|
model RegisterFormField {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
formId String
|
||||||
|
label String
|
||||||
|
type String
|
||||||
|
required Boolean @default(false)
|
||||||
|
"order" Int @default(0)
|
||||||
|
|
||||||
|
form RegisterForm @relation(fields: [formId], references: [id], onDelete: Cascade)
|
||||||
|
}
|
||||||
|
|
||||||
|
model RegisterApplication {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
guildId String
|
||||||
|
userId String
|
||||||
|
formId String
|
||||||
|
status String @default("pending")
|
||||||
|
reviewedBy String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
answers RegisterApplicationAnswer[]
|
||||||
|
|
||||||
|
form RegisterForm @relation(fields: [formId], references: [id])
|
||||||
|
|
||||||
|
@@index([guildId, formId, status])
|
||||||
|
}
|
||||||
|
|
||||||
|
model RegisterApplicationAnswer {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
applicationId String
|
||||||
|
fieldId String
|
||||||
|
value String
|
||||||
|
|
||||||
|
application RegisterApplication @relation(fields: [applicationId], references: [id], onDelete: Cascade)
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,8 +20,19 @@ const event: EventHandler = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (interaction.customId.startsWith('register:')) {
|
||||||
|
await context.register.handleButton(interaction as any);
|
||||||
|
return;
|
||||||
|
}
|
||||||
await context.tickets.handleButton(interaction);
|
await context.tickets.handleButton(interaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (interaction.isModalSubmit()) {
|
||||||
|
if (interaction.customId.startsWith('register:submit:')) {
|
||||||
|
await context.register.handleModal(interaction as any);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ async function bootstrap() {
|
|||||||
context.reactionRoles.setClient(client);
|
context.reactionRoles.setClient(client);
|
||||||
context.events.setClient(client);
|
context.events.setClient(client);
|
||||||
context.events.startScheduler();
|
context.events.startScheduler();
|
||||||
|
context.register.setClient(client);
|
||||||
await context.reactionRoles.loadCache();
|
await context.reactionRoles.loadCache();
|
||||||
logger.setSink((entry) => context.admin.pushLog(entry));
|
logger.setSink((entry) => context.admin.pushLog(entry));
|
||||||
for (const gid of settingsStore.all().keys()) {
|
for (const gid of settingsStore.all().keys()) {
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ export type ModuleKey =
|
|||||||
| 'statuspageEnabled'
|
| 'statuspageEnabled'
|
||||||
| 'birthdayEnabled'
|
| 'birthdayEnabled'
|
||||||
| 'reactionRolesEnabled'
|
| 'reactionRolesEnabled'
|
||||||
| 'eventsEnabled';
|
| 'eventsEnabled'
|
||||||
|
| 'registerEnabled';
|
||||||
|
|
||||||
export interface GuildModuleState {
|
export interface GuildModuleState {
|
||||||
key: ModuleKey;
|
key: ModuleKey;
|
||||||
@@ -29,7 +30,8 @@ const MODULES: Record<ModuleKey, { name: string; description: string }> = {
|
|||||||
statuspageEnabled: { name: 'Statuspage', description: 'Service Checks, Uptime und Status-Embed.' },
|
statuspageEnabled: { name: 'Statuspage', description: 'Service Checks, Uptime und Status-Embed.' },
|
||||||
birthdayEnabled: { name: 'Birthday', description: 'Geburtstage speichern und Glueckwuensche senden.' },
|
birthdayEnabled: { name: 'Birthday', description: 'Geburtstage speichern und Glueckwuensche senden.' },
|
||||||
reactionRolesEnabled: { name: 'Reaction Roles', description: 'Reaktionen vergeben und entfernen Rollen.' },
|
reactionRolesEnabled: { name: 'Reaction Roles', description: 'Reaktionen vergeben und entfernen Rollen.' },
|
||||||
eventsEnabled: { name: 'Termine', description: 'Events planen, erinnern und Anmeldungen sammeln.' }
|
eventsEnabled: { name: 'Termine', description: 'Events planen, erinnern und Anmeldungen sammeln.' },
|
||||||
|
registerEnabled: { name: 'Register', description: 'Registrierungsformulare und Bewerbungen.' }
|
||||||
};
|
};
|
||||||
|
|
||||||
export class BotModuleService {
|
export class BotModuleService {
|
||||||
|
|||||||
256
src/services/registerService.ts
Normal file
256
src/services/registerService.ts
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
import {
|
||||||
|
ActionRowBuilder,
|
||||||
|
ButtonBuilder,
|
||||||
|
ButtonInteraction,
|
||||||
|
ButtonStyle,
|
||||||
|
Client,
|
||||||
|
EmbedBuilder,
|
||||||
|
ModalBuilder,
|
||||||
|
TextInputBuilder,
|
||||||
|
TextInputStyle,
|
||||||
|
ModalSubmitInteraction,
|
||||||
|
GuildMember
|
||||||
|
} from 'discord.js';
|
||||||
|
import { prisma } from '../database';
|
||||||
|
import { settingsStore } from '../config/state';
|
||||||
|
import { env } from '../config/env';
|
||||||
|
|
||||||
|
export class RegisterService {
|
||||||
|
private client: Client | null = null;
|
||||||
|
|
||||||
|
public setClient(client: Client) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async listForms(guildId: string) {
|
||||||
|
return prisma.registerForm.findMany({ where: { guildId }, include: { fields: { orderBy: { order: 'asc' } } }, orderBy: { createdAt: 'desc' } });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async saveForm(form: {
|
||||||
|
id?: string;
|
||||||
|
guildId: string;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
reviewChannelId?: string;
|
||||||
|
notifyRoleIds?: string[];
|
||||||
|
isActive?: boolean;
|
||||||
|
fields: { id?: string; label: string; type: string; required?: boolean; order?: number }[];
|
||||||
|
}) {
|
||||||
|
const notify = (form.notifyRoleIds || []).filter(Boolean);
|
||||||
|
if (form.id) {
|
||||||
|
await prisma.registerForm.update({
|
||||||
|
where: { id: form.id },
|
||||||
|
data: {
|
||||||
|
name: form.name,
|
||||||
|
description: form.description,
|
||||||
|
reviewChannelId: form.reviewChannelId,
|
||||||
|
notifyRoleIds: notify,
|
||||||
|
isActive: form.isActive ?? true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await prisma.registerFormField.deleteMany({ where: { formId: form.id } });
|
||||||
|
await prisma.registerFormField.createMany({
|
||||||
|
data: (form.fields || []).map((f, idx) => ({
|
||||||
|
formId: form.id as string,
|
||||||
|
label: f.label,
|
||||||
|
type: f.type,
|
||||||
|
required: f.required ?? false,
|
||||||
|
order: f.order ?? idx
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
return prisma.registerForm.findUnique({ where: { id: form.id }, include: { fields: { orderBy: { order: 'asc' } } } });
|
||||||
|
}
|
||||||
|
const created = await prisma.registerForm.create({
|
||||||
|
data: {
|
||||||
|
guildId: form.guildId,
|
||||||
|
name: form.name,
|
||||||
|
description: form.description,
|
||||||
|
reviewChannelId: form.reviewChannelId,
|
||||||
|
notifyRoleIds: notify,
|
||||||
|
isActive: form.isActive ?? true,
|
||||||
|
fields: {
|
||||||
|
create: (form.fields || []).map((f, idx) => ({
|
||||||
|
label: f.label,
|
||||||
|
type: f.type,
|
||||||
|
required: f.required ?? false,
|
||||||
|
order: f.order ?? idx
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
include: { fields: { orderBy: { order: 'asc' } } }
|
||||||
|
});
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deleteForm(guildId: string, id: string) {
|
||||||
|
const form = await prisma.registerForm.findFirst({ where: { id, guildId } });
|
||||||
|
if (!form) return false;
|
||||||
|
await prisma.registerFormField.deleteMany({ where: { formId: id } });
|
||||||
|
await prisma.registerForm.delete({ where: { id } });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async sendPanel(guildId: string, formId: string, channelId?: string, message?: string) {
|
||||||
|
if (!this.client) return null;
|
||||||
|
const form = await prisma.registerForm.findFirst({ where: { id: formId, guildId }, include: { fields: true } });
|
||||||
|
if (!form) return null;
|
||||||
|
const targetChannelId = channelId || form.reviewChannelId || settingsStore.get(guildId)?.registerConfig?.reviewChannelId;
|
||||||
|
if (!targetChannelId) return null;
|
||||||
|
const guild = this.client.guilds.cache.get(guildId) ?? (await this.client.guilds.fetch(guildId).catch(() => null));
|
||||||
|
if (!guild) return null;
|
||||||
|
const channel = await guild.channels.fetch(targetChannelId).catch(() => null);
|
||||||
|
if (!channel || !channel.isTextBased()) return null;
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle(form.name)
|
||||||
|
.setDescription(message || 'Klicke auf Registrieren, um das Formular auszufüllen.')
|
||||||
|
.setColor(0xf97316);
|
||||||
|
const btn = new ButtonBuilder().setCustomId(`register:form:${form.id}`).setLabel('Registrieren').setStyle(ButtonStyle.Primary);
|
||||||
|
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(btn);
|
||||||
|
const sent = await (channel as any).send({ embeds: [embed], components: [row] });
|
||||||
|
return sent.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async handleButton(interaction: ButtonInteraction) {
|
||||||
|
if (interaction.customId.startsWith('register:form:')) {
|
||||||
|
const formId = interaction.customId.split(':')[2];
|
||||||
|
const form = await prisma.registerForm.findFirst({ where: { id: formId }, include: { fields: { orderBy: { order: 'asc' } } } });
|
||||||
|
if (!form) return interaction.reply({ content: 'Formular nicht gefunden.', ephemeral: true });
|
||||||
|
const modal = new ModalBuilder().setTitle(form.name).setCustomId(`register:submit:${form.id}`);
|
||||||
|
const components: any[] = [];
|
||||||
|
form.fields.slice(0, 5).forEach((f) => {
|
||||||
|
const input = new TextInputBuilder()
|
||||||
|
.setCustomId(f.id)
|
||||||
|
.setLabel(f.label.slice(0, 45) || 'Feld')
|
||||||
|
.setStyle(f.type === 'longText' ? TextInputStyle.Paragraph : TextInputStyle.Short)
|
||||||
|
.setRequired(f.required ?? false);
|
||||||
|
components.push(new ActionRowBuilder<TextInputBuilder>().addComponents(input));
|
||||||
|
});
|
||||||
|
modal.addComponents(components as any);
|
||||||
|
await interaction.showModal(modal);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interaction.customId.startsWith('register:review:')) {
|
||||||
|
const [, , action, appId] = interaction.customId.split(':');
|
||||||
|
const app = await prisma.registerApplication.findUnique({ where: { id: appId }, include: { form: true } });
|
||||||
|
if (!app) {
|
||||||
|
await interaction.reply({ content: 'Antrag nicht gefunden.', ephemeral: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const statusMap: any = { accept: 'accepted', invite: 'invited', reject: 'rejected' };
|
||||||
|
const newStatus = statusMap[action] || 'pending';
|
||||||
|
const updated = await prisma.registerApplication.update({
|
||||||
|
where: { id: appId },
|
||||||
|
data: { status: newStatus, reviewedBy: interaction.user.id }
|
||||||
|
});
|
||||||
|
await this.updateReviewMessage(interaction, updated);
|
||||||
|
const user = await this.client?.users.fetch(app.userId).catch(() => null);
|
||||||
|
if (user) {
|
||||||
|
const msg =
|
||||||
|
newStatus === 'accepted'
|
||||||
|
? 'Deine Registrierung wurde akzeptiert.'
|
||||||
|
: newStatus === 'invited'
|
||||||
|
? 'Bitte komm für ein Gespräch vorbei.'
|
||||||
|
: 'Deine Registrierung wurde abgelehnt.';
|
||||||
|
user.send(msg).catch(() => undefined);
|
||||||
|
}
|
||||||
|
await interaction.reply({ content: 'Status aktualisiert.', ephemeral: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async handleModal(interaction: ModalSubmitInteraction) {
|
||||||
|
if (!interaction.customId.startsWith('register:submit:')) return;
|
||||||
|
const formId = interaction.customId.split(':')[2];
|
||||||
|
const form = await prisma.registerForm.findFirst({
|
||||||
|
where: { id: formId },
|
||||||
|
include: { fields: { orderBy: { order: 'asc' } } }
|
||||||
|
});
|
||||||
|
if (!form) {
|
||||||
|
await interaction.reply({ content: 'Formular nicht gefunden.', ephemeral: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const answersPayload = form.fields.map((f) => ({
|
||||||
|
fieldId: f.id,
|
||||||
|
value: interaction.fields.getTextInputValue(f.id) || ''
|
||||||
|
}));
|
||||||
|
const app = await prisma.registerApplication.create({
|
||||||
|
data: {
|
||||||
|
guildId: interaction.guildId ?? '',
|
||||||
|
userId: interaction.user.id,
|
||||||
|
formId: form.id,
|
||||||
|
status: 'pending',
|
||||||
|
answers: {
|
||||||
|
create: answersPayload
|
||||||
|
}
|
||||||
|
},
|
||||||
|
include: { answers: true }
|
||||||
|
});
|
||||||
|
await interaction.reply({ content: 'Registrierung gesendet.', ephemeral: true });
|
||||||
|
await this.postReviewEmbed(form, app, interaction.user.id, interaction.guildId || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
private async postReviewEmbed(form: any, app: any, userId: string, guildId: string) {
|
||||||
|
if (!this.client) return;
|
||||||
|
const cfg = settingsStore.get(guildId);
|
||||||
|
const channelId = form.reviewChannelId || cfg?.registerConfig?.reviewChannelId;
|
||||||
|
if (!channelId) return;
|
||||||
|
const guild = this.client.guilds.cache.get(guildId) ?? (await this.client.guilds.fetch(guildId).catch(() => null));
|
||||||
|
if (!guild) return;
|
||||||
|
const channel = await guild.channels.fetch(channelId).catch(() => null);
|
||||||
|
if (!channel || !channel.isTextBased()) return;
|
||||||
|
const member = await guild.members.fetch(userId).catch(() => null);
|
||||||
|
const fields = await prisma.registerFormField.findMany({ where: { formId: form.id }, orderBy: { order: 'asc' } });
|
||||||
|
const answers = await prisma.registerApplicationAnswer.findMany({ where: { applicationId: app.id } });
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setTitle(`Registrierung: ${form.name}`)
|
||||||
|
.setDescription(form.description || '')
|
||||||
|
.setColor(0xf97316)
|
||||||
|
.addFields(
|
||||||
|
...fields.map((f) => ({
|
||||||
|
name: f.label,
|
||||||
|
value: answers.find((a) => a.fieldId === f.id)?.value || '-',
|
||||||
|
inline: false
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
.setFooter({ text: `Status: ${app.status}` })
|
||||||
|
.setTimestamp(new Date(app.createdAt));
|
||||||
|
if (member) embed.setAuthor({ name: member.user.tag, iconURL: member.user.displayAvatarURL() });
|
||||||
|
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
||||||
|
new ButtonBuilder().setCustomId(`register:review:accept:${app.id}`).setLabel('Akzeptieren').setStyle(ButtonStyle.Success),
|
||||||
|
new ButtonBuilder().setCustomId(`register:review:invite:${app.id}`).setLabel('Gespräch einladen').setStyle(ButtonStyle.Primary),
|
||||||
|
new ButtonBuilder().setCustomId(`register:review:reject:${app.id}`).setLabel('Ablehnen').setStyle(ButtonStyle.Danger)
|
||||||
|
);
|
||||||
|
const notifyRoles = form.notifyRoleIds || cfg?.registerConfig?.notifyRoleIds || [];
|
||||||
|
const content = notifyRoles.length ? notifyRoles.map((id: string) => `<@&${id}>`).join(' ') : null;
|
||||||
|
await (channel as any).send({ content: content || undefined, embeds: [embed], components: [row] });
|
||||||
|
}
|
||||||
|
|
||||||
|
private async updateReviewMessage(interaction: ButtonInteraction, app: any) {
|
||||||
|
if (!interaction.message || !interaction.message.editable) return;
|
||||||
|
const embed = (interaction.message.embeds?.[0] as any) ?? null;
|
||||||
|
if (embed) {
|
||||||
|
embed.data = { ...(embed.data || {}), footer: { text: `Status: ${app.status} | Reviewer: ${interaction.user.tag}` } };
|
||||||
|
}
|
||||||
|
const components = interaction.message.components;
|
||||||
|
await interaction.message.edit({ embeds: embed ? [embed] : interaction.message.embeds, components });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async listApplications(guildId: string, status?: string, formId?: string) {
|
||||||
|
const where: any = { guildId };
|
||||||
|
if (status) where.status = status;
|
||||||
|
if (formId) where.formId = formId;
|
||||||
|
return prisma.registerApplication.findMany({
|
||||||
|
where,
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
include: { form: true }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getApplication(id: string) {
|
||||||
|
return prisma.registerApplication.findUnique({
|
||||||
|
where: { id },
|
||||||
|
include: { form: true, answers: true }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -543,6 +543,82 @@ router.delete('/reactionroles/:id', requireAuth, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.get('/register/forms', requireAuth, async (req, res) => {
|
||||||
|
const guildId = typeof req.query.guildId === 'string' ? req.query.guildId : undefined;
|
||||||
|
if (!guildId) return res.status(400).json({ error: 'guildId required' });
|
||||||
|
const forms = await context.register.listForms(guildId);
|
||||||
|
res.json({ forms });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/register/forms', requireAuth, async (req, res) => {
|
||||||
|
const guildId = typeof req.body.guildId === 'string' ? req.body.guildId : undefined;
|
||||||
|
if (!guildId) return res.status(400).json({ error: 'guildId required' });
|
||||||
|
const data = req.body || {};
|
||||||
|
const form = await context.register.saveForm({
|
||||||
|
guildId,
|
||||||
|
name: data.name || 'Formular',
|
||||||
|
description: data.description || '',
|
||||||
|
reviewChannelId: data.reviewChannelId || undefined,
|
||||||
|
notifyRoleIds: Array.isArray(data.notifyRoleIds) ? data.notifyRoleIds : typeof data.notifyRoleIds === 'string' ? data.notifyRoleIds.split(',').map((s: string) => s.trim()).filter(Boolean) : [],
|
||||||
|
isActive: data.isActive !== false,
|
||||||
|
fields: Array.isArray(data.fields) ? data.fields : []
|
||||||
|
});
|
||||||
|
res.json({ form });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.put('/register/forms/:id', requireAuth, async (req, res) => {
|
||||||
|
const guildId = typeof req.body.guildId === 'string' ? req.body.guildId : undefined;
|
||||||
|
const id = req.params.id;
|
||||||
|
if (!guildId) return res.status(400).json({ error: 'guildId required' });
|
||||||
|
const data = req.body || {};
|
||||||
|
const form = await context.register.saveForm({
|
||||||
|
id,
|
||||||
|
guildId,
|
||||||
|
name: data.name || 'Formular',
|
||||||
|
description: data.description || '',
|
||||||
|
reviewChannelId: data.reviewChannelId || undefined,
|
||||||
|
notifyRoleIds: Array.isArray(data.notifyRoleIds) ? data.notifyRoleIds : typeof data.notifyRoleIds === 'string' ? data.notifyRoleIds.split(',').map((s: string) => s.trim()).filter(Boolean) : [],
|
||||||
|
isActive: data.isActive !== false,
|
||||||
|
fields: Array.isArray(data.fields) ? data.fields : []
|
||||||
|
});
|
||||||
|
res.json({ form });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.delete('/register/forms/:id', requireAuth, async (req, res) => {
|
||||||
|
const guildId = typeof req.body.guildId === 'string' ? req.body.guildId : undefined;
|
||||||
|
const id = req.params.id;
|
||||||
|
if (!guildId) return res.status(400).json({ error: 'guildId required' });
|
||||||
|
const ok = await context.register.deleteForm(guildId, id);
|
||||||
|
if (!ok) return res.status(404).json({ error: 'not found' });
|
||||||
|
res.json({ ok: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/register/forms/:id/panel', requireAuth, async (req, res) => {
|
||||||
|
const guildId = typeof req.body.guildId === 'string' ? req.body.guildId : undefined;
|
||||||
|
const id = req.params.id;
|
||||||
|
if (!guildId) return res.status(400).json({ error: 'guildId required' });
|
||||||
|
const channelId = typeof req.body.channelId === 'string' ? req.body.channelId : undefined;
|
||||||
|
const message = typeof req.body.message === 'string' ? req.body.message : undefined;
|
||||||
|
const msgId = await context.register.sendPanel(guildId, id, channelId, message);
|
||||||
|
if (!msgId) return res.status(400).json({ error: 'panel failed' });
|
||||||
|
res.json({ ok: true, messageId: msgId });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/register/apps', requireAuth, async (req, res) => {
|
||||||
|
const guildId = typeof req.query.guildId === 'string' ? req.query.guildId : undefined;
|
||||||
|
const status = typeof req.query.status === 'string' ? req.query.status : undefined;
|
||||||
|
const formId = typeof req.query.formId === 'string' ? req.query.formId : undefined;
|
||||||
|
if (!guildId) return res.status(400).json({ error: 'guildId required' });
|
||||||
|
const apps = await context.register.listApplications(guildId, status, formId);
|
||||||
|
res.json({ applications: apps });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/register/apps/:id', requireAuth, async (req, res) => {
|
||||||
|
const app = await context.register.getApplication(req.params.id);
|
||||||
|
if (!app) return res.status(404).json({ error: 'not found' });
|
||||||
|
res.json({ application: app });
|
||||||
|
});
|
||||||
|
|
||||||
router.get('/automations', requireAuth, async (req, res) => {
|
router.get('/automations', requireAuth, async (req, res) => {
|
||||||
const guildId = typeof req.query.guildId === 'string' ? req.query.guildId : undefined;
|
const guildId = typeof req.query.guildId === 'string' ? req.query.guildId : undefined;
|
||||||
if (!guildId) return res.status(400).json({ error: 'guildId required' });
|
if (!guildId) return res.status(400).json({ error: 'guildId required' });
|
||||||
@@ -701,7 +777,9 @@ router.post('/settings', requireAuth, async (req, res) => {
|
|||||||
birthdayEnabled,
|
birthdayEnabled,
|
||||||
birthdayConfig,
|
birthdayConfig,
|
||||||
reactionRolesEnabled,
|
reactionRolesEnabled,
|
||||||
reactionRolesConfig
|
reactionRolesConfig,
|
||||||
|
registerEnabled,
|
||||||
|
registerConfig
|
||||||
} = req.body;
|
} = req.body;
|
||||||
if (!guildId) return res.status(400).json({ error: 'guildId required' });
|
if (!guildId) return res.status(400).json({ error: 'guildId required' });
|
||||||
const normalizeArray = (val: any) =>
|
const normalizeArray = (val: any) =>
|
||||||
@@ -798,6 +876,22 @@ router.post('/settings', requireAuth, async (req, res) => {
|
|||||||
channelId: reactionRolesConfig?.channelId ?? existingReactionRoles.channelId
|
channelId: reactionRolesConfig?.channelId ?? existingReactionRoles.channelId
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const parsedRegister = {
|
||||||
|
enabled:
|
||||||
|
registerConfig?.enabled ??
|
||||||
|
(typeof registerEnabled === 'string' ? registerEnabled === 'true' : registerEnabled ?? (current as any).registerEnabled),
|
||||||
|
reviewChannelId: registerConfig?.reviewChannelId ?? (current as any).registerConfig?.reviewChannelId,
|
||||||
|
notifyRoleIds:
|
||||||
|
Array.isArray(registerConfig?.notifyRoleIds) && registerConfig?.notifyRoleIds.length
|
||||||
|
? registerConfig.notifyRoleIds
|
||||||
|
: typeof registerConfig?.notifyRoleIds === 'string'
|
||||||
|
? registerConfig.notifyRoleIds
|
||||||
|
.split(',')
|
||||||
|
.map((s: string) => s.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
: (current as any).registerConfig?.notifyRoleIds || []
|
||||||
|
};
|
||||||
|
|
||||||
const updated = await settingsStore.set(guildId, {
|
const updated = await settingsStore.set(guildId, {
|
||||||
welcomeChannelId: welcomeChannelId ?? undefined,
|
welcomeChannelId: welcomeChannelId ?? undefined,
|
||||||
logChannelId: logChannelId ?? undefined,
|
logChannelId: logChannelId ?? undefined,
|
||||||
@@ -817,7 +911,9 @@ router.post('/settings', requireAuth, async (req, res) => {
|
|||||||
birthdayEnabled: parsedBirthday.enabled,
|
birthdayEnabled: parsedBirthday.enabled,
|
||||||
birthdayConfig: parsedBirthday,
|
birthdayConfig: parsedBirthday,
|
||||||
reactionRolesEnabled: parsedReactionRoles.enabled,
|
reactionRolesEnabled: parsedReactionRoles.enabled,
|
||||||
reactionRolesConfig: parsedReactionRoles
|
reactionRolesConfig: parsedReactionRoles,
|
||||||
|
registerEnabled: parsedRegister.enabled,
|
||||||
|
registerConfig: parsedRegister
|
||||||
});
|
});
|
||||||
// Live update logging target
|
// Live update logging target
|
||||||
context.logging = new LoggingService(updated.logChannelId);
|
context.logging = new LoggingService(updated.logChannelId);
|
||||||
|
|||||||
Reference in New Issue
Block a user