Playtester werden jetzt beworben, nicht angeklickt
Der Knopf unter dem Playtester-Aufruf hat jeden aufgenommen, der ihn gedrueckt hat — ohne Frage, ohne Pruefung. Fuer ein Programm, dessen Teilnehmer Zugang und Alpha-Keys bekommen, ist das die falsche Tuer. Statt einen zweiten Pruef-Ablauf danebenzustellen, laeuft die Aufnahme jetzt ueber die Bewerbungs-Formulare, die es laengst gibt: Modal ausfuellen, Review-Embed im Staff-Kanal, ✅ oder ❌, Rolle und Antwort-DM bei Zusage. Das ist dieselbe Maschinerie, nur ein anderer Aufhaenger — und damit auch nur eine Stelle, an der spaeter etwas kaputtgehen kann. Ein Formular laesst sich als Playtester-Formular markieren ("Annahme traegt als Playtester ein"). Wer dort angenommen wird, landet zusaetzlich in der Playtester-Liste, damit die Schluessel-Verteilung ihn kennt. Nur eines kann es sein — sonst wuesste /playtester-setup nicht, welches es posten soll. /playtester-setup postet jetzt den Knopf dieses Formulars. Fehlt das Formular, fehlen Fragen oder fehlt der Review-Kanal, sagt der Befehl das, statt einen Aufruf zu posten, der ins Leere fuehrt. Der alte Knopf in bereits geposteten Nachrichten nimmt niemanden mehr auf: er verweist auf den Aufruf. Austreten bleibt Selbstbedienung — dafuer braucht es keine Freigabe. Im Panel steht im Playtester-Bereich, ueber welches Formular die Aufnahme laeuft, ob es schon gepostet ist, und ein Weg dorthin. Ohne markiertes Formular steht dort, dass gerade niemand hereinkommt — das ist sonst der Grund, warum sich tagelang niemand bewirbt. Geprueft: Routen, SQL gegen das Schema, und im Browser beide Zustaende des Hinweises sowie der neue Schalter — beim Wechsel zwischen zwei Formularen folgt er dem richtigen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -76,10 +76,10 @@ auf nichts mehr: keine Posts, keine Hintergrund-Prüfungen.
|
|||||||
| 🎉 **Giveaways** | `/giveaway` (Admin): Teilnahme-Button, automatische Ziehung; am Gewinner-Post 🔁 Neu auslosen + 👥 Teilnehmerliste |
|
| 🎉 **Giveaways** | `/giveaway` (Admin): Teilnahme-Button, automatische Ziehung; am Gewinner-Post 🔁 Neu auslosen + 👥 Teilnehmerliste |
|
||||||
| 🎂 **Geburtstage** | `/geburtstag` zum Eintragen; morgens Gratulation im Kanal + Tages-Rolle |
|
| 🎂 **Geburtstage** | `/geburtstag` zum Eintragen; morgens Gratulation im Kanal + Tages-Rolle |
|
||||||
| 📅 **Events** | Neue Discord-Events werden angekündigt + öffentliche Events-Seite |
|
| 📅 **Events** | Neue Discord-Events werden angekündigt + öffentliche Events-Seite |
|
||||||
| 🧪 **Playtester-Programm** | `/playtester-setup` postet den Bewerbungs-Button; eigener Panel-Bereich mit Liste, Rolle und dem Schlüssel-Vorrat — Playtester lassen sich dort auch wieder entfernen (die Rolle geht mit) |
|
| 🧪 **Playtester-Programm** | Aufnahme läuft über ein **Bewerbungs-Formular**: ausfüllen, das Team nimmt an oder lehnt ab. Wer angenommen wird, bekommt Rolle und Listeneintrag. Eigener Panel-Bereich mit Liste, Rolle und Schlüssel-Vorrat — Playtester lassen sich dort auch wieder entfernen (die Rolle geht mit) |
|
||||||
| 🎟️ **Alpha-Keys** | Schlüssel-Vorrat im Panel: nachlegen, gezielt an eine Person schicken, an alle Wartenden verteilen, zurückziehen oder löschen. Zustellung per Direktnachricht — kommt sie nicht an, bleibt der Schlüssel frei |
|
| 🎟️ **Alpha-Keys** | Schlüssel-Vorrat im Panel: nachlegen, gezielt an eine Person schicken, an alle Wartenden verteilen, zurückziehen oder löschen. Zustellung per Direktnachricht — kommt sie nicht an, bleibt der Schlüssel frei |
|
||||||
| 🎭 **Rollen-Menüs** | Menüs im Webinterface bauen (Emoji, Label, Rolle, optional exklusiv), Bot postet Button-Embeds; Posts jederzeit editierbar |
|
| 🎭 **Rollen-Menüs** | Menüs im Webinterface bauen (Emoji, Label, Rolle, optional exklusiv), Bot postet Button-Embeds; Posts jederzeit editierbar |
|
||||||
| 📋 **Bewerbungen** | Formulare (bis 5 Fragen) als Discord-Modal, Review mit ✅/❌ im Staff-Kanal, Rolle + DM bei Annahme — z. B. FiveM-Whitelist |
|
| 📋 **Bewerbungen** | Formulare (bis 5 Fragen) als Discord-Modal, Review mit ✅/❌ im Staff-Kanal, Rolle + DM bei Annahme — z. B. FiveM-Whitelist. Ein Formular lässt sich als **Playtester-Formular** markieren; Angenommene landen dann auch im Playtester-Programm |
|
||||||
| 🪪 **Verknüpfte Rollen** | Discords *Linked Roles*: Level, Playtester-Status und Dabei-seit werden als geprüfte Kennzahlen an Discord gemeldet — Rollen vergibt dann Discord selbst |
|
| 🪪 **Verknüpfte Rollen** | Discords *Linked Roles*: Level, Playtester-Status und Dabei-seit werden als geprüfte Kennzahlen an Discord gemeldet — Rollen vergibt dann Discord selbst |
|
||||||
|
|
||||||
### Moderation & Support
|
### Moderation & Support
|
||||||
|
|||||||
@@ -322,7 +322,7 @@ export default function Settings({ me }) {
|
|||||||
// Bewerbungs-Formulare
|
// Bewerbungs-Formulare
|
||||||
const emptyForm = {
|
const emptyForm = {
|
||||||
id: null, title: '', description: '', review_channel_id: '',
|
id: null, title: '', description: '', review_channel_id: '',
|
||||||
approve_role_id: '', post_channel_id: '', questions: [''],
|
approve_role_id: '', post_channel_id: '', questions: [''], playtester: false,
|
||||||
};
|
};
|
||||||
const [appForms, setAppForms] = useState([]);
|
const [appForms, setAppForms] = useState([]);
|
||||||
const [formDraft, setFormDraft] = useState(emptyForm);
|
const [formDraft, setFormDraft] = useState(emptyForm);
|
||||||
@@ -2195,10 +2195,29 @@ export default function Settings({ me }) {
|
|||||||
<div className="settings-section">
|
<div className="settings-section">
|
||||||
<h2 className="settings-title">// Playtester</h2>
|
<h2 className="settings-title">// Playtester</h2>
|
||||||
<p className="section-intro">
|
<p className="section-intro">
|
||||||
Wer sich über den Knopf aus <strong>/playtester-setup</strong> meldet, landet
|
Beworben wird sich über ein Formular, angenommen vom Team — wer angenommen
|
||||||
hier — mit Rolle. Schlüssel gehen per Direktnachricht raus; kommt sie nicht
|
wird, landet hier mit Rolle. Schlüssel gehen per Direktnachricht raus; kommt
|
||||||
an, bleibt der Schlüssel im Vorrat.
|
sie nicht an, bleibt der Schlüssel im Vorrat.
|
||||||
</p>
|
</p>
|
||||||
|
{(() => {
|
||||||
|
// Ohne markiertes Formular gibt es keinen Weg herein — das
|
||||||
|
// ist der haeufigste Grund, warum sich niemand bewirbt
|
||||||
|
const ptForm = appForms.find((f) => f.playtester);
|
||||||
|
return ptForm ? (
|
||||||
|
<p className="section-intro">
|
||||||
|
Aufnahme läuft über <strong>{ptForm.title}</strong>
|
||||||
|
{ptForm.message_id ? '' : ' — das Formular ist noch nicht gepostet'}.
|
||||||
|
{' '}<button className="btn btn-mini" onClick={() => setTab('bewerbungen')}>Formular öffnen</button>
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<p className="notice">
|
||||||
|
Noch kein Playtester-Formular. Ohne eines kommt niemand herein —
|
||||||
|
unter <strong>Bewerbungen</strong> ein Formular anlegen und dort
|
||||||
|
„Annahme trägt als Playtester ein" ankreuzen.
|
||||||
|
{' '}<button className="btn btn-mini" onClick={() => setTab('bewerbungen')}>Zu den Bewerbungen</button>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
<div className="field" data-setting="playtester_role_id">
|
<div className="field" data-setting="playtester_role_id">
|
||||||
<label>Playtester-Rolle <span className="field-hint">Bewerbungs-Post via /playtester-setup; leer = aus</span></label>
|
<label>Playtester-Rolle <span className="field-hint">Bewerbungs-Post via /playtester-setup; leer = aus</span></label>
|
||||||
<PickSelect
|
<PickSelect
|
||||||
@@ -2367,12 +2386,16 @@ export default function Settings({ me }) {
|
|||||||
{appForms.map((f) => (
|
{appForms.map((f) => (
|
||||||
<div className="key-row" key={f.id}>
|
<div className="key-row" key={f.id}>
|
||||||
<span className="key-name">{f.title}</span>
|
<span className="key-name">{f.title}</span>
|
||||||
<span className="key-scopes">{f.questions.length} Fragen{f.message_id ? ' · gepostet' : ' · Entwurf'}</span>
|
<span className="key-scopes">
|
||||||
|
{f.questions.length} Fragen{f.message_id ? ' · gepostet' : ' · Entwurf'}
|
||||||
|
{f.playtester ? ' · Playtester' : ''}
|
||||||
|
</span>
|
||||||
<button className="btn btn-mini" onClick={() => setFormDraft({
|
<button className="btn btn-mini" onClick={() => setFormDraft({
|
||||||
id: f.id, title: f.title, description: f.description,
|
id: f.id, title: f.title, description: f.description,
|
||||||
review_channel_id: f.review_channel_id ?? '', approve_role_id: f.approve_role_id ?? '',
|
review_channel_id: f.review_channel_id ?? '', approve_role_id: f.approve_role_id ?? '',
|
||||||
post_channel_id: f.post_channel_id ?? '',
|
post_channel_id: f.post_channel_id ?? '',
|
||||||
questions: f.questions.length ? f.questions : [''],
|
questions: f.questions.length ? f.questions : [''],
|
||||||
|
playtester: Boolean(f.playtester),
|
||||||
})}>Bearbeiten</button>
|
})}>Bearbeiten</button>
|
||||||
<button className="card-delete" title="Formular + Post löschen" onClick={() => removeForm(f.id)}>✕</button>
|
<button className="card-delete" title="Formular + Post löschen" onClick={() => removeForm(f.id)}>✕</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -2411,6 +2434,21 @@ export default function Settings({ me }) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="field">
|
||||||
|
<label className="toggle">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={Boolean(formDraft.playtester)}
|
||||||
|
onChange={(e) => setFormDraft({ ...formDraft, playtester: e.target.checked })}
|
||||||
|
/>
|
||||||
|
<span>Annahme trägt als Playtester ein</span>
|
||||||
|
<span className="field-hint">
|
||||||
|
Angenommene landen zusätzlich in der Playtester-Liste und bekommen von dort
|
||||||
|
ihren Schlüssel. <strong>/playtester-setup</strong> postet dann diesen Knopf —
|
||||||
|
so kann sich niemand mehr selbst aufnehmen. Es kann nur ein Formular sein.
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<label>Fragen <span className="field-hint">max 5 (Discord-Modal-Limit)</span></label>
|
<label>Fragen <span className="field-hint">max 5 (Discord-Modal-Limit)</span></label>
|
||||||
{formDraft.questions.map((q, i) => (
|
{formDraft.questions.map((q, i) => (
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
ModalBuilder, TextInputBuilder, TextInputStyle,
|
ModalBuilder, TextInputBuilder, TextInputStyle,
|
||||||
} from 'discord.js';
|
} from 'discord.js';
|
||||||
import {
|
import {
|
||||||
getAppForm, setAppFormMessage,
|
getAppForm, setAppFormMessage, addPlaytester,
|
||||||
createApplication, getApplication, openApplicationOf, setApplicationStatus,
|
createApplication, getApplication, openApplicationOf, setApplicationStatus,
|
||||||
} from '../db.js';
|
} from '../db.js';
|
||||||
import { brandColor, brandColor2, brandFooter } from '../runtime-settings.js';
|
import { brandColor, brandColor2, brandFooter } from '../runtime-settings.js';
|
||||||
@@ -172,6 +172,11 @@ export async function handleReviewButton(interaction) {
|
|||||||
console.error(`[bewerbung] Rolle fehlgeschlagen: ${e.message}`)
|
console.error(`[bewerbung] Rolle fehlgeschlagen: ${e.message}`)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// Playtester-Formular: Annahme trägt zusätzlich in die Liste ein, damit die
|
||||||
|
// Schlüssel-Verteilung denjenigen kennt
|
||||||
|
if (approved && form?.playtester) {
|
||||||
|
addPlaytester(application.user_id, application.username ?? application.user_id);
|
||||||
|
}
|
||||||
// DM an den Bewerber
|
// DM an den Bewerber
|
||||||
const user = await interaction.client.users.fetch(application.user_id).catch(() => null);
|
const user = await interaction.client.users.fetch(application.user_id).catch(() => null);
|
||||||
await user?.send(renderTemplate(approved ? 'application.approved' : 'application.denied', {
|
await user?.send(renderTemplate(approved ? 'application.approved' : 'application.denied', {
|
||||||
|
|||||||
+15
-9
@@ -22,7 +22,7 @@ import {
|
|||||||
import { handleRoleMenuButton } from './role-menus.js';
|
import { handleRoleMenuButton } from './role-menus.js';
|
||||||
import { handleGiveawayAdminButton } from './giveaways.js';
|
import { handleGiveawayAdminButton } from './giveaways.js';
|
||||||
import {
|
import {
|
||||||
addPlaytester, removePlaytester, isWish, bumpWish,
|
removePlaytester, playtesterForm, isWish, bumpWish,
|
||||||
getGiveaway, toggleGiveawayEntry, giveawayEntries,
|
getGiveaway, toggleGiveawayEntry, giveawayEntries,
|
||||||
} from '../db.js';
|
} from '../db.js';
|
||||||
import * as ping from './commands/ping.js';
|
import * as ping from './commands/ping.js';
|
||||||
@@ -309,22 +309,28 @@ export async function startBot() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🧪-Button: Playtester-Rolle + Liste togglen
|
// 🧪-Button aus älteren Posts. Aufgenommen wird hier niemand mehr —
|
||||||
|
// das läuft über das Playtester-Formular mit Prüfung durch das Team.
|
||||||
|
// Austreten bleibt Selbstbedienung: dafür braucht es keine Freigabe.
|
||||||
if (interaction.isButton() && interaction.customId === 'playtester_toggle') {
|
if (interaction.isButton() && interaction.customId === 'playtester_toggle') {
|
||||||
const roleId = playtesterRoleId();
|
const roleId = playtesterRoleId();
|
||||||
try {
|
try {
|
||||||
if (!roleId || !interaction.inGuild()) throw new Error('Keine Playtester-Rolle konfiguriert');
|
if (!roleId || !interaction.inGuild()) throw new Error('Keine Playtester-Rolle konfiguriert');
|
||||||
const member = interaction.member;
|
const member = interaction.member;
|
||||||
const hasRole = member.roles.cache.has(roleId);
|
if (!member.roles.cache.has(roleId)) {
|
||||||
if (hasRole) {
|
const form = playtesterForm();
|
||||||
|
await interaction.reply({
|
||||||
|
content: form
|
||||||
|
? '❕ Dieser Knopf ist veraltet. Bewirb dich über den Playtester-Aufruf — dort füllst du ein kurzes Formular aus, das Team meldet sich.'
|
||||||
|
: '❕ Bewerbungen laufen gerade nicht über diesen Knopf. Melde dich beim Team.',
|
||||||
|
flags: MessageFlags.Ephemeral,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
await member.roles.remove(roleId);
|
await member.roles.remove(roleId);
|
||||||
removePlaytester(interaction.user.id);
|
removePlaytester(interaction.user.id);
|
||||||
} else {
|
|
||||||
await member.roles.add(roleId);
|
|
||||||
addPlaytester(interaction.user.id, interaction.user.username);
|
|
||||||
}
|
|
||||||
await interaction.reply({
|
await interaction.reply({
|
||||||
content: renderTemplate(hasRole ? 'playtester.left' : 'playtester.joined', {
|
content: renderTemplate('playtester.left', {
|
||||||
user: member.displayName ?? interaction.user.username,
|
user: member.displayName ?? interaction.user.username,
|
||||||
mention: `<@${interaction.user.id}>`,
|
mention: `<@${interaction.user.id}>`,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,20 +1,52 @@
|
|||||||
// /playtester-setup — postet das Bewerbungs-Embed mit Button in den aktuellen Kanal (Admin)
|
// /playtester-setup — postet den Playtester-Aufruf in den aktuellen Kanal (Admin)
|
||||||
|
//
|
||||||
|
// Ist ein Bewerbungs-Formular als Playtester-Formular markiert, führt der Knopf
|
||||||
|
// dorthin: ausfüllen, das Team entscheidet. Ohne Formular gäbe es nur den alten
|
||||||
|
// Selbstbedienungs-Knopf — und der macht jeden zum Playtester, der draufdrückt.
|
||||||
|
// Deshalb sagt der Befehl dann, was zu tun ist, statt ihn zu posten.
|
||||||
import {
|
import {
|
||||||
SlashCommandBuilder, PermissionFlagsBits, MessageFlags,
|
SlashCommandBuilder, PermissionFlagsBits, MessageFlags,
|
||||||
ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder,
|
ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder,
|
||||||
} from 'discord.js';
|
} from 'discord.js';
|
||||||
import { playtesterRoleId, brandColor, brandFooter } from '../../runtime-settings.js';
|
import { playtesterRoleId, brandColor, brandFooter } from '../../runtime-settings.js';
|
||||||
import { renderTemplate } from '../../templates.js';
|
import { renderTemplate } from '../../templates.js';
|
||||||
|
import { playtesterForm } from '../../db.js';
|
||||||
|
|
||||||
export const data = new SlashCommandBuilder()
|
export const data = new SlashCommandBuilder()
|
||||||
.setName('playtester-setup')
|
.setName('playtester-setup')
|
||||||
.setDescription('Postet das Playtester-Bewerbungs-Embed in diesen Kanal')
|
.setDescription('Postet den Playtester-Aufruf in diesen Kanal')
|
||||||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator);
|
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator);
|
||||||
|
|
||||||
export async function execute(interaction) {
|
export async function execute(interaction) {
|
||||||
if (!playtesterRoleId()) {
|
if (!playtesterRoleId()) {
|
||||||
await interaction.reply({
|
await interaction.reply({
|
||||||
content: '❌ Erst auf der Config-Seite eine Playtester-Rolle wählen.',
|
content: '❌ Erst in der Config eine Playtester-Rolle wählen.',
|
||||||
|
flags: MessageFlags.Ephemeral,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = playtesterForm();
|
||||||
|
if (!form) {
|
||||||
|
await interaction.reply({
|
||||||
|
content: '❌ Kein Playtester-Formular gesetzt.\n'
|
||||||
|
+ 'Config → **Bewerbungen**: ein Formular anlegen und dort '
|
||||||
|
+ '„Annahme trägt als Playtester ein" ankreuzen. Dann kann sich '
|
||||||
|
+ 'niemand mehr per Knopfdruck selbst aufnehmen.',
|
||||||
|
flags: MessageFlags.Ephemeral,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (JSON.parse(form.questions || '[]').length === 0) {
|
||||||
|
await interaction.reply({
|
||||||
|
content: `❌ Das Formular „${form.title}" hat noch keine Fragen.`,
|
||||||
|
flags: MessageFlags.Ephemeral,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!form.review_channel_id) {
|
||||||
|
await interaction.reply({
|
||||||
|
content: `❌ Für „${form.title}" fehlt der Review-Kanal — sonst sieht niemand die Bewerbungen.`,
|
||||||
flags: MessageFlags.Ephemeral,
|
flags: MessageFlags.Ephemeral,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
@@ -26,14 +58,19 @@ export async function execute(interaction) {
|
|||||||
.setDescription(renderTemplate('playtester.panel_text'))
|
.setDescription(renderTemplate('playtester.panel_text'))
|
||||||
.setFooter({ text: brandFooter('PLAYTEST') });
|
.setFooter({ text: brandFooter('PLAYTEST') });
|
||||||
|
|
||||||
|
// Derselbe Knopf wie beim Bewerbungs-Formular — das Formular, die Prüfung
|
||||||
|
// und die Antwort-DMs sind dieselbe Maschinerie, nur ein anderer Aufhänger.
|
||||||
const row = new ActionRowBuilder().addComponents(
|
const row = new ActionRowBuilder().addComponents(
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId('playtester_toggle')
|
.setCustomId(`apply:${form.id}`)
|
||||||
.setStyle(ButtonStyle.Success)
|
.setStyle(ButtonStyle.Success)
|
||||||
.setLabel('Als Playtester bewerben')
|
.setLabel('Als Playtester bewerben')
|
||||||
.setEmoji('🧪')
|
.setEmoji('🧪')
|
||||||
);
|
);
|
||||||
|
|
||||||
await interaction.channel.send({ embeds: [embed], components: [row] });
|
await interaction.channel.send({ embeds: [embed], components: [row] });
|
||||||
await interaction.reply({ content: '✅ Bewerbungs-Post erstellt.', flags: MessageFlags.Ephemeral });
|
await interaction.reply({
|
||||||
|
content: `✅ Aufruf gepostet — Bewerbungen laufen über „${form.title}".`,
|
||||||
|
flags: MessageFlags.Ephemeral,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -340,24 +340,38 @@ db.exec(`
|
|||||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
);
|
);
|
||||||
`);
|
`);
|
||||||
|
// Migration: Annahme kann zusaetzlich ins Playtester-Programm eintragen
|
||||||
|
{
|
||||||
|
const cols = db.prepare('PRAGMA table_info(app_forms)').all().map((c) => c.name);
|
||||||
|
if (!cols.includes('playtester')) {
|
||||||
|
db.exec('ALTER TABLE app_forms ADD COLUMN playtester INTEGER NOT NULL DEFAULT 0');
|
||||||
|
}
|
||||||
|
}
|
||||||
const insertAppForm = db.prepare(`
|
const insertAppForm = db.prepare(`
|
||||||
INSERT INTO app_forms (title, description, review_channel_id, approve_role_id, post_channel_id, questions)
|
INSERT INTO app_forms (title, description, review_channel_id, approve_role_id, post_channel_id, questions, playtester)
|
||||||
VALUES (@title, @description, @review_channel_id, @approve_role_id, @post_channel_id, @questions)
|
VALUES (@title, @description, @review_channel_id, @approve_role_id, @post_channel_id, @questions, @playtester)
|
||||||
`);
|
`);
|
||||||
const updateAppFormStmt = db.prepare(`
|
const updateAppFormStmt = db.prepare(`
|
||||||
UPDATE app_forms SET title = @title, description = @description, review_channel_id = @review_channel_id,
|
UPDATE app_forms SET title = @title, description = @description, review_channel_id = @review_channel_id,
|
||||||
approve_role_id = @approve_role_id, post_channel_id = @post_channel_id, questions = @questions
|
approve_role_id = @approve_role_id, post_channel_id = @post_channel_id, questions = @questions,
|
||||||
|
playtester = @playtester
|
||||||
WHERE id = @id
|
WHERE id = @id
|
||||||
`);
|
`);
|
||||||
const setAppFormMessageStmt = db.prepare('UPDATE app_forms SET message_id = ? WHERE id = ?');
|
const setAppFormMessageStmt = db.prepare('UPDATE app_forms SET message_id = ? WHERE id = ?');
|
||||||
const getAppFormStmt = db.prepare('SELECT * FROM app_forms WHERE id = ?');
|
const getAppFormStmt = db.prepare('SELECT * FROM app_forms WHERE id = ?');
|
||||||
const listAppFormsStmt = db.prepare('SELECT * FROM app_forms ORDER BY id');
|
const listAppFormsStmt = db.prepare('SELECT * FROM app_forms ORDER BY id');
|
||||||
const deleteAppFormStmt = db.prepare('DELETE FROM app_forms WHERE id = ?');
|
const deleteAppFormStmt = db.prepare('DELETE FROM app_forms WHERE id = ?');
|
||||||
|
// Nur eines kann das Playtester-Formular sein — sonst weiss /playtester-setup
|
||||||
|
// nicht, welches es posten soll
|
||||||
|
const clearPlaytesterFormStmt = db.prepare('UPDATE app_forms SET playtester = 0 WHERE id != ?');
|
||||||
|
const playtesterFormStmt = db.prepare('SELECT * FROM app_forms WHERE playtester = 1 LIMIT 1');
|
||||||
export const createAppForm = (f) => insertAppForm.run(f).lastInsertRowid;
|
export const createAppForm = (f) => insertAppForm.run(f).lastInsertRowid;
|
||||||
export const updateAppForm = (f) => updateAppFormStmt.run(f).changes > 0;
|
export const updateAppForm = (f) => updateAppFormStmt.run(f).changes > 0;
|
||||||
export const setAppFormMessage = (id, messageId) => setAppFormMessageStmt.run(messageId, id);
|
export const setAppFormMessage = (id, messageId) => setAppFormMessageStmt.run(messageId, id);
|
||||||
export const getAppForm = (id) => getAppFormStmt.get(id) ?? null;
|
export const getAppForm = (id) => getAppFormStmt.get(id) ?? null;
|
||||||
export const listAppForms = () => listAppFormsStmt.all();
|
export const listAppForms = () => listAppFormsStmt.all();
|
||||||
|
export const playtesterForm = () => playtesterFormStmt.get() ?? null;
|
||||||
|
export const clearOtherPlaytesterForms = (id) => clearPlaytesterFormStmt.run(id);
|
||||||
export const deleteAppForm = (id) => deleteAppFormStmt.run(id).changes > 0;
|
export const deleteAppForm = (id) => deleteAppFormStmt.run(id).changes > 0;
|
||||||
|
|
||||||
const insertApplication = db.prepare(`
|
const insertApplication = db.prepare(`
|
||||||
|
|||||||
+2
-2
@@ -213,8 +213,8 @@ export const TEMPLATES = [
|
|||||||
vars: [],
|
vars: [],
|
||||||
default:
|
default:
|
||||||
'Du willst **EcoGame** vor allen anderen spielen und mit deinem Feedback die Entwicklung mitgestalten?\n\n' +
|
'Du willst **EcoGame** vor allen anderen spielen und mit deinem Feedback die Entwicklung mitgestalten?\n\n' +
|
||||||
'Ein Klick auf den Button setzt dich auf die Playtester-Liste — sobald die Alpha startet, ' +
|
'Klick auf den Button und füll das kurze Formular aus. Wir schauen es uns an und ' +
|
||||||
'bekommst du deinen Zugang direkt per DM. Abmelden geht jederzeit mit einem zweiten Klick.',
|
'melden uns per DM — bei einer Zusage bekommst du Rolle und Zugang.',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
+11
-3
@@ -9,7 +9,7 @@ import {
|
|||||||
topLevels, saveTag, listTags, deleteTag,
|
topLevels, saveTag, listTags, deleteTag,
|
||||||
createScheduledPost, listScheduledPosts, deleteScheduledPost,
|
createScheduledPost, listScheduledPosts, deleteScheduledPost,
|
||||||
addAlphaKeys, freeAlphaKeyCount, assignedAlphaKeys, alphaKeyOf, reserveAlphaKey, unreserveAlphaKey,
|
addAlphaKeys, freeAlphaKeyCount, assignedAlphaKeys, alphaKeyOf, reserveAlphaKey, unreserveAlphaKey,
|
||||||
createAppForm, updateAppForm, getAppForm, listAppForms, deleteAppForm,
|
createAppForm, updateAppForm, getAppForm, listAppForms, deleteAppForm, clearOtherPlaytesterForms,
|
||||||
saveTrigger, listTriggers, deleteTrigger, activityRange,
|
saveTrigger, listTriggers, deleteTrigger, activityRange,
|
||||||
createGameserver, updateGameserver, listGameservers, getGameserver, deleteGameserver,
|
createGameserver, updateGameserver, listGameservers, getGameserver, deleteGameserver,
|
||||||
saveWebAdmin, webAdminScopes, listWebAdmins, deleteWebAdmin,
|
saveWebAdmin, webAdminScopes, listWebAdmins, deleteWebAdmin,
|
||||||
@@ -1446,9 +1446,15 @@ ${rssItems}
|
|||||||
approve_role_id: roleId || null,
|
approve_role_id: roleId || null,
|
||||||
post_channel_id: String(body.post_channel_id ?? '') || null,
|
post_channel_id: String(body.post_channel_id ?? '') || null,
|
||||||
questions: JSON.stringify(questions),
|
questions: JSON.stringify(questions),
|
||||||
|
playtester: body.playtester ? 1 : 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Nur ein Formular kann das Playtester-Formular sein */
|
||||||
|
function playtesterExklusiv(form, id) {
|
||||||
|
if (form.playtester) clearOtherPlaytesterForms(id);
|
||||||
|
}
|
||||||
|
|
||||||
app.get('/api/appforms', async (request, reply) => {
|
app.get('/api/appforms', async (request, reply) => {
|
||||||
if (requireScope(request, reply, 'bewerbungen')) return;
|
if (requireScope(request, reply, 'bewerbungen')) return;
|
||||||
return { forms: listAppForms().map(formToJson) };
|
return { forms: listAppForms().map(formToJson) };
|
||||||
@@ -1459,7 +1465,8 @@ ${rssItems}
|
|||||||
const form = parseFormBody(request, reply);
|
const form = parseFormBody(request, reply);
|
||||||
if (!form) return;
|
if (!form) return;
|
||||||
const id = createAppForm(form);
|
const id = createAppForm(form);
|
||||||
return { ok: true, form: formToJson(getAppForm(id)) };
|
playtesterExklusiv(form, id);
|
||||||
|
return { ok: true, form: formToJson(getAppForm(id)), forms: listAppForms().map(formToJson) };
|
||||||
});
|
});
|
||||||
|
|
||||||
app.put('/api/appforms/:id', async (request, reply) => {
|
app.put('/api/appforms/:id', async (request, reply) => {
|
||||||
@@ -1469,12 +1476,13 @@ ${rssItems}
|
|||||||
const form = parseFormBody(request, reply);
|
const form = parseFormBody(request, reply);
|
||||||
if (!form) return;
|
if (!form) return;
|
||||||
updateAppForm({ ...form, id });
|
updateAppForm({ ...form, id });
|
||||||
|
playtesterExklusiv(form, id);
|
||||||
if (getAppForm(id).message_id) {
|
if (getAppForm(id).message_id) {
|
||||||
await publishAppForm(client, id).catch((e) =>
|
await publishAppForm(client, id).catch((e) =>
|
||||||
request.log.warn(`Formular ${id}: Discord-Update fehlgeschlagen: ${e.message}`)
|
request.log.warn(`Formular ${id}: Discord-Update fehlgeschlagen: ${e.message}`)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return { ok: true, form: formToJson(getAppForm(id)) };
|
return { ok: true, form: formToJson(getAppForm(id)), forms: listAppForms().map(formToJson) };
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/appforms/:id/publish', async (request, reply) => {
|
app.post('/api/appforms/:id/publish', async (request, reply) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user