Skip to content

fix(bot-discord): modal setValue crash on null profile fields and structured logging#300

Merged
gvieira18 merged 2 commits into
4.xfrom
fix/bot-discord-edit-profile-modal-and-logging
Jun 3, 2026
Merged

fix(bot-discord): modal setValue crash on null profile fields and structured logging#300
gvieira18 merged 2 commits into
4.xfrom
fix/bot-discord-edit-profile-modal-and-logging

Conversation

@gvieira18
Copy link
Copy Markdown
Member

@gvieira18 gvieira18 commented Jun 3, 2026

Summary

  • Bug fix: EditProfileCommand crashed with Discord BadRequestException (code 50035) when profile nickname or about were null in the database — setValue('') violated Discord's setMinLength() constraints. Now passes null to show placeholder instead.
  • Structured logging: Both EditProfileCommand and IntroductionCommand now log errors to a dedicated bot-discord daily channel (storage/logs/bot-discord.log, 30-day retention) with full context: discord_user_id, guild_id, user_id, tenant_id, field values, and exception. Also calls report() for external exception tracking.
  • Dependency updates: composer update and npm update.
  • Security: Bumps symfony/polyfill-intl-idn from vulnerable version to v1.38.1, fixing Dependabot alert #39 (low severity — xn-- labels with ASCII-only Punycode payloads treated as equivalent to decoded form).

Test plan

  • Unit tests for modal setValue validation (9 dataset cases including null, empty, short, valid, multibyte)
  • Unit test for bot-discord log channel configuration
  • Feature tests for EditProfileCommand persistence flow (happy path, missing profile, partial update, null→new values)
  • Feature tests for IntroductionCommand persistence flow (full introduction, missing guild, missing tenant pivot)
  • Rector, Pint, PHPStan, Pest — all green on full codebase (585 passed, 1 pre-existing skip)

Description

Fixes a crash in the Discord Edit Profile modal when profile fields (nickname, about) are null by passing null to modal setValue (so placeholders show) instead of an empty string; adds structured error logging (bot-discord daily channel) and calls report() for external tracking. Also includes dependency updates and unit/feature tests to cover the changes.

Dependencies & Requirements

  • PHP composer dependency updates (examples): laravel/framework ^13.11.2 → ^13.13.0; dedoc/scramble ^0.13.23 → ^0.13.26; filament/* → ^5.6.6; guzzlehttp/guzzle ^7.10.4 → ^7.11.0; predis/predis ^3.4.2 → ^3.5.0; spatie packages bumped; symfony/dom-crawler ^8.0.12 → ^8.1.0. Dev deps: driftingly/rector-laravel, laravel/pail, laravel/pao, laravel/sail, pestphp/pest, rector/rector (minor bumps).
  • NPM dev dependency updates (various): @emnapi/*, @tailwindcss/vite, concurrently, lint-staged, npm-check-updates, prettier, tailwindcss, tw-animate-css, vite. optionalDependencies removed.
  • Config: added config/logging.php channel "bot-discord" (daily driver, storage/logs/bot-discord.log, debug level, 30-day retention).
  • No public API or signature changes.

Contributor Summary

Contributor Lines Added Lines Removed Files Changed
gvieira18 321 36 8

Changes Summary

File Path Change Description
app-modules/bot-discord/src/SlashCommands/EditProfileCommand.php Use filled()/mb_strlen checks and pass null to modal setValue when below min length; extract modal values for logging; add structured Log::channel('bot-discord')->error(...) and report($throwable).
app-modules/bot-discord/src/SlashCommands/IntroductionCommand.php Add structured error logging to bot-discord channel and call report() on exceptions during modal handling.
app-modules/bot-discord/tests/Unit/SlashCommands/EditProfileModalValidationTest.php Add unit tests for modal setValue behavior (null/empty/short/valid/multibyte) and logging channel config.
app-modules/bot-discord/tests/Feature/SlashCommands/EditProfileCommandTest.php Add feature tests for EditProfileCommand persistence flows (happy path, missing profile, partial updates, null→new value).
app-modules/bot-discord/tests/Feature/SlashCommands/IntroductionCommandTest.php Add feature tests for IntroductionCommand persistence flows and failure cases (missing guild/tenant).
config/logging.php Add bot-discord daily log channel (storage/logs/bot-discord.log, 30 days, replace_placeholders).
composer.json Bump several production and dev PHP dependencies (minor/patch upgrades).
package.json Update various dev dependencies; remove optionalDependencies section.

gvieira18 added 2 commits June 2, 2026 21:08
…ured logging, and dependency updates

EditProfileCommand crashed with Discord BadRequestException when profile
nickname/about were null — setValue('') violated setMinLength constraints.
Both commands now log errors to a dedicated bot-discord daily channel with
structured context (discord_user_id, guild_id, fields, exception) and
report to the exception handler. Dependencies updated via composer/npm.
@gvieira18 gvieira18 requested a review from a team June 3, 2026 00:14
Copy link
Copy Markdown
Member

@Clintonrocha98 Clintonrocha98 left a comment

Choose a reason for hiding this comment

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

LGTM!

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 3, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 6900da54-ba20-4090-bc7b-e20226f60865

📥 Commits

Reviewing files that changed from the base of the PR and between 109445d and 74c4a72.

📒 Files selected for processing (1)
  • app-modules/bot-discord/src/SlashCommands/EditProfileCommand.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • app-modules/bot-discord/src/SlashCommands/EditProfileCommand.php

📝 Walkthrough

Walkthrough

This PR adds a dedicated bot-discord logging channel and changes IntroductionCommand and EditProfileCommand to emit structured error logs to that channel (including discord/guild/user/tenant ids and submitted modal fields). EditProfileCommand now derives modal defaults from the current user/profile and sets TextInput values to null when inputs don't meet minimum multibyte-length requirements; submitted modal values are extracted before persistence to include in error context. The PR also adds feature tests for EditProfile and Introduction flows, unit tests for modal validation and logging config, and updates composer and package.json dependency constraints.

Suggested reviewers

  • danielhe4rt
  • davicbtoliveira
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.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 captures the two main changes: fixing a crash when profile fields are null (modal setValue) and adding structured logging (bot-discord channel).
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.


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.

Copy link
Copy Markdown

@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

🧹 Nitpick comments (4)
app-modules/bot-discord/tests/Feature/SlashCommands/EditProfileCommandTest.php (1)

36-114: 🏗️ Heavy lift

These tests don't exercise EditProfileCommand.

Despite the suite name, none of these cases invoke EditProfileCommand::handle() or persistData(). They replicate the persistence steps by calling UpsertProfile/User::update and Eloquent queries directly, so the actual command logic — including the null→placeholder modal fix and the new structured error-logging/report() path — remains uncovered. The case on Lines 65-73 in particular just asserts that firstOrFail() throws ModelNotFoundException, which tests Eloquent rather than the command.

Consider driving the command (or at least persistData) with mocked interaction/components so the behavior this PR changes is actually validated.

🤖 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
`@app-modules/bot-discord/tests/Feature/SlashCommands/EditProfileCommandTest.php`
around lines 36 - 114, The tests currently exercise UpsertProfile and direct
Eloquent updates but never call EditProfileCommand::handle() or its
persistData() method, so update the test cases to invoke EditProfileCommand (or
at minimum call persistData on an EditProfileCommand instance) with appropriate
mocked interaction/context (including modal inputs and the null→placeholder
modal behavior) and assert on the structured error/report path and logging
behavior; ensure one test replaces the ModelNotFoundException-only case by
constructing the command with a missing profile and asserting the
command/reporting behavior rather than letting firstOrFail() throw, and keep
existing expectations about profile fields while using UpsertProfile only
indirectly via the command invocation.
app-modules/bot-discord/tests/Unit/SlashCommands/EditProfileModalValidationTest.php (1)

6-19: ⚡ Quick win

This test validates an inlined copy of the logic, not the production code.

The expression on Line 7 is a duplicate of the setValue(...) ternaries in EditProfileCommand, so this test passes regardless of whether the real command changes. Note the production name field actually uses mb_strlen((string) $name) >= 2 (a cast) rather than the $value !== null && ... guard asserted here, so the two are already not identical. Consider asserting against the command's modal output (or extracting the guard into a shared helper that both the command and the test call) so the test exercises real behavior.

🤖 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
`@app-modules/bot-discord/tests/Unit/SlashCommands/EditProfileModalValidationTest.php`
around lines 6 - 19, The test in EditProfileModalValidationTest.php duplicates
the inlined ternary instead of exercising the real guard used by
EditProfileCommand::setValue(...) for the name field; replace the duplicated
logic by either invoking the production validation (call the command's modal
handling or setValue(...) on an EditProfileCommand instance and assert its modal
output) or extract the guard into a shared helper (e.g.,
validateMinLength(string|null $value, int $min): ?string) and use that helper
from both EditProfileCommand and this test so the test verifies real behavior
rather than a copy.
app-modules/bot-discord/tests/Feature/SlashCommands/IntroductionCommandTest.php (1)

17-31: 💤 Low value

Consider extracting hard-coded external_account_id to a constant.

The external_account_id '540204204242206721' is used in the helper (line 23) and later referenced in tests (lines 39, 89). Extracting it to a constant would improve maintainability and make the relationship explicit.

♻️ Proposed refactor
+const TENANT_GUILD_ID = '540204204242206721';
+
 function createIntroductionScenario(): array
 {
     $tenant = Tenant::factory()->create();
 
     $tenant->providers()->create([
         'provider' => IdentityProvider::Discord->value,
-        'external_account_id' => '540204204242206721',
+        'external_account_id' => TENANT_GUILD_ID,
         'tenant_id' => $tenant->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
`@app-modules/bot-discord/tests/Feature/SlashCommands/IntroductionCommandTest.php`
around lines 17 - 31, Extract the hard-coded external_account_id string into a
single constant so tests and helpers share one source of truth: add a constant
(e.g., EXTERNAL_ACCOUNT_ID or DISCORD_EXTERNAL_ACCOUNT_ID) and replace the
literal '540204204242206721' in createIntroductionScenario() and any test
assertions that reference that value; update references in the test class to use
the new constant (referencing createIntroductionScenario and any test methods
that assert or reuse the external_account_id).
app-modules/bot-discord/src/SlashCommands/IntroductionCommand.php (1)

103-105: 💤 Low value

Consider consistency with field extraction in persistData().

The logging code uses null-safe operators (?->value) when extracting modal fields, but the persistData() method (lines 139-141) extracts the same fields without null-safety. If modal components can be missing, persistData() would also crash. If they're guaranteed to be present (due to Discord validation), the null-safe operators here may be unnecessarily defensive.

For consistency, either:

  • Add null-safe operators to persistData() field extraction
  • Remove them from logging if components are guaranteed present
🤖 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 `@app-modules/bot-discord/src/SlashCommands/IntroductionCommand.php` around
lines 103 - 105, Logging extracts modal fields using null-safe operators
(?->value) but persistData() reads the same components without null-safety;
update persistData() to mirror the logging extraction by using the null-safe
operator when accessing components in persistData() (e.g., the same component
keys 'custom_id', 'name'/'nickname'/'about') and ensure you handle possible
nulls (returning null or a sensible default) before persisting to avoid crashes
if a component is missing.
🤖 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 `@app-modules/bot-discord/src/SlashCommands/EditProfileCommand.php`:
- Around line 160-167: The log in EditProfileCommand currently writes raw user
content ($name, $nickname, $about) and identifiers via
Log::channel('bot-discord')->error; change it to avoid persisting sensitive
free-text by removing the raw values and instead log only identifiers
(interaction->user->id, interaction->guild_id, memberProvider->user->id,
memberProvider->tenant_id) plus non-sensitive metadata such as presence booleans
or lengths (e.g. 'name_present' => boolval($name), 'name_length' =>
strlen($name) ?: 0, 'about_present' => boolval($about), 'about_length' =>
strlen($about) ?: 0) or a redacted placeholder for 'about'; ensure the
'exception' is still included for debugging and update the Log::channel call to
reflect these changed fields.
- Around line 80-82: The code reads $this->memberProvider?->user?->id earlier
but later directly dereferences $this->memberProvider->user->name, risking a
null dereference; update the assignments for $name, $nickname, and $about to use
null-safe access (e.g. $this->memberProvider?->user?->name and
$profile?->nickname / $profile?->about as appropriate) and/or provide safe
fallbacks so EditProfileCommand does not access properties on a null
memberProvider or user.

In `@package.json`:
- Around line 19-20: Verify whether the explicit pins for `@emnapi/core` and
`@emnapi/runtime` (both "1.10.0") are required: if they are not needed, remove
them from devDependencies so upstream tooling/hoisting provides them
transitively; if they are required for Vite/Tailwind WASI/wasm binding
compatibility (used by `@rolldown/binding-wasm32-wasi` and
`@tailwindcss/oxide-wasm32-wasi`), keep the entries but document the reason — add
a short rationale to the PR description and add a top-level JSON field in
package.json (e.g., "x-pinnedDependencies": { "`@emnapi/core`": "Pinned for WASI
wasm bindings required by `@rolldown/`@tailwindcss", "`@emnapi/runtime`": "Pinned
for WASI wasm bindings" }) so the intent is discoverable; update the commit
message to reference these packages by name.

---

Nitpick comments:
In `@app-modules/bot-discord/src/SlashCommands/IntroductionCommand.php`:
- Around line 103-105: Logging extracts modal fields using null-safe operators
(?->value) but persistData() reads the same components without null-safety;
update persistData() to mirror the logging extraction by using the null-safe
operator when accessing components in persistData() (e.g., the same component
keys 'custom_id', 'name'/'nickname'/'about') and ensure you handle possible
nulls (returning null or a sensible default) before persisting to avoid crashes
if a component is missing.

In
`@app-modules/bot-discord/tests/Feature/SlashCommands/EditProfileCommandTest.php`:
- Around line 36-114: The tests currently exercise UpsertProfile and direct
Eloquent updates but never call EditProfileCommand::handle() or its
persistData() method, so update the test cases to invoke EditProfileCommand (or
at minimum call persistData on an EditProfileCommand instance) with appropriate
mocked interaction/context (including modal inputs and the null→placeholder
modal behavior) and assert on the structured error/report path and logging
behavior; ensure one test replaces the ModelNotFoundException-only case by
constructing the command with a missing profile and asserting the
command/reporting behavior rather than letting firstOrFail() throw, and keep
existing expectations about profile fields while using UpsertProfile only
indirectly via the command invocation.

In
`@app-modules/bot-discord/tests/Feature/SlashCommands/IntroductionCommandTest.php`:
- Around line 17-31: Extract the hard-coded external_account_id string into a
single constant so tests and helpers share one source of truth: add a constant
(e.g., EXTERNAL_ACCOUNT_ID or DISCORD_EXTERNAL_ACCOUNT_ID) and replace the
literal '540204204242206721' in createIntroductionScenario() and any test
assertions that reference that value; update references in the test class to use
the new constant (referencing createIntroductionScenario and any test methods
that assert or reuse the external_account_id).

In
`@app-modules/bot-discord/tests/Unit/SlashCommands/EditProfileModalValidationTest.php`:
- Around line 6-19: The test in EditProfileModalValidationTest.php duplicates
the inlined ternary instead of exercising the real guard used by
EditProfileCommand::setValue(...) for the name field; replace the duplicated
logic by either invoking the production validation (call the command's modal
handling or setValue(...) on an EditProfileCommand instance and assert its modal
output) or extract the guard into a shared helper (e.g.,
validateMinLength(string|null $value, int $min): ?string) and use that helper
from both EditProfileCommand and this test so the test verifies real behavior
rather than a copy.
🪄 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: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro

Run ID: a82b7017-70d5-4fa4-8679-c85c0a100b8d

📥 Commits

Reviewing files that changed from the base of the PR and between c5c45e0 and 109445d.

⛔ Files ignored due to path filters (2)
  • composer.lock is excluded by !**/*.lock
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • app-modules/bot-discord/src/SlashCommands/EditProfileCommand.php
  • app-modules/bot-discord/src/SlashCommands/IntroductionCommand.php
  • app-modules/bot-discord/tests/Feature/SlashCommands/EditProfileCommandTest.php
  • app-modules/bot-discord/tests/Feature/SlashCommands/IntroductionCommandTest.php
  • app-modules/bot-discord/tests/Unit/SlashCommands/EditProfileModalValidationTest.php
  • composer.json
  • config/logging.php
  • package.json

Comment thread app-modules/bot-discord/src/SlashCommands/EditProfileCommand.php
Comment thread app-modules/bot-discord/src/SlashCommands/EditProfileCommand.php
Comment thread package.json
Copy link
Copy Markdown
Contributor

@thalesmengue thalesmengue left a comment

Choose a reason for hiding this comment

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

lindo código usb

@gvieira18 gvieira18 merged commit 290215b into 4.x Jun 3, 2026
6 checks passed
@gvieira18 gvieira18 deleted the fix/bot-discord-edit-profile-modal-and-logging branch June 3, 2026 00:19
@gvieira18 gvieira18 self-assigned this Jun 3, 2026
gvieira18 added a commit that referenced this pull request Jun 3, 2026
## Summary

- **Guild guard**: Added `maybeHandle()` override in
`AbstractSlashCommand` that rejects interactions without `guild_id` (DM
context), responding with an ephemeral message
- **Unified hierarchy**: All 6 slash commands that extended Laracord's
`SlashCommand` directly now extend `AbstractSlashCommand`, so every
command gets the guild guard
- **Profile::ensureExists()**: Extracted `firstOrCreate` logic into a
static method on `Profile`, replacing both the `TenantUserObserver`
inline call and `IntroductionCommand`'s `firstOrFail()` — fixes
`ModelNotFoundException` for users whose TenantUser pivot predates the
observer

## Root cause

Slash commands were registered globally (no `$guild` property), making
them available in DMs where `$interaction->guild_id` and
`$interaction->member` are null. Additionally, `syncWithoutDetaching()`
only fires the `created` pivot event for **new** records — users who
already had a TenantUser pivot but no Profile (created before the
observer existed) hit `firstOrFail()` crash.

## Test plan

- [x] `php artisan test --compact --filter=IntroductionCommand` — 3
tests pass
- [x] `php artisan test --compact --filter=EditProfileCommand` — 4 tests
pass
- [x] `vendor/bin/pint --dirty` — clean
- [x] `vendor/bin/phpstan analyse
app-modules/bot-discord/src/SlashCommands/` — 0 errors
- [ ] Verify `/apresentar` works for users with existing TenantUser
pivot but no Profile (e.g. andredss, garreiz)
- [ ] Verify slash commands are rejected gracefully if somehow invoked
from DM

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Description
Guards Discord slash commands from DM (guild-less) invocations by adding
a guild check in AbstractSlashCommand::maybeHandle(), updates six slash
commands to extend the new base, and adds Profile::ensureExists() to
avoid ModelNotFoundException for users whose TenantUser pivot predates
the Profile observer.

## References
- #302
- #300

## Dependencies & Requirements
- No new dependencies or environment/configuration changes required.

## Contributor Summary
| Contributor | Lines Added | Lines Removed | Files Changed |
|---|---:|---:|---:|
| gvieira18 | 35 | 22 | 9 |

## Changes Summary
| File Path | Change Description |
|---|---|
| app-modules/bot-discord/src/SlashCommands/AbstractSlashCommand.php |
Added public override maybeHandle() to reject guild-less (DM)
interactions with an ephemeral Portuguese message. |
| app-modules/bot-discord/src/SlashCommands/CargoDelasCommand.php |
Switched base class to AbstractSlashCommand (inheritance change). |
| app-modules/bot-discord/src/SlashCommands/CodeCommand.php | Switched
base class to AbstractSlashCommand (inheritance change). |
| app-modules/bot-discord/src/SlashCommands/DontAskCommand.php |
Switched base class to AbstractSlashCommand (inheritance change). |
| app-modules/bot-discord/src/SlashCommands/DynamicVoiceCommand.php |
Switched base class to AbstractSlashCommand (inheritance change). |
|
app-modules/bot-discord/src/SlashCommands/EditVoiceChannelLimitCommand.php
| Switched base class to AbstractSlashCommand (inheritance change). |
| app-modules/bot-discord/src/SlashCommands/IntroductionCommand.php |
Switched base class to AbstractSlashCommand and replaced firstOrFail()
profile lookup with Profile::ensureExists(). |
| app-modules/identity/src/Tenant/Observers/TenantUserObserver.php |
Replaced inline firstOrCreate with Profile::ensureExists() in created()
observer. |
| app-modules/profile/src/Models/Profile.php | Added public static
ensureExists(string|Stringable $userId, string|Stringable $tenantId):
self to create-or-return Profile safely. |
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
@coderabbitai coderabbitai Bot mentioned this pull request Jun 5, 2026
5 tasks
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.

3 participants