Skip to content

feat: /showcase project showcase with upvotes (#50)#113

Merged
BillChirico merged 5 commits intomainfrom
feat/showcase-command
Feb 27, 2026
Merged

feat: /showcase project showcase with upvotes (#50)#113
BillChirico merged 5 commits intomainfrom
feat/showcase-command

Conversation

@BillChirico
Copy link
Collaborator

Summary

Implements a /showcase command system for community members to submit, browse, and upvote projects.

What's included

  • Migration (migrations/009_showcases.cjs): showcases and showcase_votes tables with indexes
  • /showcase submit: Modal with 5 fields (name, description, tech stack, repo URL, live URL) — saves to DB and posts embed with upvote button
  • /showcase browse [tag]: Paginated list (5/page), optional tech stack filter
  • /showcase top: Top 10 most upvoted projects
  • /showcase view <id>: Full project details with upvote button
  • Upvote system: Toggle upvotes via persistent button (showcase_upvote_<id>), prevents self-upvote, updates count in real-time
  • Events integration: registerShowcaseButtonHandler and registerShowcaseModalHandler following poll pattern
  • Config: showcase.enabled toggle, showcase: everyone permission, showcase in SAFE_CONFIG_KEYS
  • Dashboard: Showcase toggle in Community Features card
  • Tests: 37 tests covering all subcommands, upvote toggle, self-upvote prevention, disabled config, no guild

Test Results

Test Files: 97 passed (97)
     Tests: 2070 passed | 1 skipped (2071)

Lint

Checked 201 files in 112ms. No fixes applied.

Closes #50

Copilot AI review requested due to automatic review settings February 27, 2026 20:28
@claude
Copy link

claude bot commented Feb 27, 2026

Claude encountered an error —— View job


I'll analyze this and get back to you.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 27, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Project Showcase feature: submit projects with descriptions, tech stack, repo/live links, and view by ID.
    • /showcase command supports submit, browse (tag + pagination), top, and view; upvote projects and see counts.
    • UI: Project embeds with upvote buttons and paginated lists.
  • Configuration

    • Admin dashboard toggle to enable/disable Project Showcase per server.
  • Tests

    • Comprehensive tests covering submission, browsing, viewing, and upvote flows.

Walkthrough

Adds a Project Showcase feature: new /showcase command with submit/browse/top/view, DB migrations for showcases and votes, event handlers for modal/button interactions, dashboard toggle and API allowlist update, and comprehensive tests and UI/embed helpers.

Changes

Cohort / File(s) Summary
Config
config.json
Added top-level showcase object (enabled: false) and permissions.allowedCommands.showcase.
Database Migration
migrations/009_showcases.cjs
New migration creating showcases and showcase_votes tables, indices, FK with ON DELETE CASCADE, and down migration.
API Allowlist
src/api/utils/configAllowlist.js
Added 'showcase' to SAFE_CONFIG_KEYS to expose the new top-level config via the API.
Command Implementation
src/commands/showcase.js
New /showcase slash command (submit, browse, top, view); embed and upvote UI builders; modal handling; DB persistence and queries; upvote toggle with transactional DB updates and safety checks.
Event Wiring
src/modules/events.js
Added registerShowcaseButtonHandler and registerShowcaseModalHandler, wired into registerEventHandlers, delegating interactions to showcase handlers with error handling.
Web Dashboard
web/src/components/dashboard/config-editor.tsx
Recognizes showcase in guild config type guard and added "Project Showcase" toggle bound to draftConfig.showcase?.enabled.
Tests
tests/commands/showcase.test.js
Comprehensive tests for command flows, modal submissions, upvote transactions, UI updates, and edge cases with mocked DB/client/logger/utilities.

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: /showcase project showcase with upvotes (#50)' clearly summarizes the main change—implementing a showcase feature with upvote functionality for community projects.
Description check ✅ Passed The description comprehensively covers the changeset including migrations, all subcommands, upvote system, events integration, config, dashboard updates, and test results.
Linked Issues check ✅ Passed All objectives from issue #50 are met: /showcase submit with modal, showcase embed with upvotes, upvote tracking preventing self-votes, browse with tag filtering, top leaderboard, and DB persistence.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the showcase feature as specified in issue #50; no out-of-scope modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 95.45% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/showcase-command

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@greptile-apps
Copy link

greptile-apps bot commented Feb 27, 2026

Greptile Summary

Implements a complete /showcase command system enabling community members to submit, browse, and upvote projects. The implementation includes database migrations for showcases and showcase_votes tables, a full-featured command handler with modal submission, four subcommands (submit/browse/top/view), atomic upvote toggling via PostgreSQL transactions, and comprehensive test coverage (37 tests).

Key strengths:

  • Atomic upvote operations using transactions prevent race conditions
  • URL validation restricts protocols to http:/https: only (security fix from previous feedback)
  • Config-gated feature (showcase.enabled) checked in all entry points including upvote handler
  • Self-upvote prevention with author ID checks
  • Follows existing architectural patterns (event handlers match poll implementation, uses safeReply/safeSend wrappers)
  • All database queries use parameterized statements (SQL injection safe)
  • Comprehensive input validation via Discord modal constraints

Previous review threads identified:

  • Error handling gap: if safeSend fails when posting showcase embed (line 269), the showcase record remains in DB without message_id
  • These issues remain unaddressed in current implementation but are non-critical

Confidence Score: 4/5

  • Safe to merge — well-tested feature with proper security controls and no critical issues
  • Score reflects solid implementation with comprehensive tests (37 tests, 2070 total passing) and proper security patterns (transactions, URL validation, SQL injection prevention). Previous threads identified minor error handling gaps that don't pose security or data integrity risks. All config gates, permission checks, and input validation are properly implemented.
  • No files require special attention — previous review threads adequately documented minor issues

Important Files Changed

Filename Overview
migrations/009_showcases.cjs Creates showcases and showcase_votes tables with proper indexes and foreign key constraints. Clean migration with appropriate CASCADE behavior.
src/commands/showcase.js Implements full /showcase command with submit, browse, top, view subcommands. Uses transactions for atomic upvote toggling, validates URLs for protocol safety, includes self-upvote prevention. Previous threads identified minor error handling gaps.
src/modules/events.js Registers button and modal handlers for showcase feature, following existing poll handler pattern. Includes proper error handling and database availability checks.
tests/commands/showcase.test.js Comprehensive test suite with 37 tests covering all subcommands, upvote toggling, self-upvote prevention, config gates, and edge cases.

Sequence Diagram

sequenceDiagram
    participant U as User
    participant D as Discord
    participant C as showcase.js
    participant E as events.js
    participant DB as PostgreSQL

    Note over U,DB: /showcase submit Flow
    U->>D: /showcase submit
    D->>C: execute()
    C->>C: Check config.showcase.enabled
    C->>D: showModal()
    D->>U: Display modal form
    U->>D: Submit modal
    D->>E: InteractionCreate (modal)
    E->>E: registerShowcaseModalHandler
    E->>C: handleShowcaseModalSubmit()
    C->>C: Validate URLs (http/https only)
    C->>D: deferReply (ephemeral)
    C->>DB: INSERT INTO showcases
    C->>D: safeSend (embed + button)
    C->>DB: UPDATE message_id
    C->>U: Success message

    Note over U,DB: Upvote Flow
    U->>D: Click upvote button
    D->>E: InteractionCreate (button)
    E->>E: registerShowcaseButtonHandler
    E->>C: handleShowcaseUpvote()
    C->>C: Check config.showcase.enabled
    C->>DB: SELECT showcase
    C->>C: Prevent self-upvote
    C->>DB: BEGIN transaction
    C->>DB: Check existing vote
    alt Vote exists
        C->>DB: DELETE vote + UPDATE upvotes-1
    else No vote
        C->>DB: INSERT vote + UPDATE upvotes+1
    end
    C->>DB: COMMIT
    C->>U: Ephemeral confirmation
    C->>D: Update button count

    Note over U,DB: Browse Flow
    U->>D: /showcase browse [tag] [page]
    D->>C: execute() -> handleBrowse()
    C->>D: deferReply
    C->>DB: SELECT COUNT with tag filter
    C->>DB: SELECT showcases LIMIT/OFFSET
    C->>D: Paginated embed
    D->>U: Display results
Loading

Last reviewed commit: c3ad323

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

7 files reviewed, 6 comments

Edit Code Review Agent Settings | Greptile

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@migrations/009_showcases.cjs`:
- Around line 31-38: Add a non-unique index on the showcase_votes.showcase_id
column to speed queries that filter or aggregate by showcase_id; update the
migration (after CREATE TABLE for showcase_votes) to create an index (e.g.,
CREATE INDEX IF NOT EXISTS on showcase_votes(showcase_id)) so counts and lookups
by showcase_id use the index without changing the existing composite primary
key.

In `@src/commands/showcase.js`:
- Around line 512-522: The vote toggle logic in src/commands/showcase.js
performs separate queries (DELETE/INSERT on showcase_votes and UPDATE on
showcases) causing a race condition; wrap each branch (the "toggle off" branch
using DELETE + UPDATE that sets newUpvotes and the "add vote" branch around
lines handling INSERT + UPDATE) in a single database transaction using
pool.query('BEGIN') / pool.query('COMMIT') with a rollback on error, or replace
with a single atomic SQL statement that updates the showcases.upvotes based on
COUNT(*) from showcase_votes; ensure you run all queries for the given guildId,
showcaseId, userId within the same transaction and return/assign newUpvotes from
the transaction result.
- Line 229: The line using interaction.channel.send may throw if
interaction.channel is null; update the code around the const msg = await
interaction.channel.send({ embeds: [embed], components: [row] }); to first guard
that interaction.channel exists (e.g., if (!interaction.channel) ...) and use a
safe fallback such as sending via interaction.reply or fetching the channel
before sending. Locate the send call in src/commands/showcase.js and modify the
logic so it either uses interaction.reply({ embeds:[embed], components:[row] })
when channel is null, or fetches a valid channel from
interaction.client.channels.fetch(...) before calling send, and ensure the
result is assigned to the same const msg variable.
- Around line 443-447: getPool() throws when the DB pool is uninitialized so the
current if (!pool) branch is dead; either remove that unreachable check or wrap
the getPool() call in a try/catch around where it's used in the showcase command
(the code that calls getPool() in this file) and in the catch call
safeEditReply(interaction, { content: '❌ Database is not available.' }) then
return. Locate the getPool() invocation in this module (and related logic that
uses safeEditReply and interaction) and implement the chosen approach to ensure
the database-unavailable case is actually caught and handled.

In `@src/modules/events.js`:
- Line 7: The file imports handleShowcaseUpvote at the top but dynamically
imports handleShowcaseModalSubmit inside the event handler; make the pattern
consistent by importing handleShowcaseModalSubmit alongside handleShowcaseUpvote
at the top of src/modules/events.js (add handleShowcaseModalSubmit to the
top-level import from ../commands/showcase.js), remove the dynamic import of
handleShowcaseModalSubmit inside the handler (where it’s currently imported at
line ~426), and ensure the handler invocation still calls
handleShowcaseModalSubmit with the same arguments used previously.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 72f973d and 90de440.

📒 Files selected for processing (7)
  • config.json
  • migrations/009_showcases.cjs
  • src/api/utils/configAllowlist.js
  • src/commands/showcase.js
  • src/modules/events.js
  • tests/commands/showcase.test.js
  • web/src/components/dashboard/config-editor.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Greptile Review
  • GitHub Check: Agent
  • GitHub Check: claude-review
🧰 Additional context used
📓 Path-based instructions (7)
**/*.js

📄 CodeRabbit inference engine (AGENTS.md)

**/*.js: Use ESM modules only — import/export, never require()
Always use node: protocol for Node.js builtin imports (e.g., import { readFileSync } from 'node:fs')

Files:

  • src/api/utils/configAllowlist.js
  • src/modules/events.js
  • src/commands/showcase.js
  • tests/commands/showcase.test.js
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,tsx}: Always use semicolons
Use single quotes — enforced by Biome
Use 2-space indentation — enforced by Biome

Files:

  • src/api/utils/configAllowlist.js
  • src/modules/events.js
  • web/src/components/dashboard/config-editor.tsx
  • src/commands/showcase.js
  • tests/commands/showcase.test.js
src/**/*.js

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.js: NEVER use console.log, console.warn, console.error, or any console.* method in src/ files — always use Winston logger instead: import { info, warn, error } from '../logger.js'
Pass structured metadata to Winston logger: info('Message processed', { userId, channelId })
Use custom error classes from src/utils/errors.js and always log errors with context before re-throwing
Use getConfig(guildId?) from src/modules/config.js to read config; use setConfigValue(path, value, guildId?) to update at runtime
Use safeSend() utility for outgoing Discord messages to sanitize mentions and enforce allowedMentions
Use splitMessage() utility for messages exceeding Discord's 2000-character limit
Add tests for all new code with mandatory 80% coverage threshold on statements, branches, functions, and lines; run pnpm test before every commit

Files:

  • src/api/utils/configAllowlist.js
  • src/modules/events.js
  • src/commands/showcase.js
src/modules/*.js

📄 CodeRabbit inference engine (AGENTS.md)

src/modules/*.js: Register event handlers in src/modules/events.js by importing handler functions and calling client.on() with config parameter
Check config.yourModule.enabled before processing in module event handlers
Prefer per-request getConfig() pattern in new modules over reactive onConfigChange() wiring; only add onConfigChange() listeners for stateful resources that cannot re-read config on each use

Files:

  • src/modules/events.js
web/**/*.{tsx,ts}

📄 CodeRabbit inference engine (AGENTS.md)

Use next/image Image component with appropriate layout and sizing props in Next.js components

Files:

  • web/src/components/dashboard/config-editor.tsx
web/src/**/*.{tsx,ts}

📄 CodeRabbit inference engine (AGENTS.md)

Use Zustand store (zustand) for state management in React components; implement fetch-on-demand pattern in stores

Files:

  • web/src/components/dashboard/config-editor.tsx
src/commands/*.js

📄 CodeRabbit inference engine (AGENTS.md)

src/commands/*.js: Use parseDuration() from src/utils/duration.js for duration-based commands (timeout, tempban, slowmode); enforce Discord duration caps (timeouts max 28 days, slowmode max 6 hours)
Create slash commands by exporting data (SlashCommandBuilder) and execute() function from src/commands/*.js; export adminOnly = true for mod-only commands; commands are auto-discovered on startup

Files:

  • src/commands/showcase.js
🧠 Learnings (5)
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/modules/*.js : Register event handlers in src/modules/events.js by importing handler functions and calling client.on() with config parameter

Applied to files:

  • src/modules/events.js
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/**/*.js : Use safeSend() utility for outgoing Discord messages to sanitize mentions and enforce allowedMentions

Applied to files:

  • src/modules/events.js
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/commands/*mod*.js : Moderation commands must follow the shared pattern: deferReply({ ephemeral: true }), validate inputs, sendDmNotification(), execute Discord action, createCase(), sendModLogEmbed(), checkEscalation()

Applied to files:

  • src/modules/events.js
  • src/commands/showcase.js
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/db.js : Database connection requires PostgreSQL (via pg driver with raw SQL, no ORM); the bot works without a database but config persistence requires PostgreSQL

Applied to files:

  • migrations/009_showcases.cjs
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/commands/*.js : Create slash commands by exporting data (SlashCommandBuilder) and execute() function from src/commands/*.js; export adminOnly = true for mod-only commands; commands are auto-discovered on startup

Applied to files:

  • src/commands/showcase.js
  • tests/commands/showcase.test.js
🧬 Code graph analysis (3)
src/modules/events.js (2)
src/utils/safeSend.js (1)
  • safeReply (138-145)
src/commands/showcase.js (2)
  • handleShowcaseUpvote (469-554)
  • handleShowcaseModalSubmit (175-244)
src/commands/showcase.js (4)
src/utils/safeSend.js (2)
  • safeReply (138-145)
  • safeEditReply (178-185)
src/modules/config.js (2)
  • getConfig (282-313)
  • err (94-94)
src/logger.js (2)
  • info (230-232)
  • warn (237-239)
src/db.js (1)
  • getPool (142-147)
tests/commands/showcase.test.js (1)
src/commands/showcase.js (20)
  • name (194-194)
  • id (387-387)
  • rows (257-257)
  • subcommand (433-433)
  • userId (471-471)
  • buildShowcaseEmbed (67-92)
  • buildUpvoteRow (101-108)
  • embed (70-74)
  • embed (226-226)
  • embed (322-325)
  • embed (362-365)
  • embed (402-402)
  • row (227-227)
  • row (403-403)
  • execute (415-461)
  • handleShowcaseModalSubmit (175-244)
  • showcase (225-225)
  • showcase (401-401)
  • showcase (493-493)
  • handleShowcaseUpvote (469-554)
🔇 Additional comments (11)
src/api/utils/configAllowlist.js (1)

20-20: LGTM!

The addition of 'showcase' to SAFE_CONFIG_KEYS correctly enables API access to the new showcase configuration section, consistent with the existing pattern for other feature modules.

config.json (1)

153-154: LGTM!

The showcase permission and config additions follow established patterns:

  • Default permission "everyone" is appropriate for a community feature
  • Disabled by default (enabled: false) is a safe opt-in approach
  • Structure mirrors existing community features like poll, tldr, and afk

Also applies to: 169-171

web/src/components/dashboard/config-editor.tsx (2)

51-51: LGTM!

Adding 'showcase' to the knownSections array ensures the type guard properly validates showcase config objects returned from the API.


1203-1203: LGTM!

The showcase entry follows the established pattern for community feature toggles, with appropriate label and description that clearly communicates the feature's purpose.

migrations/009_showcases.cjs (1)

10-24: LGTM! Schema design is solid.

The table structure appropriately supports the showcase feature:

  • SERIAL PRIMARY KEY for auto-incrementing IDs
  • TEXT[] for flexible tech stack storage
  • TIMESTAMPTZ DEFAULT NOW() for proper timezone-aware timestamps
  • Indexes on guild_id and (guild_id, author_id) will optimize common query patterns
tests/commands/showcase.test.js (1)

1-857: LGTM! Comprehensive test coverage.

The test suite thoroughly covers:

  • Core exports and helper functions
  • All subcommand flows (submit, browse, top, view)
  • Modal submission handling with edge cases
  • Upvote toggle mechanics including self-vote prevention
  • Error states (no guild, disabled feature, missing DB)

The mock factories are well-designed and follow consistent patterns.

src/modules/events.js (2)

355-400: LGTM! Handler follows established patterns.

The registerShowcaseButtonHandler correctly:

  • Checks for button interaction type and customId prefix
  • Obtains the DB pool with proper unavailability handling
  • Delegates to the command handler
  • Provides ephemeral error feedback on failure
  • Matches the pattern from registerPollButtonHandler

402-448: LGTM! Modal handler is well-structured.

The registerShowcaseModalHandler properly handles:

  • Modal submit interaction filtering by customId
  • Database availability check with user feedback
  • Error logging with context
  • Fallback ephemeral error reply when not yet replied/deferred
src/commands/showcase.js (3)

29-57: LGTM! Well-structured slash command definition.

The SlashCommandBuilder is properly configured with:

  • Descriptive command and subcommand names
  • Appropriate option types (string for tag, integer for page/id)
  • Required flag correctly set for the view ID option

67-92: LGTM! Embed builder with proper length limits.

The buildShowcaseEmbed function correctly:

  • Uses .slice() to enforce Discord's embed field limits (256 for title, 4096 for description, 1024 for field values)
  • Conditionally adds fields only when data exists
  • Includes helpful metadata in footer with relative timestamp

469-554: LGTM on upvote logic flow (aside from transaction concern noted separately).

The handler correctly:

  • Prevents self-upvotes
  • Implements toggle behavior (vote/unvote)
  • Updates the button label with new count
  • Uses ephemeral replies for user feedback
  • Gracefully handles message edit failures

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Implements a new /showcase feature across the bot and dashboard, enabling community members to submit projects, browse/view listings, and toggle upvotes backed by new database tables and event handlers.

Changes:

  • Adds showcases / showcase_votes tables via a new migration.
  • Introduces /showcase command (submit via modal, browse/top/view, upvote button).
  • Wires new showcase interaction handlers into the events system and exposes the config toggle in the dashboard/API allowlist.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
web/src/components/dashboard/config-editor.tsx Adds showcase to recognized config sections and dashboard feature list.
src/commands/showcase.js New /showcase command implementation, embed/button builders, modal submit + upvote handlers.
src/modules/events.js Registers interaction handlers for showcase upvote buttons and modal submits.
src/api/utils/configAllowlist.js Allows showcase config keys to be returned/edited safely via API.
migrations/009_showcases.cjs Creates DB schema for showcases + votes.
config.json Adds showcase feature toggle and permission entry.
tests/commands/showcase.test.js Adds Vitest coverage for showcase subcommands, modal submit, and upvote toggling.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

@claude claude bot left a comment

Choose a reason for hiding this comment

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

Review Summary

Found 4 warnings and 2 nitpicks across the showcase implementation.

Issues

🟡 Warnings:

  1. Race condition in upvote toggle (src/commands/showcase.js:504-545) — The check-then-act vote flow (SELECT → INSERT/DELETE → UPDATE count) is not wrapped in a transaction, allowing concurrent clicks to corrupt the upvote count.
  2. No URL validation (src/commands/showcase.js:194-198) — User-supplied repo/live URLs are stored without validating they are well-formed http(s) URLs.
  3. interaction.channel can be null (src/commands/showcase.js:229) — After a bot restart, the channel may not be cached, causing an unhandled TypeError.
  4. AGENTS.md / README.md not updated (migrations/009_showcases.cjs) — Key Files table, Database Tables section, and README command list need updating per project conventions.

🔵 Nitpicks:
5. parseInt on customId can produce NaN (src/commands/showcase.js:470) — Malformed button customIds pass NaN to SQL queries.
6. Discord timestamp in embed footer won't render (src/commands/showcase.js:74) — <t:...:R> only works in message content, not embed footers.

🟡 Structural note:
7. Inconsistent import pattern in events.js (src/modules/events.js:7) — handleShowcaseUpvote is statically imported from a command file while handleShowcaseModalSubmit uses dynamic import. Consider a dedicated src/modules/showcaseHandler.js to match the poll pattern.

coderabbitai[bot]
coderabbitai bot previously approved these changes Feb 27, 2026
Copy link

@claude claude bot left a comment

Choose a reason for hiding this comment

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

Review Summary — 4 warnings, 3 nitpicks

🟡 Warnings

  1. src/commands/showcase.js:483-487getPool() throw uncaught + dead codegetPool() throws when uninitialized but is called outside the try/catch block. The if (!pool) guard is unreachable dead code.

  2. src/commands/showcase.js:34-42 — URL validation accepts any protocolisValidUrl() accepts javascript:, data:, file: etc. Should restrict to http:/https:.

  3. src/modules/events.js:434-443 — Modal error handler ignores deferred state — If handleShowcaseModalSubmit defers then throws, no error response is sent. User sees "thinking..." until Discord times out.

  4. migrations/009_showcases.cjs — AGENTS.md not updated — Per project conventions, Database Tables and Key Files sections need new entries.

🔵 Nitpicks

  1. src/modules/events.js:366-377, 413-424 — Dead null checks on getPool() — Same dead if (!pool) pattern in both event handlers.

  2. tests/commands/showcase.test.js — Missing test coverage — No tests for: interaction.channel null guard, invalid URL rejection, NaN showcaseId guard.

Prompt to fix all issues
Fix these issues on branch feat/showcase-command in VolvoxLLC/volvox-bot:

1. In src/commands/showcase.js around lines 483-487, getPool() throws when uninitialized (never returns falsy), so `if (!pool)` is dead code and the throw is uncaught. Wrap getPool() in try/catch:
   let pool;
   try { pool = getPool(); }
   catch { await safeEditReply(interaction, { content: '❌ Database is not available.' }); return; }

2. In src/commands/showcase.js lines 34-42, isValidUrl() accepts any URL protocol. Add protocol check:
   const parsed = new URL(str);
   return parsed.protocol === 'https:' || parsed.protocol === 'http:';

3. In src/modules/events.js lines 434-443, the modal error handler only handles !replied && !deferred. Add else branch for deferred case using safeEditReply to send error when interaction.deferred && !interaction.replied.

4. Update AGENTS.md: add showcases and showcase_votes to Database Tables section (lines 136-143), add src/commands/showcase.js to Key Files table.

5. In src/modules/events.js lines 366-377 and 413-424, wrap (await import('../db.js')).getPool() in try/catch instead of the dead if (!pool) check.

6. In tests/commands/showcase.test.js, add tests for: (a) modal submit with interaction.channel = null, (b) modal submit with invalid URL (e.g. 'not-a-url'), (c) upvote with malformed customId like 'showcase_upvote_abc'.

Copilot AI review requested due to automatic review settings February 27, 2026 20:48
Copy link

@claude claude bot left a comment

Choose a reason for hiding this comment

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

3 warnings and 2 nitpicks found. The core showcase logic is solid — URL validation, atomic transactions, null guards, and NaN checks were all well-addressed in the fix commits. Remaining issues are in the event handler wiring and documentation.

🟡 Warnings:

  1. src/modules/events.js:366-377, 413-424getPool() throws when uninitialized but the call is outside any try/catch. The if (!pool) checks are dead code. Users get no error response when DB is down.
  2. migrations/009_showcases.cjs — AGENTS.md Key Files table and Database Tables section not updated per project conventions.

🔵 Nitpicks:
3. src/modules/events.js:428-432 — Modal error handler missing ephemeral: true on safeReply path and no try/catch around reply itself (inconsistent with button handler pattern).
4. tests/commands/showcase.test.js — Missing test coverage for new guard clauses (channel null, invalid URLs, NaN showcaseId).

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 8 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

getPool() throws when DB is uninitialized (never returns null), so the
null-check guard was unreachable and uninitialized-DB errors would
propagate uncaught. Replace with try/catch in both
registerShowcaseButtonHandler and registerShowcaseModalHandler.

Resolves: PRRT_kwDORICdSM5xQ6gA, PRRT_kwDORICdSM5xQ6hV
@BillChirico
Copy link
Collaborator Author

🛡️ PR Patrol — Code Fixed

Fixed: src/modules/events.jsgetPool() dead-code guard in both showcase event handlers.

getPool() throws when the DB is uninitialized (it never returns null), so the if (!pool) null-check after the call was unreachable. Uninitialized-DB errors would propagate uncaught past both registerShowcaseButtonHandler and registerShowcaseModalHandler.

Replaced with try/catch blocks so DB errors are caught and surfaced to the user as an ephemeral error reply — consistent with the rest of the error-handling pattern in these handlers.

All 2070 tests ✅ | biome clean ✅

Copy link

@claude claude bot left a comment

Choose a reason for hiding this comment

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

Review Summary — 4 warnings, 3 nitpicks

The core showcase feature is well-implemented — URL validation, atomic transactions, null guards, and NaN checks were all addressed in the fix commits. Remaining issues are in error handling consistency, a missing config gate, and documentation.

🟡 Warnings

  1. src/commands/showcase.js:515 — Uses raw interaction.reply({ flags: 64 }) instead of safeReply with ephemeral: true, bypassing safeSend sanitization
  2. src/commands/showcase.js:520handleShowcaseUpvote missing showcase.enabled config check — upvotes work even when feature is disabled (unlike execute() and handleShowcaseModalSubmit which both check)
  3. src/commands/showcase.js:269-272 — If safeSend throws after INSERT, orphaned DB record persists without message_id
  4. src/modules/events.js:432-436 — Modal error handler missing ephemeral: true on safeReply path and no try/catch around reply (inconsistent with button handler at lines 390-398)

🟡 Documentation

  1. AGENTS.md & README.md not updated — Missing Key Files entry, Database Tables entries, and README Features entry per project conventions

🔵 Nitpicks

  1. migrations/009_showcases.cjs:38 — Missing index on showcase_votes(showcase_id)
  2. tests/commands/showcase.test.js — No tests for channel null guard, invalid URL rejection, or NaN showcaseId guard
Prompt to fix all issues
Fix these issues on branch feat/showcase-command in VolvoxLLC/volvox-bot:

1. In src/commands/showcase.js line 515, replace `await interaction.reply({ content: '❌ Invalid showcase ID.', flags: 64 });` with `await safeReply(interaction, { content: '❌ Invalid showcase ID.', ephemeral: true });`

2. In src/commands/showcase.js, add a showcase.enabled config check at the top of handleShowcaseUpvote (after extracting guildId around line 520):
   const guildConfig = getConfig(guildId);
   if (guildConfig.showcase?.enabled === false) {
     await safeReply(interaction, { content: '❌ The showcase feature is disabled in this server.', ephemeral: true });
     return;
   }

3. In src/commands/showcase.js lines 269-272, wrap the safeSend + UPDATE in try/catch. On failure, DELETE the orphaned showcase record and reply with error.

4. In src/modules/events.js lines 432-436, replace the modal error handler with:
   } catch (err) {
     logError('Showcase modal error', { error: err.message });
     try {
       if (interaction.deferred || interaction.replied) {
         await safeEditReply(interaction, { content: '❌ Something went wrong.' });
       } else {
         await safeReply(interaction, { content: '❌ Something went wrong.', ephemeral: true });
       }
     } catch {
       // Ignore — we tried
     }
   }

5. Update AGENTS.md: add `src/commands/showcase.js` to Key Files table (around line 64), add `showcases` and `showcase_votes` to Database Tables section (around line 143).

6. Update README.md: add a showcase feature entry in the Features section (around line 18), e.g.:
   - **🏆 Project Showcase** — Submit, browse, and upvote community projects with `/showcase`. Includes tech stack filtering and a top-10 leaderboard.

7. In migrations/009_showcases.cjs after line 38, add:
   pgm.sql('CREATE INDEX IF NOT EXISTS idx_showcase_votes_showcase ON showcase_votes(showcase_id)');

8. In tests/commands/showcase.test.js, add tests for:
   (a) handleShowcaseModalSubmit with interaction.channel = null — should editReply with error
   (b) handleShowcaseModalSubmit with invalid URL like 'not-a-url' — should reply with URL error
   (c) handleShowcaseUpvote with malformed customId 'showcase_upvote_abc' — should reply with invalid ID error

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@src/commands/showcase.js`:
- Around line 244-273: The code inserts a showcases row before verifying the
channel and before safeSend succeeds, which can create orphaned DB records;
update the flow in the function that calls pool.query(...) for the INSERT (the
block that creates showcase, calls buildShowcaseEmbed and buildUpvoteRow and
uses safeSend/safeEditReply) so that you confirm interaction.channel exists and
safeSend completes successfully before persisting the showcase row (or
alternatively perform the INSERT inside a transaction and only COMMIT after
safeSend, rolling back or deleting the inserted row on send failure), and ensure
you set message_id via the UPDATE to the same showcase.id only after a
successful send.
- Around line 514-517: The invalid showcase ID branch currently calls
interaction.reply directly; replace that call with the project's safe send
wrapper (e.g., safeReply or safeSend used elsewhere in this file) to ensure
mentions are sanitized and allowedMentions enforced. Locate the branch that
checks Number.isNaN(showcaseId) and swap the interaction.reply(...) for the
equivalent await safeReply(interaction, { content: '❌ Invalid showcase ID.',
flags: 64 }) (or safeSend with the same options) so behavior and ephemeral flag
remain unchanged.

In `@src/modules/events.js`:
- Around line 361-365: In registerShowcaseButtonHandler (the
Events.InteractionCreate handler) gate processing by calling
getConfig(interaction.guildId) at the top of the button branch and check the
showcase feature flag (e.g., config.showcase.enabled); if the flag is false,
immediately reply ephemerally (disabled message) and return so no DB/upvote
mutations run; otherwise continue with the existing
customId.startsWith('showcase_upvote_') logic and DB work.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 90de440 and 9dfdba7.

📒 Files selected for processing (4)
  • migrations/009_showcases.cjs
  • src/commands/showcase.js
  • src/modules/events.js
  • tests/commands/showcase.test.js
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Greptile Review
  • GitHub Check: claude-review
🧰 Additional context used
📓 Path-based instructions (5)
**/*.js

📄 CodeRabbit inference engine (AGENTS.md)

**/*.js: Use ESM modules only — import/export, never require()
Always use node: protocol for Node.js builtin imports (e.g., import { readFileSync } from 'node:fs')

Files:

  • src/commands/showcase.js
  • tests/commands/showcase.test.js
  • src/modules/events.js
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,tsx}: Always use semicolons
Use single quotes — enforced by Biome
Use 2-space indentation — enforced by Biome

Files:

  • src/commands/showcase.js
  • tests/commands/showcase.test.js
  • src/modules/events.js
src/**/*.js

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.js: NEVER use console.log, console.warn, console.error, or any console.* method in src/ files — always use Winston logger instead: import { info, warn, error } from '../logger.js'
Pass structured metadata to Winston logger: info('Message processed', { userId, channelId })
Use custom error classes from src/utils/errors.js and always log errors with context before re-throwing
Use getConfig(guildId?) from src/modules/config.js to read config; use setConfigValue(path, value, guildId?) to update at runtime
Use safeSend() utility for outgoing Discord messages to sanitize mentions and enforce allowedMentions
Use splitMessage() utility for messages exceeding Discord's 2000-character limit
Add tests for all new code with mandatory 80% coverage threshold on statements, branches, functions, and lines; run pnpm test before every commit

Files:

  • src/commands/showcase.js
  • src/modules/events.js
src/commands/*.js

📄 CodeRabbit inference engine (AGENTS.md)

src/commands/*.js: Use parseDuration() from src/utils/duration.js for duration-based commands (timeout, tempban, slowmode); enforce Discord duration caps (timeouts max 28 days, slowmode max 6 hours)
Create slash commands by exporting data (SlashCommandBuilder) and execute() function from src/commands/*.js; export adminOnly = true for mod-only commands; commands are auto-discovered on startup

Files:

  • src/commands/showcase.js
src/modules/*.js

📄 CodeRabbit inference engine (AGENTS.md)

src/modules/*.js: Register event handlers in src/modules/events.js by importing handler functions and calling client.on() with config parameter
Check config.yourModule.enabled before processing in module event handlers
Prefer per-request getConfig() pattern in new modules over reactive onConfigChange() wiring; only add onConfigChange() listeners for stateful resources that cannot re-read config on each use

Files:

  • src/modules/events.js
🧠 Learnings (8)
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/commands/*.js : Create slash commands by exporting data (SlashCommandBuilder) and execute() function from src/commands/*.js; export adminOnly = true for mod-only commands; commands are auto-discovered on startup

Applied to files:

  • src/commands/showcase.js
  • src/modules/events.js
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/commands/*mod*.js : Moderation commands must follow the shared pattern: deferReply({ ephemeral: true }), validate inputs, sendDmNotification(), execute Discord action, createCase(), sendModLogEmbed(), checkEscalation()

Applied to files:

  • src/commands/showcase.js
  • src/modules/events.js
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/modules/moderation.js : Case numbering must be per-guild sequential, assigned atomically using COALESCE(MAX(case_number), 0) + 1 in a single INSERT statement

Applied to files:

  • src/commands/showcase.js
  • migrations/009_showcases.cjs
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/**/*.js : Use safeSend() utility for outgoing Discord messages to sanitize mentions and enforce allowedMentions

Applied to files:

  • src/commands/showcase.js
  • src/modules/events.js
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/**/*.js : Add tests for all new code with mandatory 80% coverage threshold on statements, branches, functions, and lines; run pnpm test before every commit

Applied to files:

  • tests/commands/showcase.test.js
📚 Learning: 2026-02-27T16:24:15.055Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.055Z
Learning: Update AGENTS.md, README.md, CONTRIBUTING.md, .env.example, and config.json documentation whenever code changes are made (new commands, modules, env vars, or architecture changes)

Applied to files:

  • migrations/009_showcases.cjs
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/db.js : Database connection requires PostgreSQL (via pg driver with raw SQL, no ORM); the bot works without a database but config persistence requires PostgreSQL

Applied to files:

  • migrations/009_showcases.cjs
  • src/modules/events.js
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/modules/*.js : Register event handlers in src/modules/events.js by importing handler functions and calling client.on() with config parameter

Applied to files:

  • src/modules/events.js
🧬 Code graph analysis (2)
tests/commands/showcase.test.js (1)
src/commands/showcase.js (26)
  • name (213-213)
  • id (427-427)
  • rows (297-297)
  • subcommand (473-473)
  • userId (519-519)
  • data (47-75)
  • data (47-75)
  • buildShowcaseEmbed (85-111)
  • buildUpvoteRow (120-127)
  • embed (88-93)
  • embed (261-261)
  • embed (362-365)
  • embed (402-405)
  • embed (442-442)
  • row (262-262)
  • row (443-443)
  • execute (455-502)
  • handleShowcaseModalSubmit (194-284)
  • showcase (260-260)
  • showcase (441-441)
  • showcase (541-541)
  • pool (244-258)
  • pool (386-393)
  • pool (429-432)
  • pool (483-483)
  • handleShowcaseUpvote (510-617)
src/modules/events.js (2)
src/commands/showcase.js (7)
  • client (556-556)
  • pool (244-258)
  • pool (386-393)
  • pool (429-432)
  • pool (483-483)
  • handleShowcaseUpvote (510-617)
  • handleShowcaseModalSubmit (194-284)
src/utils/safeSend.js (2)
  • safeReply (138-145)
  • safeEditReply (178-185)

Copilot AI review requested due to automatic review settings February 27, 2026 21:46
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
src/commands/showcase.js (1)

244-273: ⚠️ Potential issue | 🟠 Major

Orphaned DB records when channel is unavailable.

The showcase record is inserted at line 244 before validating interaction.channel at line 264. If the channel is null, the function returns early, leaving an orphaned record in the database with no associated message.

Move the channel check before the INSERT, or wrap in a transaction that rolls back on send failure.
,

🛠️ Proposed fix: check channel before insert
   await interaction.deferReply({ ephemeral: true });

+  if (!interaction.channel) {
+    await safeEditReply(interaction, { content: '❌ Cannot post in this channel.' });
+    return;
+  }
+
   const { rows } = await pool.query(
     `INSERT INTO showcases (guild_id, author_id, name, description, tech_stack, repo_url, live_url, channel_id)
      VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
      RETURNING *`,
@@
   const showcase = rows[0];
   const embed = buildShowcaseEmbed(showcase);
   const row = buildUpvoteRow(showcase.id, 0);

-  if (!interaction.channel) {
-    await safeEditReply(interaction, { content: '❌ Cannot post in this channel.' });
-    return;
-  }
-
   const msg = await safeSend(interaction.channel, { embeds: [embed], components: [row] });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/showcase.js` around lines 244 - 273, The code inserts a showcase
via pool.query before verifying interaction.channel, which can leave orphaned DB
rows if posting fails; fix by moving the channel availability check
(interaction.channel) before the INSERT, or wrap the INSERT + safeSend + UPDATE
in a DB transaction so you can rollback on send failure (use the same pool
client for BEGIN/COMMIT/ROLLBACK around the queries), ensuring the safeSend call
and subsequent UPDATE to set message_id only run after a successful send;
reference pool.query, interaction.channel, safeSend, safeEditReply,
buildShowcaseEmbed, buildUpvoteRow, and the UPDATE that sets message_id when
applying the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/commands/showcase.js`:
- Around line 244-273: The code inserts a showcase via pool.query before
verifying interaction.channel, which can leave orphaned DB rows if posting
fails; fix by moving the channel availability check (interaction.channel) before
the INSERT, or wrap the INSERT + safeSend + UPDATE in a DB transaction so you
can rollback on send failure (use the same pool client for BEGIN/COMMIT/ROLLBACK
around the queries), ensuring the safeSend call and subsequent UPDATE to set
message_id only run after a successful send; reference pool.query,
interaction.channel, safeSend, safeEditReply, buildShowcaseEmbed,
buildUpvoteRow, and the UPDATE that sets message_id when applying the change.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9dfdba7 and c3ad323.

📒 Files selected for processing (1)
  • src/commands/showcase.js
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Greptile Review
  • GitHub Check: Agent
🧰 Additional context used
📓 Path-based instructions (4)
**/*.js

📄 CodeRabbit inference engine (AGENTS.md)

**/*.js: Use ESM modules only — import/export, never require()
Always use node: protocol for Node.js builtin imports (e.g., import { readFileSync } from 'node:fs')

Files:

  • src/commands/showcase.js
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,tsx}: Always use semicolons
Use single quotes — enforced by Biome
Use 2-space indentation — enforced by Biome

Files:

  • src/commands/showcase.js
src/**/*.js

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.js: NEVER use console.log, console.warn, console.error, or any console.* method in src/ files — always use Winston logger instead: import { info, warn, error } from '../logger.js'
Pass structured metadata to Winston logger: info('Message processed', { userId, channelId })
Use custom error classes from src/utils/errors.js and always log errors with context before re-throwing
Use getConfig(guildId?) from src/modules/config.js to read config; use setConfigValue(path, value, guildId?) to update at runtime
Use safeSend() utility for outgoing Discord messages to sanitize mentions and enforce allowedMentions
Use splitMessage() utility for messages exceeding Discord's 2000-character limit
Add tests for all new code with mandatory 80% coverage threshold on statements, branches, functions, and lines; run pnpm test before every commit

Files:

  • src/commands/showcase.js
src/commands/*.js

📄 CodeRabbit inference engine (AGENTS.md)

src/commands/*.js: Use parseDuration() from src/utils/duration.js for duration-based commands (timeout, tempban, slowmode); enforce Discord duration caps (timeouts max 28 days, slowmode max 6 hours)
Create slash commands by exporting data (SlashCommandBuilder) and execute() function from src/commands/*.js; export adminOnly = true for mod-only commands; commands are auto-discovered on startup

Files:

  • src/commands/showcase.js
🧠 Learnings (5)
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/commands/*.js : Create slash commands by exporting data (SlashCommandBuilder) and execute() function from src/commands/*.js; export adminOnly = true for mod-only commands; commands are auto-discovered on startup

Applied to files:

  • src/commands/showcase.js
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/commands/*mod*.js : Moderation commands must follow the shared pattern: deferReply({ ephemeral: true }), validate inputs, sendDmNotification(), execute Discord action, createCase(), sendModLogEmbed(), checkEscalation()

Applied to files:

  • src/commands/showcase.js
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/commands/*.js : Use parseDuration() from src/utils/duration.js for duration-based commands (timeout, tempban, slowmode); enforce Discord duration caps (timeouts max 28 days, slowmode max 6 hours)

Applied to files:

  • src/commands/showcase.js
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/modules/moderation.js : Case numbering must be per-guild sequential, assigned atomically using COALESCE(MAX(case_number), 0) + 1 in a single INSERT statement

Applied to files:

  • src/commands/showcase.js
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/**/*.js : Use safeSend() utility for outgoing Discord messages to sanitize mentions and enforce allowedMentions

Applied to files:

  • src/commands/showcase.js
🔇 Additional comments (2)
src/commands/showcase.js (2)

455-502: LGTM!

The execute function properly validates server context and feature flag, handles the modal-based submit flow separately, and wraps database access in try/catch. Error handling logs structured metadata and provides user-friendly error messages.


561-606: LGTM!

The vote toggle is now properly wrapped in a transaction with BEGIN/COMMIT/ROLLBACK, addressing the race condition concern. The client is correctly released in the finally block.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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.

Project showcase — /showcase command for members to share and discover projects

2 participants