Skip to content

feat(role-menu): replace reaction-role with button-only role-menu - #11

Merged
HyperSoWeak merged 4 commits into
mainfrom
feat/role-menu
May 26, 2026
Merged

HyperSoWeak merged 4 commits into
mainfrom
feat/role-menu

Conversation

@HyperSoWeak

@HyperSoWeak HyperSoWeak commented May 26, 2026 •

Copy link
Copy Markdown
Owner

Summary

Replaces the /reaction-role command with a new /role-menu command that supports buttons only.

Changes

New command: /role-menu

Subcommand Parameters Description
create channel content Bot sends a menu message in the given channel
add message role1-5 label1-5 Add up to 5 buttons at once (labels optional)
remove message role Remove a button by role
edit message content Edit the menu message text
delete message Delete the entire menu from DB
list — List all menus with message IDs + Discord links

Removed

  • /reaction-role command and all subcommands
  • Reaction-mode code (emoji.ts, url.ts, reaction event listeners)
  • GatewayIntentBits.GuildMessageReactions, Partials.Message, Partials.Reaction

DB migration

  • Drop emoji column from reaction_role_mappings
  • Drop button_id column (now computed on-the-fly as rr:<menuId>:<roleId>)
  • Add label TEXT column (nullable, overrides role name on button)
  • Unique key changed from (menu_id, emoji, button_id) → (menu_id, role_id)

Why buttons only

Button interactions are reliable through bot restarts. Reaction events are gateway-only and missed while the bot is offline.


🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a /role-menu command to create and manage role-assignment menus with optional per-role labels and improved button handling.
  • Refactor

    • Replaced the legacy reaction-role system with a focused role-menu implementation and updated runtime behavior for button interactions and menu management.
  • Chores

    • Updated ignore rules to exclude local worktree artifacts.

Review Change Stack

- Remove all reaction-mode code (listeners, emoji helpers, URL parser)
- Drop emoji/button_id DB columns; add label column
- New /role-menu command: create, add (≤5 roles + labels), remove, edit, delete, list
- remove uses message-id + role instead of mapping-id
- list shows message ID + Discord link for easy copy-paste
@coderabbitai

coderabbitai Bot commented May 26, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR replaces an emoji-based reaction role system with a button-based role menu system. It introduces a new /role-menu command, schema migration, service layer, and button listeners while removing the deprecated reaction-role implementation.

Changes

Role-Menu Feature Implementation

Layer / File(s) Summary
Database schema and migration
prisma/migrations/..., prisma/schema.prisma
Migration removes button_id and emoji from reaction_role_mappings, adds label, and updates uniqueness to (menuId, roleId). Prisma schema updated to match.
Role-menu service layer
src/features/role-menu/service.ts
Prisma-backed CRUD helpers for menu/mapping lookup, creation, deletion, listing, and role-mapping queries; MenuWithMappings type added.
Role-menu button listeners
src/features/role-menu/listeners.ts
Button ID construction/parsing, interaction handler that toggles mapped roles with ephemeral replies and error handling, and listener registration wiring.
Role-menu slash command
src/commands/role-menu/index.ts
Slash command with create, add, remove, edit, delete, and list subcommands; includes role validation, menu message resolution, button regeneration, and audit logging.
Command registry and category types
src/core/command/types.ts, src/commands/index.ts
Add 'role-menu' command category; replace reactionRoleCommands with roleMenuCommands in command exports.
Help text and labels
src/commands/info/help.ts
Update CATEGORY_LABELS to map 'role-menu' to 'Role Menu'.
Bootstrap and feature wiring
src/index.ts
Import role-menu listeners, remove GuildMessageReactions intent, narrow partials to channel/guild-member, and register listeners on client ready.
Infrastructure configuration
.gitignore
Add .worktrees/ to ignored paths.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🐰 Buttons dance where reactions once bloomed,
Role menus now shine in Discord's room,
Labels whisper which role to share,
Click once, the rabbit toggles with care.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main changeset: replacing the emoji/reaction-based /reaction-role command with a button-only /role-menu command, which is the central theme across all file modifications.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/role-menu

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@prisma/migrations/20260526000000_role_menu_cleanup/migration.sql`:
- Around line 2-7: Add a dedupe step before creating the unique index on
reaction_role_mappings(menu_id, role_id): identify rows having the same
(menu_id, role_id) and delete duplicates while keeping a single canonical row
per pair (for example by keeping the row with the lowest primary key/id or
earliest ctid), then proceed to CREATE UNIQUE INDEX
"reaction_role_mappings_menu_id_role_id_key". This ensures the CREATE UNIQUE
INDEX on reaction_role_mappings(menu_id, role_id) will not fail due to legacy
duplicate rows.

In `@src/commands/role-menu/index.ts`:
- Around line 178-181: In the for loop that iterates i from 1 to 5 and calls
interaction.options.getRole(`role${i}`), don't stop parsing when one optional
slot is missing: replace the break that exits the loop with continue so later
roles (e.g., role3 when role2 is absent) are still processed; keep the existing
call to ensureRoleAssignable(role, interaction) for non-null role values and
only skip to the next iteration when role is null.
- Around line 191-196: The current .catch(() => null) on the addRoleButton call
masks all DB errors; instead catch only Prisma unique-constraint P2002 and
return null for that case, rethrow any other errors. Modify the code around the
addRoleButton invocation so it awaits addRoleButton(...) inside a try/catch, and
in the catch check if err is a PrismaClientKnownRequestError and err.code ===
'P2002' then set mapping = null (to indicate "已存在"), otherwise throw err;
reference addRoleButton and ReactionRoleMapping/@@unique([menuId, roleId]) in
your change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 842a0ced-3508-4669-95cf-5fe91dfb3c4d

📥 Commits

Reviewing files that changed from the base of the PR and between d4f101e and 52e32ec.

📒 Files selected for processing (15)
  • .gitignore
  • prisma/migrations/20260526000000_role_menu_cleanup/migration.sql
  • prisma/schema.prisma
  • src/commands/index.ts
  • src/commands/info/help.ts
  • src/commands/reaction-role/index.ts
  • src/commands/role-menu/index.ts
  • src/core/command/types.ts
  • src/features/reaction-role/emoji.ts
  • src/features/reaction-role/listeners.ts
  • src/features/reaction-role/service.ts
  • src/features/reaction-role/url.ts
  • src/features/role-menu/listeners.ts
  • src/features/role-menu/service.ts
  • src/index.ts
💤 Files with no reviewable changes (5)
  • src/commands/reaction-role/index.ts
  • src/features/reaction-role/url.ts
  • src/features/reaction-role/listeners.ts
  • src/features/reaction-role/emoji.ts
  • src/features/reaction-role/service.ts

Comment on lines +2 to +7
ALTER TABLE "reaction_role_mappings" DROP COLUMN "button_id",
DROP COLUMN "emoji",
ADD COLUMN "label" TEXT;

-- CreateIndex
CREATE UNIQUE INDEX "reaction_role_mappings_menu_id_role_id_key" ON "reaction_role_mappings"("menu_id", "role_id");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard migration against pre-existing duplicate (menu_id, role_id) rows.

Line 7 can fail at deploy time if legacy data has multiple rows for the same menu/role pair (previous schema allowed that through different emoji/button values). Add a dedupe step before creating the unique index.

💡 Suggested migration hardening
 ALTER TABLE "reaction_role_mappings" DROP COLUMN "button_id",
 DROP COLUMN "emoji",
 ADD COLUMN     "label" TEXT;
 
+-- Deduplicate rows that would violate the new unique key
+WITH ranked AS (
+  SELECT id,
+         ROW_NUMBER() OVER (PARTITION BY menu_id, role_id ORDER BY id) AS rn
+  FROM "reaction_role_mappings"
+)
+DELETE FROM "reaction_role_mappings" r
+USING ranked x
+WHERE r.id = x.id
+  AND x.rn > 1;
+
 -- CreateIndex
 CREATE UNIQUE INDEX "reaction_role_mappings_menu_id_role_id_key" ON "reaction_role_mappings"("menu_id", "role_id");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ALTER TABLE "reaction_role_mappings" DROP COLUMN "button_id",
DROP COLUMN "emoji",
ADD COLUMN "label" TEXT;
-- CreateIndex
CREATE UNIQUE INDEX "reaction_role_mappings_menu_id_role_id_key" ON "reaction_role_mappings"("menu_id", "role_id");
ALTER TABLE "reaction_role_mappings" DROP COLUMN "button_id",
DROP COLUMN "emoji",
ADD COLUMN "label" TEXT;
-- Deduplicate rows that would violate the new unique key
WITH ranked AS (
SELECT id,
ROW_NUMBER() OVER (PARTITION BY menu_id, role_id ORDER BY id) AS rn
FROM "reaction_role_mappings"
)
DELETE FROM "reaction_role_mappings" r
USING ranked x
WHERE r.id = x.id
AND x.rn > 1;
-- CreateIndex
CREATE UNIQUE INDEX "reaction_role_mappings_menu_id_role_id_key" ON "reaction_role_mappings"("menu_id", "role_id");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@prisma/migrations/20260526000000_role_menu_cleanup/migration.sql` around
lines 2 - 7, Add a dedupe step before creating the unique index on
reaction_role_mappings(menu_id, role_id): identify rows having the same
(menu_id, role_id) and delete duplicates while keeping a single canonical row
per pair (for example by keeping the row with the lowest primary key/id or
earliest ctid), then proceed to CREATE UNIQUE INDEX
"reaction_role_mappings_menu_id_role_id_key". This ensures the CREATE UNIQUE
INDEX on reaction_role_mappings(menu_id, role_id) will not fail due to legacy
duplicate rows.

Comment on lines +178 to +181
for (let i = 1; i <= 5; i++) {
const role = interaction.options.getRole(`role${i}`) as Role | null;
if (!role) break;
ensureRoleAssignable(role, interaction);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Do not stop parsing at first missing optional role slot.

Line 180 uses break; this drops later provided roles (e.g., role1 + role3). Use continue instead.

💡 Suggested fix
       for (let i = 1; i <= 5; i++) {
         const role = interaction.options.getRole(`role${i}`) as Role | null;
-        if (!role) break;
+        if (!role) continue;
         ensureRoleAssignable(role, interaction);
         const label = interaction.options.getString(`label${i}`) ?? undefined;
         pairs.push({ role, label });
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (let i = 1; i <= 5; i++) {
const role = interaction.options.getRole(`role${i}`) as Role | null;
if (!role) break;
ensureRoleAssignable(role, interaction);
for (let i = 1; i <= 5; i++) {
const role = interaction.options.getRole(`role${i}`) as Role | null;
if (!role) continue;
ensureRoleAssignable(role, interaction);
const label = interaction.options.getString(`label${i}`) ?? undefined;
pairs.push({ role, label });
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/role-menu/index.ts` around lines 178 - 181, In the for loop that
iterates i from 1 to 5 and calls interaction.options.getRole(`role${i}`), don't
stop parsing when one optional slot is missing: replace the break that exits the
loop with continue so later roles (e.g., role3 when role2 is absent) are still
processed; keep the existing call to ensureRoleAssignable(role, interaction) for
non-null role values and only skip to the next iteration when role is null.

Comment on lines +191 to +196
const mapping = await addRoleButton({ menuId: menu.id, roleId: role.id, label }).catch(
() => null,
);
if (!mapping) {
lines.push(`⚠️ <@&${role.id}> 已存在,跳過`);
continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the file under review and show the relevant region
echo "=== src/commands/role-menu/index.ts (lines ~160-230) ==="
nl -ba src/commands/role-menu/index.ts | sed -n '160,230p'

# Find where addRoleButton is defined and/or imported
echo
echo "=== References to addRoleButton ==="
rg -n "addRoleButton" -S src || true

# If it's imported from another module, print that module's contents near the function
# Use rg to capture import path(s)
echo
echo "=== addRoleButton import statements (in role-menu file) ==="
rg -n "import .*addRoleButton|from .*addRoleButton" src/commands/role-menu/index.ts || true

# Try to locate addRoleButton implementation in the repo
echo
echo "=== Possible addRoleButton definition ==="
rg -n "function addRoleButton|const addRoleButton|async function addRoleButton" -S src || true

Repository: HyperSoWeak/yuzu-bot

Length of output: 182


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="src/commands/role-menu/index.ts"

echo "=== Exists? $file ==="
test -f "$file" && echo "yes" || (echo "missing"; exit 1)

echo "=== src/commands/role-menu/index.ts (lines ~160-230) ==="
# Print with line numbers using awk (nl not guaranteed)
awk 'NR>=160 && NR<=230 {printf "%d\t%s\n", NR, $0}' "$file"

echo
echo "=== References to addRoleButton ==="
rg -n "addRoleButton" -S src/commands/role-menu/index.ts || true

echo
echo "=== addRoleButton imports in role-menu file ==="
rg -n "import .*addRoleButton|from .*addRoleButton" -S "$file" || true

echo
echo "=== addRoleButton definition candidates in repo ==="
rg -n "function addRoleButton|const addRoleButton|async function addRoleButton" -S src || true

Repository: HyperSoWeak/yuzu-bot

Length of output: 3558


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== src/features/role-menu/service.ts (around addRoleButton) ==="
awk 'NR>=1 && NR<=220 {print NR "\t" $0}' src/features/role-menu/service.ts | sed -n '1,220p'

echo
echo "=== Search for Prisma client usage in service.ts ==="
rg -n "Prisma|prisma|P2002|PrismaClientKnownRequestError" src/features/role-menu/service.ts || true

echo
echo "=== Find prisma schema file(s) ==="
fd -e prisma schema . 2>/dev/null || true
fd -e prisma . 2>/dev/null || true

# Try typical location
if [ -f prisma/schema.prisma ]; then
  echo
  echo "=== prisma/schema.prisma (models mentioning role_menu / mapping) ==="
  rg -n "reaction_role_menu|role_menu|menuId|roleId|Mapping|mapping|P2002" prisma/schema.prisma || true
fi

# Also search entire repo for "P2002" occurrences
echo
echo "=== Search whole repo for P2002 usage ==="
rg -n "P2002|PrismaClientKnownRequestError" src prisma || true

Repository: HyperSoWeak/yuzu-bot

Length of output: 3734


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== src/features/achievement/service.ts (around P2002 handling) ==="
awk 'NR>=1 && NR<=120 {print NR "\t" $0}' src/features/achievement/service.ts | sed -n '1,120p'

echo
echo "=== Search for PrismaClientKnownRequestError usage across src ==="
rg -n "PrismaClientKnownRequestError|`@prisma/client`.*Prisma|from '`@prisma/client`'.*Prisma" src || true

echo
echo "=== Search for pattern (err as { code?: string }).code ==="
rg -n "\\(err as \\{ code\\?: string \\}\\)\\.code|err\\.code === 'P2002'" src || true

Repository: HyperSoWeak/yuzu-bot

Length of output: 3083


Only suppress Prisma unique-conflict (P2002) errors in role-menu add flow

In src/commands/role-menu/index.ts (lines 191-196), the .catch(() => null) on addRoleButton masks all Prisma/DB failures as “already exists”. addRoleButton uses prisma.reactionRoleMapping.create, and ReactionRoleMapping has @@unique([menuId, roleId]), so only Prisma error code P2002 for that unique constraint should map to “已存在”; rethrow other errors (consistent with the repo’s P2002 handling in src/features/achievement/service.ts).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/role-menu/index.ts` around lines 191 - 196, The current
.catch(() => null) on the addRoleButton call masks all DB errors; instead catch
only Prisma unique-constraint P2002 and return null for that case, rethrow any
other errors. Modify the code around the addRoleButton invocation so it awaits
addRoleButton(...) inside a try/catch, and in the catch check if err is a
PrismaClientKnownRequestError and err.code === 'P2002' then set mapping = null
(to indicate "已存在"), otherwise throw err; reference addRoleButton and
ReactionRoleMapping/@@unique([menuId, roleId]) in your change.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/features/role-menu/service.ts (1)

56-61: ⚡ Quick win

Use findUnique instead of findFirst for unique constraint.

Since the schema enforces a unique constraint on (menuId, roleId), use findUnique instead of findFirst for correctness, clarity, and potential performance benefits.

♻️ Proposed refactor
 export async function findMappingByRole(
   menuId: string,
   roleId: string,
 ): Promise<ReactionRoleMapping | null> {
-  return prisma.reactionRoleMapping.findFirst({ where: { menuId, roleId } });
+  return prisma.reactionRoleMapping.findUnique({ where: { menuId_roleId: { menuId, roleId } } });
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/role-menu/service.ts` around lines 56 - 61, The function
findMappingByRole should use Prisma's findUnique for the (menuId, roleId) unique
constraint: replace prisma.reactionRoleMapping.findFirst(...) in
findMappingByRole with prisma.reactionRoleMapping.findUnique(...) and pass the
composite unique key object (e.g. where: { menuId_roleId: { menuId, roleId } })
so the query uses the schema-defined unique identifier instead of findFirst.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/features/role-menu/service.ts`:
- Around line 20-26: The upsert call using only messageId can return a menu
belonging to a different guild; fix by verifying guildId before returning: first
fetch existing = await prisma.reactionRoleMenu.findUnique({ where: { messageId:
input.messageId }, include: { mappings: true } }); if existing and
existing.guildId !== input.guildId throw an authorization/validation error;
otherwise proceed to create or update (or call prisma.reactionRoleMenu.upsert as
before) and return the record. Reference the prisma.reactionRoleMenu.upsert call
and input.messageId/input.guildId in your changes.

---

Nitpick comments:
In `@src/features/role-menu/service.ts`:
- Around line 56-61: The function findMappingByRole should use Prisma's
findUnique for the (menuId, roleId) unique constraint: replace
prisma.reactionRoleMapping.findFirst(...) in findMappingByRole with
prisma.reactionRoleMapping.findUnique(...) and pass the composite unique key
object (e.g. where: { menuId_roleId: { menuId, roleId } }) so the query uses the
schema-defined unique identifier instead of findFirst.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 81497ed0-3133-42d8-9e96-94671fdf0220

📥 Commits

Reviewing files that changed from the base of the PR and between 52e32ec and fbb4fb5.

📒 Files selected for processing (1)
  • src/features/role-menu/service.ts

Comment on lines +20 to +26
return prisma.reactionRoleMenu.upsert({
where: { messageId: input.messageId },
update: {},
create: input,
include: { mappings: true },
});
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚖️ Poor tradeoff

Verify guildId after upsert to prevent cross-guild access.

The upsert uses only messageId in the where clause and update: {}, so if a menu with the same messageId but different guildId already exists, it will return that menu without updating guildId. This violates guild isolation and could allow cross-guild access if a messageId from one guild is referenced in another guild's context.

While Discord message IDs are globally unique and downstream code filters by guildId, the function's contract (accepting guildId as input) suggests it should enforce guild matching.

🛡️ Proposed fix to add guild verification
 export async function getOrCreateMenu(input: {
   guildId: string;
   channelId: string;
   messageId: string;
 }): Promise<MenuWithMappings> {
-  return prisma.reactionRoleMenu.upsert({
+  const menu = await prisma.reactionRoleMenu.upsert({
     where: { messageId: input.messageId },
     update: {},
     create: input,
     include: { mappings: true },
   });
+  if (menu.guildId !== input.guildId) {
+    throw new Error(`Message ${input.messageId} belongs to a different guild`);
+  }
+  return menu;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return prisma.reactionRoleMenu.upsert({
where: { messageId: input.messageId },
update: {},
create: input,
include: { mappings: true },
});
}
const menu = await prisma.reactionRoleMenu.upsert({
where: { messageId: input.messageId },
update: {},
create: input,
include: { mappings: true },
});
if (menu.guildId !== input.guildId) {
throw new Error(`Message ${input.messageId} belongs to a different guild`);
}
return menu;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/role-menu/service.ts` around lines 20 - 26, The upsert call
using only messageId can return a menu belonging to a different guild; fix by
verifying guildId before returning: first fetch existing = await
prisma.reactionRoleMenu.findUnique({ where: { messageId: input.messageId },
include: { mappings: true } }); if existing and existing.guildId !==
input.guildId throw an authorization/validation error; otherwise proceed to
create or update (or call prisma.reactionRoleMenu.upsert as before) and return
the record. Reference the prisma.reactionRoleMenu.upsert call and
input.messageId/input.guildId in your changes.

@HyperSoWeak
HyperSoWeak merged commit e8a7069 into main May 26, 2026
2 checks passed
@HyperSoWeak
HyperSoWeak deleted the feat/role-menu branch May 26, 2026 07:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant