Tickets: Anliegen zur Auswahl, und das Team kann uebernehmen
Bisher sah jedes Ticket gleich aus: ein Knopf, ein Thread, und im Thread stand als Erstes die Frage, worum es ueberhaupt geht. Und wer sich darum kuemmert, stand nirgends — man sah hinterher nur, wer zufaellig auf "Schliessen" geklickt hat. Anliegen sind Kategorien, die man in der Config anlegt: Name, Emoji, eine Zeile Beschreibung, ein Einstiegstext und optional eine Rolle, die beim Oeffnen erfaehrt, dass etwas anliegt. Der Ticket-Post wird dadurch vom Knopf zum Auswahlmenue; das gewaehlte Anliegen steht im Thread-Namen und der Einstiegstext gleich im ersten Beitrag — gute Stelle fuer "was wir wissen muessen". Wer keine anlegt, merkt nichts: ohne Eintrag bleibt es beim einen Knopf. Wer nur eine Sorte Tickets hat, soll nichts einrichten muessen. Uebernehmen ist ein Knopf im Thread, sichtbar fuers Team (wer aufraeumen darf, darf auch uebernehmen). Danach steht im Thread, wer sich kuemmert, und der Knopf wird zu "Freigeben". Die Bedingung "nur wenn noch niemand dran ist" steckt im UPDATE und nicht davor — zwei gleichzeitige Klicks wuerden sonst beide gewinnen. Beim Schliessen landen Anliegen und Bearbeiter im Transcript und im Mod-Log-Eintrag. Die Erstellung ist dabei aus client.js nach tickets.js gewandert. Knopf und Menue gehen jetzt denselben Weg, und in client.js sind drei Importzeilen weggefallen, die nur noch dort standen. Geprueft: alsEmoji gegen 20 Eingaben (Unicode, Hautfarbe, ZWJ-Ketten, Server-Emoji, Murks) und das fertige Panel gegen discord.js selbst — ein ungueltiges Emoji oder ein zu langes Label laesst Discord sonst die ganze Nachricht fallen, und das faellt erst beim Senden auf. Im Browser: Liste, Bearbeiten-Formular und der leere Zustand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
getLevelRow, isPlaytester, saveWish, toggleWishVote, myWishVotes,
|
||||
logAudit, listAudit, playerHistory, uptimeBuckets, watchdogBuckets, watchdogDays,
|
||||
heartbeatBuckets, heartbeatFirst,
|
||||
listTicketCategories, createTicketCategory, updateTicketCategory, deleteTicketCategory,
|
||||
listIncidents, listMonitored, createMonitored, updateMonitored, deleteMonitored,
|
||||
deleteUserData,
|
||||
saveSsoApp, listSsoApps, deleteSsoApp, getSsoApp,
|
||||
@@ -296,6 +297,60 @@ export function registerApiRoutes(app, client) {
|
||||
return { deleted: deleteMonitored(Number(request.params.id)), dienste: listMonitored() };
|
||||
});
|
||||
|
||||
// Ticket-Anliegen. Ohne Eintrag bleibt es beim einen Knopf — deshalb ist
|
||||
// eine leere Liste kein Fehler, sondern der Normalfall fuer kleine Server.
|
||||
app.get('/api/ticket-categories', async (request, reply) => {
|
||||
if (requireAnyScope(request, reply)) return;
|
||||
return { kategorien: listTicketCategories() };
|
||||
});
|
||||
|
||||
/** Eingaben pruefen — gibt null zurueck und hat dann schon geantwortet */
|
||||
function anliegenBody(request, reply) {
|
||||
const b = request.body ?? {};
|
||||
const name = String(b.name ?? '').trim();
|
||||
if (!name) {
|
||||
reply.code(400).send({ error: 'Name fehlt.' });
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
name: name.slice(0, 80),
|
||||
// Discord lehnt die ganze Nachricht ab, wenn ein Emoji nicht passt —
|
||||
// geprueft wird beim Bauen des Menues, hier nur gekuerzt
|
||||
emoji: String(b.emoji ?? '').trim().slice(0, 32) || '🎫',
|
||||
beschreibung: String(b.beschreibung ?? '').trim().slice(0, 100),
|
||||
ping_role_id: String(b.ping_role_id ?? '').trim() || null,
|
||||
intro: String(b.intro ?? '').trim().slice(0, 1024),
|
||||
sort: Number(b.sort) || 0,
|
||||
};
|
||||
}
|
||||
|
||||
app.post('/api/ticket-categories', async (request, reply) => {
|
||||
if (requireScope(request, reply, 'settings')) return;
|
||||
const k = anliegenBody(request, reply);
|
||||
if (!k) return;
|
||||
createTicketCategory(k);
|
||||
logAudit(getSessionUser(request), 'ticket-anliegen angelegt', k.name);
|
||||
return { ok: true, kategorien: listTicketCategories() };
|
||||
});
|
||||
|
||||
app.put('/api/ticket-categories/:id', async (request, reply) => {
|
||||
if (requireScope(request, reply, 'settings')) return;
|
||||
const k = anliegenBody(request, reply);
|
||||
if (!k) return;
|
||||
updateTicketCategory({ ...k, id: Number(request.params.id) });
|
||||
logAudit(getSessionUser(request), 'ticket-anliegen geaendert', k.name);
|
||||
return { ok: true, kategorien: listTicketCategories() };
|
||||
});
|
||||
|
||||
app.delete('/api/ticket-categories/:id', async (request, reply) => {
|
||||
if (requireScope(request, reply, 'settings')) return;
|
||||
logAudit(getSessionUser(request), 'ticket-anliegen entfernt', String(request.params.id));
|
||||
return {
|
||||
deleted: deleteTicketCategory(Number(request.params.id)),
|
||||
kategorien: listTicketCategories(),
|
||||
};
|
||||
});
|
||||
|
||||
// AutoMod: Discords eigene Regeln lesen und schalten. Die Wortlisten
|
||||
// bleiben in Discord — dort ist das Bearbeiten besser geloest.
|
||||
app.get('/api/automod', async (request, reply) => {
|
||||
|
||||
Reference in New Issue
Block a user