diff --git a/server/src/routes/dash.ts b/server/src/routes/dash.ts
index a40077a..79bc118 100644
--- a/server/src/routes/dash.ts
+++ b/server/src/routes/dash.ts
@@ -597,7 +597,7 @@ dashRoutes.get('/settings', (c) => {
discord: {
clientId: d.clientId,
guildId: d.guildId,
- roleId: d.roleId,
+ roleIds: d.roleIds,
hatGeheimnis: Boolean(d.clientSecret),
bereit: d.bereit,
// Die Stelle, an der der erste Anlauf fast immer scheitert.
@@ -607,7 +607,10 @@ dashRoutes.get('/settings', (c) => {
clientSecret:
Boolean(d.clientSecret) && !ausDatenbank(DISCORD_KEYS.clientSecret),
guildId: Boolean(d.guildId) && !ausDatenbank(DISCORD_KEYS.guildId),
- roleId: Boolean(d.roleId) && !ausDatenbank(DISCORD_KEYS.roleId),
+ roleIds:
+ d.roleIds.length > 0 &&
+ !ausDatenbank(DISCORD_KEYS.roleIds) &&
+ !ausDatenbank(DISCORD_KEYS.roleIdAlt),
},
},
})
@@ -619,7 +622,7 @@ dashRoutes.post('/settings', async (c) => {
clientId?: string
clientSecret?: string
guildId?: string
- roleId?: string
+ roleIds?: string | string[]
}
}>(c)
if (!body) return c.json(KEIN_JSON, 400)
@@ -642,10 +645,31 @@ dashRoutes.post('/settings', async (c) => {
if (v && !istId(v)) return c.json({ error: 'Server-ID: 17 bis 20 Ziffern' }, 400)
setSetting(DISCORD_KEYS.guildId, v)
}
- if (d.roleId !== undefined) {
- const v = d.roleId.trim()
- if (v && !istId(v)) return c.json({ error: 'Rollen-ID: 17 bis 20 Ziffern' }, 400)
- setSetting(DISCORD_KEYS.roleId, v)
+ if (d.roleIds !== undefined) {
+ // Komma, Semikolon, Leerzeichen, Zeilenumbruch — alles trennt. Wer
+ // drei IDs untereinander einfuegt, soll sie nicht erst zu einer Zeile
+ // zusammenschieben muessen.
+ const liste = (Array.isArray(d.roleIds) ? d.roleIds.join(',') : d.roleIds)
+ .split(/[\s,;]+/)
+ .map((t) => t.trim())
+ .filter(Boolean)
+
+ // JEDE einzeln pruefen und die schlechte BEIM NAMEN nennen. "Rollen-ID
+ // ungueltig" bei fuenf Eintraegen ist eine Suchaufgabe, keine Meldung.
+ for (const eintrag of liste) {
+ if (!istId(eintrag)) {
+ return c.json(
+ { error: `"${eintrag}" ist keine Rollen-ID (17 bis 20 Ziffern)` },
+ 400,
+ )
+ }
+ }
+
+ const eindeutig = [...new Set(liste)]
+ setSetting(DISCORD_KEYS.roleIds, eindeutig.join(','))
+ // Den alten Einzelwert raeumen wir weg, sobald die Liste gepflegt
+ // wird: zwei Quellen fuer dieselbe Frage laufen sonst auseinander.
+ setSetting(DISCORD_KEYS.roleIdAlt, '')
}
// Ein leeres Feld heisst "unveraendert lassen", nicht "loeschen" — sonst
// wuerde jedes Speichern das Geheimnis wegwerfen, weil es nie im Formular
@@ -664,7 +688,7 @@ dashRoutes.post('/settings', async (c) => {
!jetzt.clientId && 'Client-ID',
!jetzt.clientSecret && 'Client-Geheimnis',
!jetzt.guildId && 'Server-ID',
- !jetzt.roleId && 'Rollen-ID',
+ jetzt.roleIds.length === 0 && 'mindestens eine Rollen-ID',
].filter(Boolean),
})
})
diff --git a/server/src/routes/discord.ts b/server/src/routes/discord.ts
index 8840743..dafb968 100644
--- a/server/src/routes/discord.ts
+++ b/server/src/routes/discord.ts
@@ -144,10 +144,15 @@ discordRoutes.get('/discord/callback', async (c) => {
}
const member = (await memberAntwort.json()) as { roles?: string[] }
- if (!Array.isArray(member.roles) || !member.roles.includes(d.roleId)) {
- // Bewusst ohne den Namen der Rolle: wer sie nicht hat, muss auch
- // nicht erfahren, wie sie heisst.
- return zurueck(c, 'Dir fehlt die noetige Rolle auf dem Server.')
+ // EINE der eingetragenen Rollen reicht. Mehrere Rollen sind der
+ // Normalfall, sobald mehr als eine Person Zugang haben soll — Admin
+ // und Fotograf sind selten dieselbe Rolle.
+ const hatRolle =
+ Array.isArray(member.roles) && member.roles.some((r) => d.roleIds.includes(r))
+ if (!hatRolle) {
+ // Bewusst ohne die Namen der Rollen: wer keine hat, muss auch nicht
+ // erfahren, welche es gaebe.
+ return zurueck(c, 'Dir fehlt eine der noetigen Rollen auf dem Server.')
}
// -- Konto finden oder anlegen --------------------------------------
diff --git a/server/src/settings.ts b/server/src/settings.ts
index b8b2773..66a9184 100644
--- a/server/src/settings.ts
+++ b/server/src/settings.ts
@@ -50,31 +50,50 @@ export const DISCORD_KEYS = {
clientId: 'discord.client_id',
clientSecret: 'discord.client_secret',
guildId: 'discord.guild_id',
- roleId: 'discord.role_id',
+ /** MEHRERE Rollen, mit Komma getrennt. Der alte Schluessel mit genau einer
+ * Rolle wird weiter gelesen (siehe rollen()) — wer schon eine eingetragen
+ * hatte, soll sie nicht verlieren, nur weil hier eine Liste daraus wurde. */
+ roleIds: 'discord.role_ids',
+ roleIdAlt: 'discord.role_id',
} as const
export type DiscordConfig = {
clientId: string
clientSecret: string
guildId: string
- roleId: string
+ /** Wer EINE davon hat, darf herein. Leere Liste heisst: nicht eingerichtet. */
+ roleIds: string[]
/** Erst wenn alle vier stehen, hat die Anmeldung ueberhaupt eine Chance.
* Ein halb eingerichteter Weg, der auf halber Strecke abbricht, ist
* schlimmer als ein Knopf, den es nicht gibt. */
bereit: boolean
}
+/** Die Rollen, aus welcher Quelle sie auch kommen.
+ *
+ * Reihenfolge: die neue Liste, dann der alte Einzelwert, dann die Umgebung.
+ * Getrennt wird an Komma, Semikolon, Leerzeichen und Zeilenumbruch — wer eine
+ * Liste aus Discord kopiert, soll sie nicht erst von Hand aufraeumen. */
+export function rollen(): string[] {
+ const roh =
+ getSetting(DISCORD_KEYS.roleIds) ||
+ getSetting(DISCORD_KEYS.roleIdAlt) ||
+ (process.env.DISCORD_ROLE_IDS ?? process.env.DISCORD_ROLE_ID ?? '')
+
+ return [...new Set(roh.split(/[\s,;]+/).map((t) => t.trim()).filter(Boolean))]
+}
+
export function discordConfig(): DiscordConfig {
const clientId = getSetting(DISCORD_KEYS.clientId, 'DISCORD_CLIENT_ID')
const clientSecret = getSetting(DISCORD_KEYS.clientSecret, 'DISCORD_CLIENT_SECRET')
const guildId = getSetting(DISCORD_KEYS.guildId, 'DISCORD_GUILD_ID')
- const roleId = getSetting(DISCORD_KEYS.roleId, 'DISCORD_ROLE_ID')
+ const roleIds = rollen()
return {
clientId,
clientSecret,
guildId,
- roleId,
- bereit: Boolean(clientId && clientSecret && guildId && roleId),
+ roleIds,
+ bereit: Boolean(clientId && clientSecret && guildId && roleIds.length > 0),
}
}
diff --git a/server/ui/src/api.ts b/server/ui/src/api.ts
index 32490e4..a7c89ea 100644
--- a/server/ui/src/api.ts
+++ b/server/ui/src/api.ts
@@ -154,7 +154,8 @@ export type Einstellungen = {
discord: {
clientId: string
guildId: string
- roleId: string
+ /** Wer EINE davon hat, darf herein. */
+ roleIds: string[]
/** Nur OB eines hinterlegt ist — das Geheimnis selbst geht nie heraus. */
hatGeheimnis: boolean
bereit: boolean
@@ -163,7 +164,7 @@ export type Einstellungen = {
clientId: boolean
clientSecret: boolean
guildId: boolean
- roleId: boolean
+ roleIds: boolean
}
}
}
diff --git a/server/ui/src/components/DiscordKarte.tsx b/server/ui/src/components/DiscordKarte.tsx
index c2c5663..8701472 100644
--- a/server/ui/src/components/DiscordKarte.tsx
+++ b/server/ui/src/components/DiscordKarte.tsx
@@ -19,7 +19,7 @@ export function DiscordKarte() {
const [clientId, setClientId] = useState('')
const [geheimnis, setGeheimnis] = useState('')
const [guildId, setGuildId] = useState('')
- const [roleId, setRoleId] = useState('')
+ const [rollen, setRollen] = useState('')
async function laden() {
setLaedt(true)
@@ -28,7 +28,7 @@ export function DiscordKarte() {
setDaten(e)
setClientId(e.discord.clientId)
setGuildId(e.discord.guildId)
- setRoleId(e.discord.roleId)
+ setRollen(e.discord.roleIds.join('\n'))
} catch (err) {
note('fehler', err instanceof Error ? err.message : 'Unbekannt')
} finally {
@@ -51,7 +51,7 @@ export function DiscordKarte() {
// das Geheimnis wegwerfen, weil es nie im Formular steht.
...(geheimnis ? { clientSecret: geheimnis } : {}),
guildId,
- roleId,
+ roleIds: rollen,
}),
(r) =>
r.bereit
@@ -84,7 +84,8 @@ export function DiscordKarte() {
rechts={
d.bereit ? (
- eingerichtet
+ eingerichtet ·{' '}
+ {d.roleIds.length === 1 ? '1 Rolle' : `${d.roleIds.length} Rollen`}
) : (
@@ -156,13 +157,26 @@ export function DiscordKarte() {
hinweis={d.ausDerUmgebung.guildId ? 'kommt derzeit aus der Umgebung' : undefined}
onChange={(e) => setGuildId(e.target.value)}
/>
- setRoleId(e.target.value)}
- />
+