## Phase 6 – WebUI-Fundament (Status: abgeschlossen, manuell bestätigt) - OAuth/Sessions, Settings-Framework, Layout, Modul-Toggles, Access-Rules - Login manuell bestätigt ## Phase 7 – WebUI Modul-Seiten + Owner-Panel (Status: abgeschlossen, manuell bestätigt) ### Abgeschlossen - Alle Modul-Dashboard-Seiten, Commands-Seite, Overview-Aktivität - Owner-Panel (`/owner`: Übersicht, Guilds, Users, Flags, Presence, Team, Jobs, Changelog, Audit) - Prisma `20260722190000_phase7_owner`, Shared Zod (`dashboard.ts`, `owner.ts`) - BullMQ WebUI↔Bot, Presence-/Wartungsmodus-Anbindung - Manuell bestätigt (User-Freigabe) ## Phase 8 – Landing, Status, Rechtsseiten (Status: abgeschlossen, manuell bestätigt) ### Abgeschlossen - Landing `/`, Status `/status`, Rechtsseiten, Public APIs, Core-Commands, `docs/verification.md` - Manuell bestätigt (User-Freigabe) ## Deploy-Branch (`deploy`) – Traefik + Dashboard-Domain ### Abgeschlossen (Code auf Branch `deploy`; `main` unverändert) - Traefik-Vollstack: Compose-Netz `traefik-network` (external) + `internal` - WebUI hinter Traefik: Host `dashboard.nexumi.de`, Entrypoint `websecure`, `certresolver=letsencrypt` - Host-Port `3000` entfernt; nur Traefik exponiert die WebUI - `WEBUI_URL=https://dashboard.nexumi.de` (OAuth-Callback: `/api/auth/callback`) - Marketing-Landing archiviert unter `apps/webui/src/archived/landing-page.tsx` - `/` zeigt Login; `/login` leitet auf `/` weiter (Query erhalten) - **SPEC-Abweichung (bewusst):** öffentliche Landing später extern auf `nexumi.de`; Dashboard unter Subdomain - Rechtsseiten vollständig (Impressum/Datenschutz/ToS) DE+EN; Locale per Accept-Language (Cookie hat Vorrang) - Plesk-Landing unter `landingpage/` mit lokalen Rechtsseiten (`impressum.php`, `datenschutz.php`, `nutzungsbedingungen.php`) ### Manuell testen (Deploy) - [ ] DNS `dashboard.nexumi.de` → Traefik-Host - [ ] Discord OAuth Redirect: `https://dashboard.nexumi.de/api/auth/callback` - [ ] `docker compose up -d --build` (Netz `traefik-network` muss existieren) - [ ] TLS-Zertifikat (Resolver-Name ggf. an Traefik anpassen) - [ ] Login `/` → Dashboard; Session-Cookie `Secure` ## Post-Phase – Premium + DSGVO (Status: implementiert, manuelle Tests offen) ### Abgeschlossen (Code) - Premium: `PremiumTierConfig`, Guild-/User-Zuweisung, Owner-UI `/owner/premium` - Limits enforced (Tags/Feeds/Backups) in Bot + WebUI - DSGVO: `/privacy`, `/gdpr delete confirm:true` (Löschung/Anonymisierung + `GdprDeletionLog`) - Log-Retention: `LoggingConfig.retentionDays` + täglicher Job `retention`/`purgeExpiredLogs` - Snipe standardmäßig aus (`GuildSettings.snipeEnabled`, Toggle in Allgemeine Einstellungen) - Migration `20260722200000_premium_gdpr` ### Manuell testen - [ ] Owner `/owner/premium`: Limits speichern, Guild PREMIUM zuweisen - [ ] Free-Limit: mehr Tags/Feeds/Backups als Free-Cap → Fehler - [ ] `/privacy` und `/gdpr delete confirm:true` - [ ] Logging-Retention im Dashboard setzen - [ ] Snipe: default aus; nach Aktivierung in Einstellungen nutzbar ### Bewusst offen - Musik- und KI-Modul (nur auf Zuruf) - Premium-Zahlungsanbindung - Externe Marketing-Landing auf `nexumi.de` ## Post-Phase – Dashboard-UI + Command-Palette + Landing-Polish (Status: implementiert) ### Abgeschlossen (Code) - Guild-/Owner-Shell: Branding, Sticky-Header, mobile Sidebar-Sheet, Overview-Polish - Globale Cmd/Ctrl+K-Suche: Seiten, Slash-Commands, Einstellungsfelder (Deep-Links) - Settings-Feld-Registry + Form-Anchors; Commands-Seite liest `?q=` - Plesk-Landing: Inter, Hero-Hierarchie, dezente Motion, Designsprache an WebUI ### Manuell testen - [ ] Dashboard: Cmd/Ctrl+K → Seite, Command und Option navigieren (Hash-Scroll) - [ ] Mobile: Sidebar-Sheet + Such-Trigger - [ ] Overview und Owner-Panel Layout - [ ] Landing Light/Dark (`prefers-color-scheme`) + CTAs ### Bewusst offen - Musik- und KI-Modul (nur auf Zuruf) - Premium-Zahlungsanbindung ## Post-Phase – Channel-Picker überall (Status: implementiert) ### Abgeschlossen (Code) - `DiscordChannelSelect` / `DiscordChannelMultiSelect` + `useGuildChannels` - Alle Modul-Forms: Kanal-ID-Texteingaben durch durchsuchbare Channel-Auswahl ersetzt - Voice-/Category-Filter für TempVoice/Stats/Tickets ## Post-Phase – Role-Picker überall (Status: implementiert) ### Abgeschlossen (Code) - `DiscordRoleSelect` / `DiscordRoleMultiSelect` + `useGuildRoles` - Modul-Forms/Manager: Rollen-ID-Texteingaben durch durchsuchbare Role-Auswahl ersetzt (Welcome, Verification, Leveling, Logging, Birthdays, Giveaways, Tags, Tickets, Access Rules, Feeds, Scheduler, Commands) - Selfroles: Role-Builder statt Textarea (`roleId | label | emoji`) ### Manuell testen - [ ] Rollen in den Modul-Settings per Dropdown setzen und speichern - [ ] Multi-Select (Autoroles, No-XP-Roles, Support-Roles, …) speichert string[] korrekt - [ ] Access-Rules: Rolle wählen und Module zuweisen ## Post-Phase – Command-Global-Rules + Channel-Picker (Status: implementiert) ### Abgeschlossen (Code) - Globale Command Channel-Whitelist/-Blacklist in `GuildSettings` + Commands-UI - Searchable Multi-Select für Kanäle/Rollen (statt Raw-IDs) auf der Commands-Seite - APIs: `/commands/globals`, `/channels`, `/roles` - Bot erzwingt globale + per-Command Channel/Role/Cooldown-Regeln in `routeCommand` - Migration `20260722210000_command_global_channels` ### Manuell testen - [ ] Commands: globale Whitelist/Blacklist speichern, in Discord prüfen - [ ] Per-Command Channel/Rollen per Dropdown setzen - [ ] Deaktivierter Command / Cooldown antwortet ephemeral ## Post-Phase – Discord Embed-Builder (Status: implementiert) ### Abgeschlossen (Code) - Wiederverwendbare `DiscordEmbedBuilder`-Komponente inkl. Live-Vorschau - Basis: Titel, Beschreibung, Farbe - Erweitert: Author (Name/Icon/URL), Thumbnail, großes Bild, Footer (Text/Icon), Titel-URL, Timestamp - Welcome/Leave, Tags (EMBED) und Scheduler nutzen denselben Builder + `buildEmbedFromPayload` - Platzhalter in Text-/URL-Feldern inkl. `{user.avatar}` / `{server.icon}` (Welcome/Tags) ### Manuell testen - [ ] Welcome/Leave-Embed mit Author, Thumbnail, Banner und Footer speichern und in Discord prüfen - [ ] Tag mit Antworttyp Embed (auch nur Bild/Author ohne Titel) erstellen/bearbeiten - [ ] Geplante Nachricht mit erweitertem Embed anlegen - [ ] Einfaches Embed (nur Titel/Beschreibung/Farbe) funktioniert weiter wie zuvor ## Post-Phase – Logging Multi-Event pro Kanal (Status: implementiert) ### Abgeschlossen (Code) - Logging-UI: pro Log-Kanal mehrere Event-Typen per Multi-Select - Bestehende 1:1-Mappings werden beim Laden nach Kanal gruppiert; Speichern expandiert wieder auf `LogChannel`-Zeilen - Event-Typen sind exklusiv (DB `@@unique([guildId, eventType])`) – Auswahl in einer Zeile entfernt sie aus anderen ### Manuell testen - [ ] Mehrere Events einem Kanal zuweisen und speichern - [ ] Event von Kanal A nach Kanal B umhängen (verschwindet bei A) - [ ] Bot schreibt Logs der gewählten Events in den richtigen Kanal ## Post-Phase – Platzhalter-Hilfe (Status: implementiert) ### Abgeschlossen (Code) - Aufklappbare `PlaceholderHelp`-Komponente mit Kopieren pro Token - Kontextspezifische Presets (Welcome, Tags, Birthdays, Leveling, TempVoice, Stats, Feeds, Scheduler) - Welcome-DM nutzt jetzt dieselben Platzhalter wie Welcome-Text (nicht nur `{user}`) ### Manuell testen - [ ] Welcome: Liste aufklappen, Platzhalter kopieren, in Embed/Text einfügen - [ ] Tags/Birthdays/Leveling/Stats/Feeds/Scheduler: nur die jeweiligen Tokens sichtbar ## Post-Phase – Components V2 (Status: implementiert, manuelle Tests offen) ### Abgeschlossen (Code) - Shared Zod: `MessageComponentsV2` + Actions, `COMPONENTS_V2` für Welcome/Tags, Leave-Typen, custom_id `cv2:{source}:{ref}:{actionId}` - Prisma-Migration `20260722220000_components_v2`: `welcomeComponents`/`leaveType`/`leaveComponents`, `Tag.components`, `ScheduledMessage.components`, `ComponentMessageBinding` - Bot: `buildComponentsV2Payload`, Anbindung Welcome/Leave/Tags/Scheduler, Interaction-Handler, Dashboard-Send-Queue `messages`/`dashboardMessageSend` - Slash: `/embed builder` + `/embed components` (Quick-V2) - WebUI: `DiscordComponentsV2Builder`, Mode-Switches Welcome/Tags/Scheduler, Dashboard-Seite `/messages` ### Manuell testen - [ ] Welcome V2 mit Container + Thumbnail-Section + Link-Button + Toggle-Role-Button - [ ] Tag V2 mit String-Select → ephemeral reply - [ ] Scheduler V2 Ankündigung - [ ] Dashboard Messages: Embed + Components V2 senden - [ ] Slash `/embed components` - [ ] Bestehende Embeds unverändert - [ ] Migration anwenden (`prisma migrate deploy`) und Bot/WebUI neu bauen ### Bewusst offen - Modal-only Components, File-Component, Premium-Buttons (nicht im MVP) - Pixelgenaue Discord-Vorschau im Dashboard ## Post-Phase – Harden (Sharding / Gates / Observability) (Status: implementiert) ### Abgeschlossen (Code) - Confirmations (Moderation + Guildbackup) in Redis mit TTL statt Prozess-`Map` - Command-Registrierung einmal im Manager (Redis-Lock); Recurring Jobs einmal (Primary-Shard + Lock) - Modul-Toggles + FeatureFlags + User-/Guild-Blacklists im Bot erzwungen (`routeCommand`, Events, GuildCreate leave) - Moderation: Hierarchy-Checks, Duration vor Ban-Confirm, Timeout-Cap 28d, warn remove guild-scoped, atomare Case-Nummern, Temp-Ban Job-Dedup + UNBAN-Case, `setDefaultMemberPermissions` - Sentry (`@sentry/node`) in Bot + WebUI (`instrumentation.ts`); echte Prometheus-Metrics (`/metrics`) - Interaction-Registry statt if-Kette; Unit-Tests für Duration, Confirm-Store, Module-Gates, Metrics ### Manuell testen - [ ] Ban mit Dauer: Confirm auf anderem Shard / nach Restart innerhalb TTL - [ ] Dashboard: Modul Moderation aus → `/ban` antwortet „Modul deaktiviert“ - [ ] Owner: User-Blacklist → Interactions blockiert; Guild-Blacklist → Bot verlässt Server - [ ] `/metrics` mit Bearer-Token: Counters + Shard-Ping - [ ] Temp-Ban ablaufen → Unban + Case `UNBAN` ### Bewusst offen - Musik- und KI-Modul (nur auf Zuruf) - Premium-Zahlungsanbindung - P4-UX (Server-Lock, Ban deleteMessageSeconds) bewusst nachgelagert ## Post-Phase – Verification Captcha-Anbieter + Alt-Erkennung (Status: implementiert) ### Abgeschlossen (Code) - Captcha-Anbieter wählbar pro Guild: `MATH`, `RECAPTCHA_V2`, `RECAPTCHA_V3`, `HCAPTCHA`, `TURNSTILE` - Google/hCaptcha/Turnstile Keys in `.env` (WebUI); Dashboard zeigt nur konfigurierte Anbieter - Captcha-Seite `/verify/captcha` mit Provider-Widgets; Server-side Siteverify - Alt-Account-Erkennung: gesalzener IP-Hash (Captcha-Flow) + Invite-Cluster; optional blockieren via `failAction` - Prisma `VerificationRecord` + Config-Felder; Migration `20260723120000_verification_captcha_alt` - Slash `/verify setup`: `captcha_provider`, `alt_detection` ### Manuell testen - [ ] `.env`: `RECAPTCHA_SITE_KEY`/`SECRET` setzen → Dashboard zeigt reCAPTCHA v2/v3 - [ ] Guild Captcha-Modus + Anbieter wählen → Verify-Button → WebUI Captcha → Rolle - [ ] Alt-Erkennung an: zweiter Account gleiche IP → Kick/Ban je nach failAction - [ ] Migration: `prisma migrate deploy` + Bot/WebUI neu bauen - [ ] `CAPTCHA_IP_HASH_SALT` gesetzt (min. 16 Zeichen), sonst keine IP-Alt-Signale ### Bewusst offen - Kein Browser-Fingerprinting (DSGVO); IP nur gehasht mit Salt - Keine manuelle „Alt freigeben“-UI (nur Flag in `VerificationRecord`) ## Post-Phase – Leveling Rollen-Belohnungen UI (Status: implementiert) ### Abgeschlossen (Code) - Dashboard Leveling: Level→Rolle-Belohnungen bearbeiten (Liste, speichern ersetzt `LevelReward`) - Shared Schema `rewards` + `hasUniqueRewardLevels`; i18n DE/EN - Stapel-Toggle mit Hinweis neben dem Belohnungs-Editor ### Manuell testen - [ ] Belohnung hinzufügen (z. B. Level 5 → Rolle), speichern, Mitglied levelt → Rolle vergeben - [ ] Stack an/aus: stapeln vs. nur aktuelle Belohnungsrolle - [ ] Doppeltes Level → Fehlermeldung, speichern blockiert ## Post-Phase – Giveaway Bugs (Timestamp + End-Status) (Status: implementiert) ### Abgeschlossen (Code) - Embed: Endzeit als Discord-Timestamp in der **Description** (Footer rendert `` nicht) - `/giveaway end` wirft bei fehlender/bereits beendeter ID Fehler statt Fake-Erfolg - Dashboard „Jetzt beenden“ prüft `endedAt` nach Job; Timeout/Fehler → kein Erfolgs-Toast ### Manuell testen - [ ] Neues Giveaway: Endzeit als „in X Stunden“ sichtbar, nicht als Roh-`` - [ ] `/giveaway end` mit falscher ID → Fehlermeldung; mit korrekter ID → Reroll möglich - [ ] Dashboard „Jetzt beenden“ → Status beendet, Discord-Nachricht aktualisiert ## Post-Phase – Audit Remediation (Reliability + Security) (Status: implementiert) ### Abgeschlossen (Code) - Scheduler: Self-Remove des aktiven Jobs entfernt; safeRemove für externe Cancels - Channel-Picker: Fehler/leere Responses nicht cachen; Error+Reload wie Rollen - `requirePermission`: Member- vs. Bot-Permissions getrennt; Defer auf Ban/Purge/Lock/Backup/Giveaway/Media/Translate - Giveaway: atomares End (`updateMany`), Pause cancel/reschedule, Dashboard Pause/Delete via BullMQ - Component-/Context-Gates: Maintenance + Modul-Toggle - Owner-Guild-Bypass ab SUPPORT; OAuth `refresh_token` + Session-Refresh - Queue safeRemove (Temp-Ban, Reminders, Selfroles); `addJobAndAwait` rethrowt Enqueue-Fehler - DefaultMemberPermissions für Admin-Commands; Cooldown erst nach erfolgreichem Execute - API: BadRequest/Upstream-Errors; Discord Roles/Channels 401/403/429/502 ### Manuell testen - [ ] One-shot Schedule sendet einmal (kein Doppel-Post nach Worker-Retry) - [ ] Channel-Dropdown bei API-Fehler: Retry statt permanent „Keine Treffer“ - [ ] Giveaway Pause im Dashboard → Button disabled; Unpause → Timer weiter; End trotz Pause - [ ] Modul aus → Join-Button / Ticket-Panel antwortet „Modul deaktiviert“ - [ ] VIEWER-Owner ohne Manage Guild: kein Dashboard-Guild-Zugriff; SUPPORT+: Bypass - [ ] Session nach Token-Ablauf refresht ohne erzwungenen Re-Login (mit gültigem refresh_token) ## Post-Phase – Giveaway End Fix + Role Picker (Status: implementiert) ### Abgeschlossen (Code) - Root cause: `endGiveaway` removed its own locked BullMQ job → End-Job crashte vor DB-Update (Timer/UI/Command hingen) - `cancelGiveawayJob` nur noch außerhalb des Workers (Command/Dashboard); locked remove wird ignoriert - Dashboard-End nutzt eindeutige Job-IDs (`giveaway-end-now-…`); klarere 502-Fehler statt generischem 500 - Bot-Start: `recoverOverdueGiveaways` beendet überfällige aktive Giveaways - Role-Picker: kein Cache von leeren/fehlerhaften Responses; Redis-Cache best-effort; manuelle Filter statt cmdk-Default ### Manuell testen - [ ] Giveaway starten (kurz), warten bis Timer abläuft → Embed „Beendet“, Button weg, Gewinner gezogen - [ ] Giveaway vorzeitig per Dashboard „Jetzt beenden“ → kein Internal Server Error - [ ] `/giveaway list` → ID kopieren → `/giveaway end id:…` → Erfolg - [ ] Verifizierung: Rollen-Dropdown zeigt Serverrollen (nicht nur „Keine Treffer“) - [ ] Nach Deploy: bereits hängende Giveaways werden beim Bot-Start recovered ## Post-Phase – Starboard Verbesserungen (Status: implementiert) ### Abgeschlossen (Code) - `/starboard setup` funktioniert trotz `enabled: false` (Gate-Ausnahme) - Kanal-Permission-Check vor Setup; `/starboard status` - Sync: Source-Delete/Bulk-Delete, Message-Edit, RemoveAll/RemoveEmoji - Race-sicheres Create + Recreate wenn Starboard-Msg fehlt - Zod/UI: Emoji bis 80 Zeichen, Threshold 1–100, Channel Pflicht bei enabled, NSFW-Hinweis - Unit-Tests `emojiMatches` / `shouldIgnoreMessage` ### Manuell testen - [ ] `/starboard setup` auf frischem Server ohne Dashboard-Toggle - [ ] Stern ≥ Schwelle → Post; unter Schwelle → Post entfernt - [ ] Original löschen/editieren → Starboard aktualisiert/entfernt - [ ] Dashboard: aktivieren ohne Kanal → Fehler ## Post-Phase – Welcome Embed Mentions + Autorole + Preview (Status: implementiert) ### Abgeschlossen (Code) - Embed `{user}` in Titel/Author/Footer als Displayname (Discord resolved Mentions dort nicht) - Beschreibung/Content behalten `<@id>`-Mentions - Autorole: Rollen nachladen, managed/@everyone filtern, `editable`-Check, Warn-Logs bei Hierarchy/Permission - WebUI Embed-/Components-Vorschau: echte Session-User- + Guild-Daten statt Dummy `@Alex` ### Manuell testen - [ ] Welcome-Embed Titel `Welcome {user}` → Anzeigename, nicht `<@id>` - [ ] Beschreibung `{user}` → klickbare Mention - [ ] User-Autorollen gesetzt, Bot-Rolle über Autorolle, Manage Roles → Rolle bei Join - [ ] Dashboard Welcome-Embed-Vorschau zeigt eigenen Namen/Avatar und Servername/-icon ## Post-Phase – Owner Guild-Liste Meta + Invite (Status: implementiert) ### Abgeschlossen (Code) - Owner `/owner/guilds`: Discord-Name, Icon, Member-Count, ID (Live-Meta via Bot-Token, Redis-Cache) - Suche nach Name und ID - Aktion „Invite erstellen“ (Systemkanal zuerst, max. 24h / 5 Uses, Clipboard + Owner-Audit) ### Manuell testen - [ ] Serverliste zeigt Name/Logo/Mitglieder statt nur ID - [ ] Suche nach Servername filtert - [ ] Invite erstellen → Link im Toast/Clipboard; Bot braucht Create Instant Invite ## Post-Phase – Owner Guild-Dashboard Bypass (Status: implementiert) ### Abgeschlossen (Code) - Owner-Team (jede Owner-Rolle) kann jedes Guild-Dashboard öffnen, in dem der Bot installiert ist — ohne Discord „Server verwalten“ - `requireGuildAccess` / Layout nutzen Owner-Bypass; Guild-Meta via Bot-API wenn nicht in OAuth-Liste - Owner `/owner/guilds`: Button „Dashboard öffnen“; Hinweisbanner im Guild-Dashboard bei Bypass ### Manuell testen - [ ] Owner-UI → Server → Dashboard öffnen (Server, auf dem man kein Member/Manager ist) - [ ] Welcome/Settings speichern funktioniert; Banner „Owner-Zugriff“ sichtbar - [ ] Nicht-Owner ohne Manage Guild → weiterhin kein Zugriff ## Post-Phase – Owner Presence WebUI Polish (Status: implementiert) ### Abgeschlossen (Code) - PresenceForm: Live-Vorschau, i18n-Enums, Zeichenzähler, Switch+Confirm für Wartung, Hinweise - Sofort-Apply: WebUI enqueued `presenceRefresh` nach Save - Wartungsmodus: Discord DND + Wartungstext (keine Rotation) - VIEWER read-only auf `/owner/presence` (Save nur ADMIN+) ### Manuell testen - [ ] Status/Text speichern → Discord-Präsenz innerhalb weniger Sekunden - [ ] Rotierende Zeilen → Vorschau erste Zeile; Job rotiert weiter - [ ] Wartung an → Confirm, Discord zeigt Wartungstext, kein Rotating - [ ] VIEWER sieht Formular, kann nicht speichern ## Post-Phase – Owner About /about (Status: implementiert) ### Abgeschlossen (Code) - Core-Command `/about` (neben SPEC-`/info`); Help-Katalog + Locales DE/EN - Prisma `BotAboutConfig` Singleton + Migration `20260724120000_bot_about` - Owner `/owner/about`: TEXT / Embed / Components V2 (Builder), Redis-Cache, Audit - Components-V2-Source `a` für Button-Interaktionen aus `/about` ### Manuell testen - [ ] Migration deploy + Bot/WebUI neu bauen - [ ] Owner About Embed speichern → `/about` zeigt Embed - [ ] About deaktivieren → Deaktiviert-Hinweis - [ ] `/info` unverändert technisch ## Post-Phase – Automod/Moderation Dashboard Ausbau (Status: implementiert) ### Abgeschlossen (Code) - Automod: alle 10 Filterregeln im Dashboard (Aktion, Schwellen, Link-WL/BL, Wörter/Regex, Ausnahmen) - Lockdown an/aus enqueued `activateLockdown` / `deactivateLockdown` an den Bot - Anti-Raid-Aktion `VERIFY` (Unverified-Rolle) zusätzlich zu `LOCKDOWN` - Moderation: Warn-Tabelle + Case edit/delete im Dashboard - `/lock` `/unlock` Option `server` für guild-weiten Lock - Shared: `AutomodRuleDashboardSchema`, `CaseUpdateDashboardSchema`, `AntiRaidAction` inkl. VERIFY ### Manuell testen - [ ] Automod: WORD_FILTER Wörter setzen, EXTERNAL_LINK Whitelist, Regel speichern → Filter greift - [ ] Lockdown-Toggle aus → Kanäle wieder beschreibbar - [ ] Anti-Raid VERIFY: Unverified-Rolle in Verification konfiguriert, Join-Welle → Rolle - [ ] Cases: Grund editieren / löschen; Warnings entfernen - [ ] `/lock server:true` und `/unlock server:true` ## Post-Phase – Selfroles WebUI → Discord Sync (Status: implementiert) ### Abgeschlossen (Code) - Root cause: Dashboard speicherte Selfrole-Panels nur in Postgres, ohne BullMQ-Job an den Bot - Neue Queue `selfroles` mit Jobs `selfRolePanelCreate` / `Update` / `Delete` - WebUI wartet (bounded) auf Bot-Bestätigung; Panel hat danach `messageId` - Update zieht fehlende Discord-Nachricht nach (auch für Alt-Panels ohne `messageId`) - Roles-JSON `{ entries }` wird im Dashboard korrekt gelesen ### Manuell testen - [ ] WebUI: Panel erstellen → Embed/Buttons erscheinen im gewählten Channel - [ ] Panel editieren (Titel/Rollen) → Discord-Nachricht aktualisiert sich - [ ] Channel wechseln → alte Nachricht weg, neue im Zielkanal - [ ] Panel löschen → Discord-Nachricht entfernt - [ ] Alt-Panel ohne Message speichern → Nachricht wird nachgezogen ## Post-Phase – Selfroles Role-Builder (Status: implementiert) ### Abgeschlossen (Code) - Textarea `roleId | label | emoji` durch zeilenbasierten Builder ersetzt - `DiscordRoleSelect` + Label/Beschreibung-Felder + `DiscordEmojiSelect` (Server-CDN + Unicode) - API `GET /api/guilds/[guildId]/emojis` inkl. Redis-Cache; Shared `DiscordEmojiOption` - Reaktions-Modus: Emoji je Rolle vor Save Pflicht - Emoji-Picker Discord-ähnlich: 9er-Grid, Kategorie-Sidebar, Vollsatz via `@emoji-mart/data`, Hautfarben, Suche ### Manuell testen - [ ] Panel mit Role-Picker, Label, Server-Emoji und Unicode speichern → Discord-Nachricht korrekt - [ ] Dropdown: optionale Beschreibung erscheint in den Select-Optionen - [ ] REACTIONS ohne Emoji → Toast, kein Save - [ ] Bestehende Panels mit Custom-Emoji-Strings laden und editieren - [ ] Emoji-Picker: Kategorien scrollen, Suche, Hautfarbe, Server-Grid ## Nächster geplanter Schritt - Deploy auf Traefik-Server manuell verifizieren; danach ggf. `deploy` → `main` mergen (nur auf Freigabe). ## Post-Phase – Verification Panel aus WebUI (Status: implementiert) ### Abgeschlossen (Code) - Root cause: Dashboard speicherte nur `VerificationConfig`, ohne Panel in Discord zu posten - Neuer BullMQ-Job `verificationPanelSync` (Queue `verification`); WebUI wartet bounded auf Bot-Bestätigung - `syncVerificationPanel` shared für `/verify panel` und Dashboard (Edit bei gleichem Kanal, sonst neu posten) - i18n DE/EN: Hinweis in Modul-Beschreibung + Fehlertexte bei Panel-Sync-Fail ### Manuell testen - [ ] WebUI: Verifizierung aktivieren, Kanal + Rolle setzen, Speichern → Panel erscheint im Kanal - [ ] Speichern mit geändertem Modus → bestehendes Panel wird aktualisiert - [ ] Kanal wechseln und speichern → altes Panel weg, neues im Zielkanal - [ ] Bot ohne Send-Permission → Fehlermeldung; Config bleibt gespeichert, erneutes Speichern nach Fix ## Post-Phase – Ticket Panel aus WebUI + Kategorie-Emoji-Picker (Status: implementiert) ### Abgeschlossen (Code) - Root cause: Dashboard speicherte Ticket-Config/Kategorien ohne Panel in Discord - Prisma `TicketConfig.panelChannelId` / `panelMessageId`; Migration `20260725160000_ticket_panel` - BullMQ-Job `ticketPanelSync` (Queue `tickets`); Sync auch bei Kategorie create/update/delete - `syncTicketPanel` shared für `/ticket panel_create` und Dashboard; Custom-Emoji-Parsing für Buttons/Select - WebUI: Panel-Kanal-Picker; `DiscordEmojiSelect` in Kategorie-Erstellung und -Bearbeitung - Emoji-Zod max 80 (Custom-Emoji-Format) ### Manuell testen - [ ] WebUI: Kategorie(n) anlegen, Panel-Kanal setzen, Speichern → Panel mit Buttons im Kanal - [ ] Kategorie mit Emoji-Picker (Unicode + Server-Emoji) speichern → Button zeigt Emoji - [ ] Kategorie ändern/löschen → Panel-Buttons aktualisieren sich - [ ] Panel-Kanal wechseln → altes Panel weg, neues im Zielkanal ## Post-Phase – Ticket Support-Ping + Control-Panel (Status: implementiert) ### Abgeschlossen (Code) - Support-Rollen: Member-Cache nachladen (`guild.members.fetch`), bis 50 Staff in Private Threads adden - Ticket-Eröffnung pingt Support-Rollen (`allowedMentions.roles`) - Control-Panel im Ticket: Claim, Close, User-Select Add/Remove (wie TempVoice-Pattern) - Opener darf schließen; Claim/Add/Remove nur Support-Staff - Prioritäts-Dropdown im Discord-Control-Panel (`ticket:ctrl:priority`) - Robustheit: Unknown Channel (10003) beim Close nur noch debug; Unknown Member (10007) bei Add/Remove/Interactions abgefangen; Ticket-Commands nutzen `ephemeral()` flags ### Manuell testen - [ ] Ticket öffnen (Thread) → Support-Rolle wird gepingt, Staff erscheinen im Thread - [ ] Ticket öffnen (Channel) → Support-Rolle gepingt, Rolle hat Kanal-Zugriff - [ ] Control-Panel: Claim / Close / User hinzufügen / entfernen - [ ] Opener kann schließen; Nicht-Staff kann nicht claimen ## Post-Phase – Offene Tickets im WebUI (Status: implementiert) ### Abgeschlossen (Code) - Liste offener Tickets (`OPEN`/`CLAIMED`) auf der Tickets-Seite - Aktionen: In Discord öffnen, Übernehmen, Schließen (optionaler Grund), Priorität setzen - BullMQ-Job `ticketDashboardAction`; Shared Zod `TicketDashboard` / `TicketDashboardAction` - API `GET …/tickets/open`, `POST …/tickets/open/[ticketId]/action` ### Manuell testen - [ ] Ticket öffnen → erscheint in der WebUI-Liste - [ ] Claim / Priority aus Dashboard → Nachricht im Ticket-Kanal - [ ] Close aus Dashboard → Ticket verschwindet aus der Liste, Kanal/Thread wird geschlossen - [ ] Refresh-Button aktualisiert die Liste ## Post-Phase – Ticket Support-Rollen Speichern (Bugfix) ### Abgeschlossen (Code) - Root cause: Kategorie-Save warf Fehler wenn Panel-Sync scheiterte → UI „Speichern fehlgeschlagen“, obwohl `supportRoleIds` schon in Postgres lagen - `maybeSyncTicketPanel` fängt Sync-Fehler ab; Kategorie-CRUD bleibt erfolgreich - Nach Speichern: Response inkl. `supportRoleIds` in die UI übernommen - Zod: `supportRoleIds` jetzt `SnowflakeSchema[]` ### Manuell testen - [ ] Kategorie: Support-Rolle wählen → Speichern → Erfolgstoast - [ ] Seite neu laden → Rolle ist noch ausgewählt - [ ] Neues Ticket → Support-Rolle wird gepingt / in Thread aufgenommen ## Post-Phase – Ban/Kick-Reason Logging + Verification Fail Event (Status: implementiert) ### Abgeschlossen (Code) - Root cause Ban/Kick-Reason im Log: Discord sendet Reasons nicht über Gateway (`ban.reason` war immer leer) - `MEMBER_BAN` / Kick-`MEMBER_LEAVE` lesen Reason + Moderator jetzt aus dem Audit-Log - Ephemere Success-Antworten für Ban/Kick/Timeout zeigen den Grund mit - Neuer Log-Event-Typ `VERIFICATION_FAIL` (Shared + WebUI-Picker DE/EN) - Alt-Check / Account-zu-jung schreiben ins Logging (`logVerificationFail`); auch Flag-only ohne Block ### Manuell testen - [ ] `/kick` mit Reason → Log-Kanal (MEMBER_LEAVE) zeigt Reason + Moderator - [ ] `/ban` mit Reason → Log-Kanal (MEMBER_BAN) zeigt Reason + Moderator; Bot braucht „Audit-Log anzeigen“ - [ ] Logging: `VERIFICATION_FAIL` einem Kanal zuordnen - [ ] Alt-Check mit Block → User sieht Fehler-Ephemeral; Log-Kanal bekommt Embed - [ ] Alt-Check nur Flag (ohne Block) → User wird verifiziert, Log trotzdem ## Post-Phase – Tag Auto-Responder Cooldown (Status: implementiert) ### Abgeschlossen (Code) - `Tag.cooldownSeconds` (Default 15, 0 = aus) inkl. Migration `20260725170000_tag_cooldown` - Auto-Responder: kanalbezogener Redis-Cooldown, bei aktivem Cooldown stille Ignorierung (kein Spam) - `/tag run`: nutzerbezogener Cooldown mit ephemeral Hinweis - Dashboard Tags: Cooldown-Feld (DE/EN), Shared Zod + WebUI Persistenz ### Manuell testen - [ ] Tag mit Trigger-Wort: zweimal schnell tippen → nur erste Antwort - [ ] Cooldown im Dashboard auf 0 setzen → Bot antwortet wieder sofort - [ ] `/tag run` während Cooldown → ephemeral mit Restzeit ## Post-Phase – Server-Stats WebUI Refresh (Bugfix) ### Abgeschlossen (Code) - Root cause: Dashboard speicherte nur `StatsConfig`, ohne Discord-Kanal-Rename; Cron erst alle 10 Min. Online-Count ohne `withCounts` oft 0 (kein GuildPresences-Intent) - BullMQ-Job `statsRefreshGuild` (Queue `stats`); WebUI triggert nach Speichern bei enabled + mind. einem Kanal - Guild-Fetch mit `withCounts: true` für `approximatePresenceCount` / Member-Approx - `/stats setup|refresh` refresht nur den aktuellen Guild (nicht alle) ### Manuell testen - [ ] WebUI: Stats aktivieren, Voice-Kanäle setzen, Speichern → Kanalnamen zeigen `{count}`-Werte innerhalb weniger Sekunden - [ ] Toggle aus → Speichern → keine weiteren Renames (Cron überspringt disabled) - [ ] Online-Kanal zeigt sinnvolle Zahl (nicht dauerhaft 0) - [ ] Bot ohne „Kanäle verwalten“ → Config gespeichert; Bot-Log warnt; nach Permission-Fix erneut speichern ## Post-Phase – Scheduler Cron / Run-at Fix (Status: implementiert) ### Abgeschlossen (Code) - Cron und einmaliges „Ausführen am“ nutzen die Guild-Zeitzone (`GuildSettings.timezone`) - Cron über BullMQ `upsertJobScheduler` inkl. `tz`; Delete entfernt Job-Scheduler korrekt - Dashboard: Timing-Mode (einmalig vs. Cron), naive `datetime-local` an Server, Fehlertexte i18n - Dark Mode: `color-scheme` + Input `text-foreground` für datetime-local Lesbarkeit - Shared Helper `resolveScheduleRunAt` / `guildLocalDateTimeToUtc` - Delete: Scheduler-Kinder (`repeat:…`) nicht per `job.remove()`; Legacy-Hash-IDs via `removeJobScheduler` / Scan ### Manuell testen - [ ] WebUI einmalig: Datum/Uhrzeit in Server-Zeitzone → Nachricht zur erwarteten lokalen Zeit - [ ] WebUI Cron `0 9 * * *` bei Europe/Berlin → ca. 09:00 Berlin (nicht UTC) - [ ] Cron-Plan löschen → keine weiteren Sends - [ ] Alten Cron-Eintrag (jobId `repeat:…`) im Dashboard löschen → kein API-Error -8 - [ ] Dark Mode: Cron-Feld und datetime-local lesbar/bedienbar