Skip to content

feat: base discord bot#126

Merged
danielhe4rt merged 28 commits into
3.xfrom
feat/discord-bot
Nov 26, 2025
Merged

feat: base discord bot#126
danielhe4rt merged 28 commits into
3.xfrom
feat/discord-bot

Conversation

@danielhe4rt
Copy link
Copy Markdown
Contributor

@danielhe4rt danielhe4rt commented Nov 25, 2025

Summary by CodeRabbit

  • New Features

    • Integrated Discord bot: slash commands for introduction, edit, profile, code, dynamic voice, and utilities plus interactive buttons/modals.
    • Automated greetings and tailored welcome messages for new/returning members.
    • Artisan command to generate Discord tenant entries.
  • Chores

    • Added DISCORD_TOKEN to example env and updated ignore list to exclude internal bot data.
  • Tests

    • Several message/profile tests marked as skipped.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 25, 2025

Walkthrough

Adds a new bot-discord module (commands, slash commands, events, tasks, provider resolution), refactors message and profile flows to use resolver DTOs, updates DTOs and actions, adjusts tests to skip some cases, updates root composer and env, and adds .laracord to .gitignore.

Changes

Cohort / File(s) Summary
Repository & Env
\.gitignore, \.env.example, composer.json
Adds .laracord to gitignore, adds DISCORD_TOKEN to .env.example, updates root composer: adds he4rt/bot-discord, laracord/framework, path & VCS repositories, bumps packages, and sets minimum-stability to dev.
Bot Discord package manifest & provider
app-modules/bot-discord/composer.json, app-modules/bot-discord/src/Providers/BotDiscordServiceProvider.php
New package manifest with PSR-4 autoload and Laravel provider registration; service provider configures bot discovery (events, commands, slash commands, tasks).
Bot Discord events
app-modules/bot-discord/src/Events/...
app-modules/bot-discord/src/Events/GreetingsEvent.php, .../MessageReceivedEvent.php, .../WelcomeMember.php
Adds event handlers: GreetingsEvent (time/keyword replies), MessageReceivedEvent (filters bots, resolves tenant, persists NewMessageDTO), WelcomeMember (resolves user/character, sends themed welcome).
Slash commands & abstract base
app-modules/bot-discord/src/SlashCommands/...
.../AbstractSlashCommand.php, .../IntroductionCommand.php, .../EditProfileCommand.php, .../ProfileCommand.php, .../DynamicVoiceCommand.php, .../DontAskCommand.php, .../CodeCommand.php
Adds AbstractSlashCommand (middleware pipeline, provider/member resolution) and multiple slash commands for profile, introduction, edit, dynamic voice, dont-ask, and code, including modal handling and persist flows.
Commands & Tasks
app-modules/bot-discord/src/Commands/PingCommand.php, app-modules/bot-discord/src/Tasks/DynamicVoiceTask.php
Adds Ping command with interaction button and DynamicVoiceTask skeleton with 30s interval.
User/Provider/Character resolver & DTOs
app-modules/bot-discord/src/Actions/UserCharacterResolver.php, app-modules/bot-discord/src/DTO/ResolvedUserCharacter.php
Adds UserCharacterResolver to create or lookup user/character/provider linkage and ResolvedUserCharacter DTO encapsulating result.
Message module changes
app-modules/message/src/Actions/NewMessage.php, app-modules/message/src/DTO/NewMessageDTO.php
Refactors NewMessage to use UserCharacterResolver and CharacterEntity for experience; adds providerUsername to NewMessageDTO; changes persist signature to accept NewMessageDTO.
User profile changes
app-modules/user/src/Actions/UpdateProfile.php, app-modules/user/src/DTO/UpdateProfileDTO.php, app-modules/user/src/Http/Controllers/UsersController.php
Replaces UpdateProfile constructor and API: now handles UpdateProfileDTO; adds new readonly UpdateProfileDTO with fromPayload/toProfile; controller now calls action with DTO.
Character caching removal
app-modules/character/src/Actions/FindCharacterIdByUserId.php
Removes caching layer; direct delegation to findCharacterByUserId.
Provider model doc
app-modules/provider/src/Models/Provider.php
Adds PHPDoc annotation for int $tenant_id (documentation only).
CLI command
app/Console/Commands/GenerateDiscordTenant.php
New Artisan command to create a Tenant and attach a Discord provider by guildId.
Tests
app-modules/message/tests/*, app-modules/user/tests/Feature/UpdateProfileTest.php
Marks multiple tests as skipped (changed closures to })->skip()), adjusts one NewMessage test DTO construction (added positional arg and named parameter), and skips a data provider dataset.
CI workflow
.github/workflows/_phpstan.yml
Removes the "Perform ide-helper" step from the PHPStan workflow.

Sequence Diagram(s)

sequenceDiagram
    participant Discord as Discord API
    participant Event as MessageReceivedEvent
    participant Resolver as UserCharacterResolver
    participant NewMsg as NewMessage
    participant Persist as PersistMessage

    Discord->>Event: MESSAGE_CREATE (Message)
    Event->>Event: ignore bot messages
    Event->>Resolver: resolve(provider, providerId, username, tenantId)
    alt existing linkage
        Resolver-->>Event: ResolvedUserCharacter(existing)
    else new linkage
        Resolver-->>Event: ResolvedUserCharacter(new)
    end
    Event->>NewMsg: handle(NewMessageDTO)
    NewMsg->>NewMsg: compute obtainedExperience from Character
    NewMsg->>Persist: persist(NewMessageDTO with obtainedExperience, providerId)
    Persist-->>NewMsg: persisted
    NewMsg-->>Event: done
Loading
sequenceDiagram
    participant User
    participant Slash as IntroductionCommand
    participant Modal as Discord Modal
    participant Resolver as UserCharacterResolver
    participant Update as UpdateProfile

    User->>Slash: /introduction
    Slash->>Modal: show modal (name,nickname,github,linkedin,about)
    Modal-->>Slash: submit components
    Slash->>Resolver: resolve(provider, providerId, username, tenantId)
    Resolver-->>Slash: ResolvedUserCharacter
    Slash->>Update: handle(UpdateProfileDTO)
    Update-->>Slash: success
    Slash->>Discord: reply confirmation + assign role
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing careful review:
    • UserCharacterResolver (creation side effects and transactional safety).
    • NewMessage refactor (experience calculation, persist signature change).
    • AbstractSlashCommand (middleware pipeline and provider/member resolution correctness).
    • UpdateProfile signature change and all call sites (controller, slash commands).
    • Tests skipped—verify intentionality and coverage gaps.

Poem

🐇 I nibbled code and stitched a bot so spry,

New profiles, greetings, modals in the sky.
Characters bloom where messages hop,
Buttons wave, welcome roles drop.
Hooray — a warren of features, carrot-top!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: base discord bot' accurately summarizes the main change—adding a foundational Discord bot module with commands, events, and service provider infrastructure.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/discord-bot

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5871040 and 955ca01.

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • .github/workflows/_phpstan.yml (0 hunks)
  • app-modules/bot-discord/src/Providers/BotDiscordServiceProvider.php (1 hunks)
💤 Files with no reviewable changes (1)
  • .github/workflows/_phpstan.yml
🚧 Files skipped from review as they are similar to previous changes (1)
  • app-modules/bot-discord/src/Providers/BotDiscordServiceProvider.php
⏰ 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: Perform Rector Check / Run
  • GitHub Check: Perform Pest Tests / Run
  • GitHub Check: Perform Phpstan Check / Run

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: 2

🧹 Nitpick comments (11)
app-modules/message/src/DTO/NewMessageDTO.php (1)

12-21: New required providerUsername field may break existing make() callers

You’ve added a required public string $providerUsername and now unconditionally read $payload['provider_username'] in make(). Any existing callers that build the payload without this key will now error at runtime.

Consider either:

  • Verifying all producers of this payload now include provider_username, or
  • Providing a safe fallback / validation when the key is missing (e.g., explicit assertion or default).

Also applies to: 23-35

app/Console/Commands/GenerateDiscordTenant.php (1)

18-45: Make tenant generation idempotent and improve command description

Two points here:

  1. Idempotency / duplicates

Tenant::factory()->afterCreating(...)->create(); (Lines 32–42) will always create a new Tenant and associated Provider for the same {guildId}. If someone runs the command twice for the same guild, you’ll end up with multiple Tenants and multiple Provider rows, while MessageReceivedEvent later does a firstOrFail() lookup by model_type and provider_id only.

Consider guarding against this by checking for an existing provider for the guild before creating a new tenant, e.g.:

 use He4rt\Provider\Enums\ProviderEnum;
+use He4rt\Provider\Models\Provider;
 use He4rt\Tenant\Models\Tenant;

@@
     public function handle(): void
     {
+        $guildId = (string) $this->argument('guildId');
+
+        $exists = Provider::query()
+            ->where('model_type', Tenant::class)
+            ->where('provider', ProviderEnum::Discord)
+            ->where('provider_id', $guildId)
+            ->exists();
+
+        if ($exists) {
+            $this->warn('A tenant/provider for this guild already exists.');
+            return;
+        }
+
         Tenant::factory()
-            ->afterCreating(function (Tenant $tenant): void {
+            ->afterCreating(function (Tenant $tenant) use ($guildId): void {
                 $tenant
                     ->providers()
                     ->create([
                         'tenant_id' => $tenant->getKey(),
                         'provider' => ProviderEnum::Discord,
-                        'provider_id' => $this->argument('guildId'),
+                        'provider_id' => $guildId,
                     ]);
             })
             ->create();
  1. Command description

protected $description = 'Command description'; (Line 25) is still a placeholder. Updating it to something like “Generate a Tenant and Discord provider binding for a guild ID” will make php artisan list more useful.

composer.json (1)

18-20: Global minimum-stability: dev + dev-next framework may affect production stability

You’ve introduced laracord/framework: "dev-next" and switched the project-wide "minimum-stability" to "dev" (Line 157). Together, this allows Composer to pull dev versions of dependencies more freely.

That’s fine if:

  • This app is expected to run against bleeding-edge packages, and
  • You’re comfortable with the risk that a transitive dependency update could introduce breaking changes.

If this is intended for production, consider whether you can instead:

  • Keep "minimum-stability": "stable" and allow dev versions only where explicitly needed (e.g., via explicit dev- constraints/aliases), and/or
  • Pin laracord/framework (and other key libs) to a known-good commit/tag.

Please double-check that this relaxation of stability is intentional and aligns with your deployment expectations.

Also applies to: 36-40, 47-47, 51-69, 157-170

app-modules/bot-discord/src/Events/MessageReceivedEvent.php (1)

25-57: Tighten guild handling, provider lookup, and static-analysis suppression

A few targeted improvements here:

  1. Skip direct messages early

For non-guild messages guild_id is typically null. Right now this yields an empty-string lookup and a firstOrFail() exception on every DM, which then gets logged as an error. Consider short‑circuiting when there is no guild:

     public function handle(Message $message, Discord $discord): void
     {
         if ($message->author->bot) {
             return;
         }
+
+        // Ignore direct messages (no guild context).
+        if ($message->guild_id === null) {
+            return;
+        }
  1. Constrain tenant provider lookup to Discord

To avoid future collisions if another provider type ever writes Tenant providers with the same provider_id, you can also filter by the provider enum:

-        try {
-            $tenantProvider = Provider::query()
-                ->where('model_type', Tenant::class)
-                ->where('provider_id', (string) $message->guild_id)
-                ->firstOrFail();
+        try {
+            $tenantProvider = Provider::query()
+                ->where('model_type', Tenant::class)
+                ->where('provider', ProviderEnum::Discord)
+                ->where('provider_id', (string) $message->guild_id)
+                ->firstOrFail();
  1. Suppress unused $discord parameter warning

Since the method signature is likely defined by Laracord, it’s reasonable that $discord is unused. Instead of changing the signature, you can suppress PHPMD for this method:

-    /**
-     * Handle the event.
-     */
-    public function handle(Message $message, Discord $discord): void
+    /**
+     * Handle the event.
+     *
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function handle(Message $message, Discord $discord): void
  1. Logging verbosity

The sprintf currently logs the full trace for every failure (Line 55). That’s great for debugging, but in high‑traffic or production environments it can be noisy and may expose internal details. Depending on your logging policy, you might later want to either:

  • Log a shorter message to the main logger and send full traces only to a debug channel, or
  • Downgrade to debug() for traces and keep error() for the message + basic context.
app-modules/user/src/Actions/UpdateProfile.php (2)

14-21: Consider scoping provider lookup by tenant and using DTO’s tenantId

UpdateProfileDTO carries tenantId, but the lookup only filters by model_type, provider and provider_id. In a multi-tenant setup, the same provider_id could exist in more than one tenant and update the wrong user.

If providers has a tenant_id (as used elsewhere with Tenant::class), it’s safer to include it and align with the DTO:

-        $provider = Provider::query()
-            ->where('model_type', User::class)
-            ->where('provider', $profileDTO->provider)
-            ->where('provider_id', $profileDTO->providerId)
-            ->first();
+        $provider = Provider::query()
+            ->where('tenant_id', $profileDTO->tenantId)
+            ->where('model_type', User::class)
+            ->where('provider', $profileDTO->provider)
+            ->where('provider_id', $profileDTO->providerId)
+            ->first();

If tenant_id is intentionally not relevant here, consider dropping tenantId from the DTO to avoid confusion.


23-29: Harden logging and relation access for missing user/information

Right now you only log a generic "Provider not found" and then assume $provider->user and its information() relation are always present. Any data inconsistency on the provider’s model_id could trigger a fatal error here.

Two small robustness tweaks:

  • Log with context to help debugging.
  • Guard against a missing user before updating.
-        if (! $provider) {
-            Log::error('Provider not found');
-
-            return;
-        }
-
-        $provider->user->information()->update($profileDTO->toProfile());
+        if (! $provider) {
+            Log::error('Provider not found', [
+                'provider' => $profileDTO->provider->name ?? null,
+                'provider_id' => $profileDTO->providerId,
+                'tenant_id' => $profileDTO->tenantId,
+            ]);
+
+            return;
+        }
+
+        if (! $provider->user) {
+            Log::error('Provider has no related user', [
+                'provider_id' => $provider->id,
+            ]);
+
+            return;
+        }
+
+        $provider->user->information()->update($profileDTO->toProfile());
app-modules/bot-discord/src/Commands/PingCommand.php (1)

44-52: Address unused $args parameter while keeping Laracord signature

$args is unused and PHPMD complains, but the signature likely must stay aligned with Laracord\Commands\Command::handle(Message $message, array $args).

You can silence the warning without changing behavior, e.g.:

-    public function handle(Message $message, array $args): void
+    /**
+     * Handle the command.
+     *
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function handle(Message $message, array $args): void
     {
-        $this
+        // $args intentionally unused
+        $this
             ->message()
             ->title('Ping Command')
             ->content('Hello world!')
             ->button('👋', route: 'wave')
             ->reply($message);
     }

Alternatively, you could rename it to array $_args to signal intentional non-use.

app-modules/bot-discord/composer.json (1)

1-22: Optional: enrich package metadata and platform constraints

The manifest is valid as-is, but a few small tweaks would improve clarity and tooling support:

  • Add a meaningful "description" so Composer/Packagist consumers know what this module is.
  • Consider adding "php" (and possibly "laravel/framework" / "laracord/*") constraints in "require" to document the supported runtime.
  • If this package is only ever used inside the monorepo, you can still add those constraints to help static analysis and future extraction.

No functional change required now; just something to consider.

app-modules/user/src/DTO/UpdateProfileDTO.php (2)

23-35: Make fromPayload more defensive, especially for optional fields

fromPayload assumes all keys exist; any missing key will trigger notices or type issues, even for nullable properties. If payloads come from external sources (Discord interactions, HTTP), it’s safer to tolerate missing optional fields:

    public static function fromPayload(array $payload): self
    {
        return new self(
-            providerId: $payload['provider_id'],
-            provider: $payload['provider'],
-            tenantId: $payload['tenant_id'],
-            name: $payload['name'],
-            nickname: $payload['nickname'],
-            linkedinUrl: $payload['linkedin_url'],
-            githubUrl: $payload['github_url'],
-            birthdate: $payload['birthdate'],
-            about: $payload['about']
+            providerId: $payload['provider_id'],
+            provider: $payload['provider'],
+            tenantId: $payload['tenant_id'],
+            name: $payload['name'] ?? null,
+            nickname: $payload['nickname'] ?? null,
+            linkedinUrl: $payload['linkedin_url'] ?? null,
+            githubUrl: $payload['github_url'] ?? null,
+            birthdate: $payload['birthdate'] ?? null,
+            about: $payload['about'] ?? null,
        );
    }

If you rely on some of them being mandatory, you can assert/validate explicitly instead of relying on array indexes.


11-20: Ensure $payload['provider'] is a ProviderEnum (or cast it here)

The constructor typehints ProviderEnum $provider, but fromPayload passes $payload['provider'] directly. If callers ever pass the raw scalar (e.g. 'discord') instead of an enum instance, this will fail at runtime.

Either document that fromPayload expects a ProviderEnum instance in provider, or normalize here:

-            provider: $payload['provider'],
+            provider: $payload['provider'] instanceof ProviderEnum
+                ? $payload['provider']
+                : ProviderEnum::from($payload['provider']),

This keeps the DTO boundary strict while being resilient to string/int payloads.

app-modules/bot-discord/src/Events/GreetingsEvent.php (1)

24-48: Silence unused $discord parameter or use it for context

The handle(Message $message, Discord $discord) signature matches the event contract, but $discord isn’t used, hence the PHPMD warning.

Two options:

  • Explicitly mark the parameter as intentionally unused:
-    public function handle(Message $message, Discord $discord): void
+    /**
+     * Handle the event.
+     *
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function handle(Message $message, Discord $discord): void
  • Or rename to signal that it’s unused:
-    public function handle(Message $message, Discord $discord): void
+    public function handle(Message $message, Discord $_discord): void

Functionally nothing changes; it just keeps static analysis clean.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 19d58f7 and d0afab1.

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (16)
  • .gitignore (1 hunks)
  • app-modules/bot-discord/composer.json (1 hunks)
  • app-modules/bot-discord/src/Commands/PingCommand.php (1 hunks)
  • app-modules/bot-discord/src/Events/GreetingsEvent.php (1 hunks)
  • app-modules/bot-discord/src/Events/MessageReceivedEvent.php (1 hunks)
  • app-modules/bot-discord/src/Providers/BotDiscordServiceProvider.php (1 hunks)
  • app-modules/bot-discord/src/SlashCommands/IntroductionCommand.php (1 hunks)
  • app-modules/character/src/Actions/FindCharacterIdByUserId.php (1 hunks)
  • app-modules/message/src/Actions/NewMessage.php (1 hunks)
  • app-modules/message/src/DTO/NewMessageDTO.php (2 hunks)
  • app-modules/provider/src/Models/Provider.php (1 hunks)
  • app-modules/user/src/Actions/UpdateProfile.php (1 hunks)
  • app-modules/user/src/DTO/UpdateProfileDTO.php (1 hunks)
  • app/Console/Commands/GenerateDiscordTenant.php (1 hunks)
  • bootstrap/providers.php (2 hunks)
  • composer.json (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
app-modules/bot-discord/src/Providers/BotDiscordServiceProvider.php (4)
app-modules/bot-discord/src/Commands/PingCommand.php (1)
  • PingCommand (11-63)
app-modules/bot-discord/src/Events/GreetingsEvent.php (1)
  • GreetingsEvent (12-49)
app-modules/bot-discord/src/Events/MessageReceivedEvent.php (1)
  • MessageReceivedEvent (18-60)
app-modules/bot-discord/src/SlashCommands/IntroductionCommand.php (1)
  • IntroductionCommand (19-181)
app-modules/bot-discord/src/Events/MessageReceivedEvent.php (5)
app-modules/message/src/Actions/NewMessage.php (2)
  • NewMessage (15-72)
  • persist (22-71)
app-modules/message/src/DTO/NewMessageDTO.php (1)
  • NewMessageDTO (10-36)
app-modules/provider/src/Models/Provider.php (1)
  • Provider (28-94)
app-modules/tenant/src/Models/Tenant.php (1)
  • Tenant (23-103)
app-modules/bot-discord/src/Providers/BotDiscordServiceProvider.php (1)
  • bot (16-25)
app-modules/bot-discord/src/Commands/PingCommand.php (1)
app-modules/bot-discord/src/SlashCommands/IntroductionCommand.php (1)
  • handle (66-114)
app/Console/Commands/GenerateDiscordTenant.php (2)
app-modules/provider/src/Models/Provider.php (1)
  • Provider (28-94)
app-modules/tenant/src/Models/Tenant.php (1)
  • Tenant (23-103)
app-modules/user/src/DTO/UpdateProfileDTO.php (1)
app-modules/provider/src/Models/Provider.php (1)
  • Provider (28-94)
app-modules/user/src/Actions/UpdateProfile.php (4)
app-modules/provider/src/Models/Provider.php (2)
  • Provider (28-94)
  • user (57-60)
app-modules/user/src/DTO/UpdateProfileDTO.php (2)
  • UpdateProfileDTO (9-49)
  • toProfile (38-48)
app-modules/user/src/Models/Information.php (1)
  • user (34-37)
app-modules/user/src/Models/User.php (1)
  • information (82-85)
app-modules/bot-discord/src/Events/GreetingsEvent.php (2)
app-modules/bot-discord/src/Events/MessageReceivedEvent.php (1)
  • handle (30-59)
app-modules/bot-discord/src/Providers/BotDiscordServiceProvider.php (1)
  • bot (16-25)
🪛 PHPMD (2.15.0)
app-modules/bot-discord/src/Events/MessageReceivedEvent.php

30-30: Avoid unused parameters such as '$discord'. (undefined)

(UnusedFormalParameter)

app-modules/bot-discord/src/Commands/PingCommand.php

44-44: Avoid unused parameters such as '$args'. (undefined)

(UnusedFormalParameter)

app-modules/bot-discord/src/Events/GreetingsEvent.php

24-24: Avoid unused parameters such as '$discord'. (undefined)

(UnusedFormalParameter)

⏰ 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: Perform Pest Tests / Run
  • GitHub Check: Perform Rector Check / Run
🔇 Additional comments (7)
app-modules/character/src/Actions/FindCharacterIdByUserId.php (2)

12-15: Based on my verification, I have found critical issues with the original review comment. Let me provide the rewritten version:


The review comment contains incorrect assumptions that do not match the codebase reality.

The two main concerns in the original comment are unfounded:

  1. Caching removal claim is unsupported: There is no evidence of caching in the current code or in the git history. The file contains only one commit ("wip") and the HEAD version is identical to the current implementation—both lack any caching logic. The claim that "caching logic has been removed" cannot be verified.

  2. Discord bot context concern is invalid: FindCharacterIdByUserId is only invoked from HTTP controllers (CharactersController, NewVoiceMessage via MessagesController). It is never called from Discord bot event handlers. The Discord bot passes tenantId explicitly via DTO objects (as seen in MessageReceivedEvent and NewMessage), so request()->tenant_id is available in all actual execution paths.

No verification of call frequency is needed since the concerns do not reflect actual usage patterns.


20-20: Based on my verification, I found that the original review comment's concern is not applicable to this PR. Here's what I discovered:

  1. Discord bot does NOT call FindCharacterIdByUserId: The Discord bot's event handlers (MessageReceivedEvent, IntroductionCommand) use different actions like NewMessage and UpdateProfile.

  2. Discord bot correctly passes tenant_id: When NewMessage is called from Discord context, tenant_id is explicitly passed as a parameter through NewMessageDTO, not obtained from request()->tenant_id.

  3. FindCharacterIdByUserId is only used in HTTP contexts: The three actions that depend on it (NewVoiceMessage, ClaimDailyBonus, ClaimCharacterBadge) are not called from Discord bot code and are only used in HTTP-based message and character modules.


request()->tenant_id on line 20 is not used by Discord bot integration.

This action is only invoked from HTTP contexts within the message and character modules, where an HTTP request context is always available. Discord bot handlers use different code paths that properly pass tenant_id as parameters. No changes are required.

.gitignore (1)

43-43: Ignoring .laracord directory is appropriate

This keeps Laracord’s local artifacts out of version control and is consistent with the other framework/IDE ignores already present.

app-modules/provider/src/Models/Provider.php (1)

19-27: Docblock update for tenant_id matches existing model state

Adding @property int $tenant_id aligns the PHPDoc with the actual $fillable fields on Provider and improves IDE/static-analysis support without changing runtime behavior.

app-modules/bot-discord/src/Providers/BotDiscordServiceProvider.php (1)

14-25: Clear, cohesive registration of Discord events and commands

The bot() method cleanly wires events and commands in one fluent chain, which keeps the Discord bot configuration centralized and readable. No issues spotted here.

bootstrap/providers.php (1)

5-7: DiscordServiceProvider registration fits the existing bootstrap pattern

Importing and appending DiscordServiceProvider::class to the providers array integrates the Discord stack cleanly with the existing Laravel bootstrap flow.

Also applies to: 16-27

app-modules/bot-discord/src/SlashCommands/IntroductionCommand.php (1)

69-109: I'll verify the concerns raised in this review comment about the setValue() calls with sample data and fake() usage in the modal. Let me start by examining the actual code and understanding the DiscordPHP API behavior.
<function_calls>

#!/bin/bash

First, let's locate and examine the IntroductionCommand.php file

find . -type f -name "IntroductionCommand.php" | head -5


</function_calls>
<function_calls>

#!/bin/bash

Read the IntroductionCommand.php file to see the complete context

cat -n app-modules/bot-discord/src/SlashCommands/IntroductionCommand.php | head -150


</function_calls>
<function_calls>

DiscordPHP TextInput setValue placeholder behavior modal form

</function_calls>

Comment thread app-modules/bot-discord/src/SlashCommands/IntroductionCommand.php Outdated
Comment thread app-modules/message/src/Actions/NewMessage.php Outdated
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: 0

🧹 Nitpick comments (4)
app-modules/bot-discord/src/Tasks/DynamicVoiceTask.php (1)

9-27: Placeholder task needs implementation.

The class name DynamicVoiceTask suggests voice channel management functionality, but the current implementation only logs "Hello world." Additionally, the 5-second interval is aggressive for a placeholder that may create log noise in production.

Consider either:

  1. Implementing the actual dynamic voice logic
  2. Increasing the interval or disabling the task until ready
  3. Adding a TODO comment documenting the intended behavior

Would you like me to open an issue to track the implementation of the dynamic voice channel functionality?

app-modules/bot-discord/src/SlashCommands/ProfileCommand.php (1)

24-24: Improve the user-facing command description.

The description "The Profile Command slash command." is a placeholder that will be visible to users in Discord's command picker. Consider a more descriptive message like "View your He4rt profile" or similar.

-    protected $description = 'The Profile Command slash command.';
+    protected $description = 'View your He4rt community profile.';
app-modules/bot-discord/src/SlashCommands/DynamicVoiceCommand.php (1)

24-24: Improve the user-facing command description.

Similar to other commands, the description "The Dynamic Voice Command slash command." is placeholder text visible to users. Consider describing the actual functionality.

-    protected $description = 'The Dynamic Voice Command slash command.';
+    protected $description = 'Create or manage dynamic voice channels.';
app-modules/bot-discord/src/SlashCommands/EditProfileCommand.php (1)

24-24: Improve the user-facing command description.

The description "The Edit Profile Command slash command." should be more user-friendly, e.g., "Edit your He4rt community profile."

-    protected $description = 'The Edit Profile Command slash command.';
+    protected $description = 'Edit your He4rt community profile.';
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d0afab1 and 45014eb.

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • app-modules/bot-discord/src/Providers/BotDiscordServiceProvider.php (1 hunks)
  • app-modules/bot-discord/src/SlashCommands/DynamicVoiceCommand.php (1 hunks)
  • app-modules/bot-discord/src/SlashCommands/EditProfileCommand.php (1 hunks)
  • app-modules/bot-discord/src/SlashCommands/ProfileCommand.php (1 hunks)
  • app-modules/bot-discord/src/Tasks/DynamicVoiceTask.php (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
app-modules/bot-discord/src/SlashCommands/EditProfileCommand.php (2)
app-modules/bot-discord/src/SlashCommands/DynamicVoiceCommand.php (1)
  • handle (57-65)
app-modules/bot-discord/src/SlashCommands/ProfileCommand.php (1)
  • handle (57-64)
app-modules/bot-discord/src/SlashCommands/ProfileCommand.php (2)
app-modules/bot-discord/src/SlashCommands/DynamicVoiceCommand.php (1)
  • handle (57-65)
app-modules/bot-discord/src/SlashCommands/EditProfileCommand.php (1)
  • handle (57-64)
app-modules/bot-discord/src/SlashCommands/DynamicVoiceCommand.php (4)
app-modules/meeting/src/Models/Meeting.php (1)
  • admin (71-74)
app-modules/bot-discord/src/SlashCommands/EditProfileCommand.php (1)
  • handle (57-64)
app-modules/bot-discord/src/SlashCommands/ProfileCommand.php (1)
  • handle (57-64)
app-modules/bot-discord/src/Tasks/DynamicVoiceTask.php (1)
  • handle (24-27)
🔇 Additional comments (4)
app-modules/bot-discord/src/Providers/BotDiscordServiceProvider.php (1)

10-19: Clean service provider implementation.

The provider correctly extends LaracordServiceProvider and chains discovery methods for events, commands, slash commands, and tasks with properly aligned paths and namespaces.

app-modules/bot-discord/src/SlashCommands/ProfileCommand.php (1)

57-64: Placeholder implementation noted.

The scaffold is consistent with other slash commands in this PR. The actual profile retrieval logic will need to be implemented before release.

app-modules/bot-discord/src/SlashCommands/DynamicVoiceCommand.php (1)

67-75: Good use of interaction routing.

The interactions() method correctly demonstrates Laracord's button interaction routing pattern with the 'wave' route. This provides a good foundation for adding more complex interactions.

app-modules/bot-discord/src/SlashCommands/EditProfileCommand.php (1)

57-64: Consider modal interaction for profile editing.

Profile editing typically requires user input. Laracord supports modals for form-based interactions. When implementing the actual logic, consider using ->modal() to collect profile data from the user.

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: 2

🧹 Nitpick comments (1)
app-modules/bot-discord/src/SlashCommands/ProfileCommand.php (1)

80-97: Color value looks like hex, but Laracord expects decimal

Laracord’s ->color() expects a decimal value (often passed as a string). The value '800080' looks like the hex color #800080 (purple), but here it will be treated as the decimal 800,080, which is a completely different color.

If your intent is #800080, consider converting it to decimal:

-                ->color('800080')
+                ->color('8388736') // decimal for hex 0x800080

(or use whatever brand color you actually want in decimal).

Please double‑check Laracord’s current color() docs to confirm the expected format and adjust the value if needed.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 45014eb and ac724d7.

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • app-modules/bot-discord/src/SlashCommands/ProfileCommand.php (1 hunks)
  • app-modules/bot-discord/src/Tasks/DynamicVoiceTask.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app-modules/bot-discord/src/Tasks/DynamicVoiceTask.php
🧰 Additional context used
🧬 Code graph analysis (1)
app-modules/bot-discord/src/SlashCommands/ProfileCommand.php (4)
app-modules/provider/src/Models/Provider.php (1)
  • Provider (28-94)
app-modules/bot-discord/src/SlashCommands/EditProfileCommand.php (1)
  • handle (57-64)
app-modules/bot-discord/src/SlashCommands/DynamicVoiceCommand.php (1)
  • handle (57-65)
app-modules/user/src/Models/User.php (1)
  • information (82-85)
⏰ 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: Perform Pest Tests / Run
  • GitHub Check: Perform Rector Check / Run
🔇 Additional comments (1)
app-modules/bot-discord/src/SlashCommands/ProfileCommand.php (1)

13-56: Nice, consistent SlashCommand setup and defaults

Class metadata ($name, $description, $options, $permissions, $admin, $hidden) follows Laracord’s conventions and keeps the command self‑describing. No issues here.

Comment thread app-modules/bot-discord/src/SlashCommands/ProfileCommand.php
Comment thread app-modules/bot-discord/src/SlashCommands/ProfileCommand.php
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: 2

🧹 Nitpick comments (1)
app-modules/bot-discord/src/SlashCommands/DontAskCommand.php (1)

66-66: Consider externalizing the hardcoded image URL.

The Discord CDN URL is hardcoded. Consider moving it to a configuration file or environment variable for easier maintenance and updates.

Example approach - add to a config file:

// config/bot-discord.php
return [
    'images' => [
        'dont_ask' => env('DISCORD_DONT_ASK_IMAGE', 'https://media.discordapp.net/attachments/546151895010508827/1046092564513701909/Frame_1282_1.png'),
    ],
];

Then reference it:

-        ->imageUrl('https://media.discordapp.net/attachments/546151895010508827/1046092564513701909/Frame_1282_1.png')
+        ->imageUrl(config('bot-discord.images.dont_ask'))
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ac724d7 and 4ff7fca.

📒 Files selected for processing (1)
  • app-modules/bot-discord/src/SlashCommands/DontAskCommand.php (1 hunks)
🔇 Additional comments (3)
app-modules/bot-discord/src/SlashCommands/DontAskCommand.php (3)

1-10: LGTM!

The namespace, imports, and file structure are properly configured.


27-39: Verify the admin and hidden flag settings.

The command is currently set to $admin = false and $hidden = false, making it accessible to all users. Given the command's moderator-oriented purpose (prompting users to provide better context), consider whether this should be restricted to admins or hidden from the command list.


41-53: LGTM!

The command options are properly configured with appropriate type and requirement settings.

Comment thread app-modules/bot-discord/src/SlashCommands/DontAskCommand.php
Comment thread app-modules/bot-discord/src/SlashCommands/DontAskCommand.php
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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app-modules/user/src/Http/Controllers/UsersController.php (1)

44-52: Type mismatch: UpdateProfile::handle() now expects UpdateProfileDTO, but controller passes string

UpdateProfile::handle() is typed as handle(UpdateProfileDTO $profileDTO): void (see app-modules/user/src/Actions/UpdateProfile.php), but putProfile() currently calls:

$action->handle($value);

Here $value is declared as string, so this will trigger a TypeError at runtime and the new DTO-based flow is never used. The UpdateProfileRequest $request is also unused.

You should construct an UpdateProfileDTO from the route param + validated request data and pass that DTO into UpdateProfile::handle(), e.g.:

// Pseudocode – use the actual factory/constructor on UpdateProfileDTO
$profileDTO = /* build UpdateProfileDTO from $request->validated() and $value */;
$action->handle($profileDTO);

This will align the controller with the refactored action and avoid runtime type errors.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4ff7fca and b46dc00.

📒 Files selected for processing (4)
  • .env.example (1 hunks)
  • app-modules/bot-discord/src/SlashCommands/EditProfileCommand.php (1 hunks)
  • app-modules/bot-discord/src/SlashCommands/IntroductionCommand.php (1 hunks)
  • app-modules/user/src/Http/Controllers/UsersController.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app-modules/bot-discord/src/SlashCommands/IntroductionCommand.php
🧰 Additional context used
🧬 Code graph analysis (2)
app-modules/user/src/Http/Controllers/UsersController.php (1)
app-modules/user/src/Actions/UpdateProfile.php (1)
  • handle (14-30)
app-modules/bot-discord/src/SlashCommands/EditProfileCommand.php (4)
app-modules/provider/src/Models/Provider.php (1)
  • Provider (28-94)
app-modules/user/src/Actions/UpdateProfile.php (1)
  • UpdateProfile (12-31)
app-modules/user/src/DTO/UpdateProfileDTO.php (2)
  • UpdateProfileDTO (9-49)
  • fromPayload (23-36)
app-modules/user/src/Models/User.php (1)
  • information (82-85)
🔇 Additional comments (5)
.env.example (1)

77-77: Based on my verification, I can now provide the rewritten review comment:


DISCORD_TOKEN is not a duplicate of HE4RT_DISCORD_BOT_KEY—they serve different purposes.

DISCORD_TOKEN (line 77) is intended for the Laracord framework, while HE4RT_DISCORD_BOT_KEY (line 63) is used by the existing bot configuration in config/he4rt.php. However, DISCORD_TOKEN is not referenced anywhere in the codebase, which suggests either:

  1. The Laracord framework reads it automatically during service provider initialization, or
  2. The PR setup is incomplete.

Consider adding a clarifying comment in .env.example above DISCORD_TOKEN= to document that it's intended for the Laracord Discord bot framework, distinguishing it from the legacy HE4RT_DISCORD_BOT_KEY.

Alternatively, align the naming convention (e.g., LARACORD_DISCORD_TOKEN or HE4RT_DISCORD_BOT_TOKEN) to make the distinction clearer for future maintainers.

app-modules/bot-discord/src/SlashCommands/EditProfileCommand.php (4)

169-169: Potential null value: guild icon may be null if the guild has no custom icon.

Consider handling this gracefully to avoid unexpected behavior.

-                ->footerIcon($interaction->guild->icon)
+                ->footerIcon($interaction->guild->icon ?? '')

66-80: LGTM!

The provider lookup and early return pattern with a helpful message directing users to /introduction is well-implemented.


84-129: Modal implementation looks good.

The modal is well-structured with appropriate validation constraints (min/max lengths, required flags) and prefills values from the existing profile.


137-179: Solid error handling and response pattern.

The try-catch with logging and the dual response (public embed + ephemeral confirmation) provides good UX. Minor issues noted separately.

Comment thread app-modules/bot-discord/src/SlashCommands/EditProfileCommand.php
Comment thread app-modules/bot-discord/src/SlashCommands/EditProfileCommand.php
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: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b46dc00 and cc13c3e.

📒 Files selected for processing (2)
  • app-modules/bot-discord/src/SlashCommands/AbstractSlashCommand.php (1 hunks)
  • app-modules/bot-discord/src/SlashCommands/ProfileCommand.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app-modules/bot-discord/src/SlashCommands/ProfileCommand.php
🧰 Additional context used
🧬 Code graph analysis (1)
app-modules/bot-discord/src/SlashCommands/AbstractSlashCommand.php (3)
app-modules/provider/src/Models/Provider.php (1)
  • Provider (28-94)
app-modules/tenant/src/Models/Tenant.php (1)
  • Tenant (23-103)
app-modules/bot-discord/src/Providers/BotDiscordServiceProvider.php (1)
  • bot (12-19)
🔇 Additional comments (2)
app-modules/bot-discord/src/SlashCommands/AbstractSlashCommand.php (2)

18-20: LGTM!

Nullable provider properties are appropriately typed for storing resolved context.


22-38: LGTM!

The pipeline setup correctly integrates with Laracord's middleware system and passes the interaction context through.

Comment thread app-modules/bot-discord/src/SlashCommands/AbstractSlashCommand.php
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: 4

♻️ Duplicate comments (7)
app-modules/bot-discord/src/SlashCommands/IntroductionCommand.php (4)

94-100: Confirm “Linkedisney” label is intentional

The label "Linkedisney (Opcional)" looks like a typo for LinkedIn; if this isn’t a deliberate in‑community joke, consider renaming to avoid confusion (and keep it consistent with EditProfileCommand).


135-138: Provider lookup should be constrained by provider type (Discord)

The tenant provider query only filters by model_type and provider_id. If other providers ever share the same provider_id (e.g. multi‑provider setup), this can become ambiguous. AbstractSlashCommand’s beforePipeline() already uses a provider filter (ProviderEnum::Discord); mirroring that here would make the lookup deterministic.

 use He4rt\Provider\Models\Provider;
+use He4rt\Provider\Enums\ProviderEnum;
@@
-        $tenantProvider = Provider::query()
-            ->where('model_type', Tenant::class)
-            ->where('provider_id', (string) $interaction->guild_id)
-            ->firstOrFail();
+        $tenantProvider = Provider::query()
+            ->where('model_type', Tenant::class)
+            ->where('provider', ProviderEnum::Discord)
+            ->where('provider_id', (string) $interaction->guild_id)
+            ->firstOrFail();

This also keeps it consistent with AbstractSlashCommand::beforePipeline().


120-123: respondWithMessage should receive a MessageBuilder, not a string

These calls pass a raw string into Interaction::respondWithMessage, but DiscordPHP’s API expects a MessageBuilder as the first argument. This is likely to trigger a TypeError at runtime; your success path already uses message()->reply() instead.

You can fix by wrapping the strings in MessageBuilder instances and importing it:

 use Discord\Builders\Components\TextInput;
+use Discord\Builders\MessageBuilder;
 use Discord\Helpers\Collection;
@@
-        if (! $role) {
-            $interaction->respondWithMessage('Erro ao encontrar o role He4rt', true);
+        if (! $role) {
+            $interaction->respondWithMessage(
+                MessageBuilder::new()->setContent('Erro ao encontrar o role He4rt'),
+                true
+            );
@@
-        if ($hasRole) {
-            $interaction->respondWithMessage('Você já apresentou!!', true);
+        if ($hasRole) {
+            $interaction->respondWithMessage(
+                MessageBuilder::new()->setContent('Você já apresentou!!'),
+                true
+            );
@@
-            $interaction->respondWithMessage(
-                'Ocorreu um erro ao processar sua apresentação. Por favor, tente novamente mais tarde.',
-                true
-            );
+            $interaction->respondWithMessage(
+                MessageBuilder::new()->setContent(
+                    'Ocorreu um erro ao processar sua apresentação. Por favor, tente novamente mais tarde.'
+                ),
+                true
+            );
What is the PHP method signature of `Discord\Parts\Interactions\Interaction::respondWithMessage` in DiscordPHP?

Also applies to: 128-132, 181-184


140-149: birthdate is never collected in the modal

The payload maps:

'birthdate' => $components->get('custom_id', 'birthdate')?->value ?? null,

but the modal defines no TextInput with custom_id = 'birthdate', so this will always evaluate to null. Given UpdateProfileDTO::fromPayload() expects a birthdate key, this is confusing and may eventually lead to accidental data clearing if this command is ever used against pre‑existing profiles.

Consider either:

  • Adding a TextInput for birthdate to the modal (and documenting expected format), or
  • Intentionally preserving any existing DB value (e.g. by resolving the user provider and reading their current information->birthdate instead of using components).
app-modules/bot-discord/src/SlashCommands/EditProfileCommand.php (3)

91-105: Same “Linkedisney” label here – verify it’s not a typo

This modal also labels the LinkedIn field as "Linkedisney (Opcional)". If that’s not an intentional joke, consider correcting it (and aligning with the rest of the UX):

-                TextInput::new('Linkedisney (Opcional)', TextInput::STYLE_SHORT)
+                TextInput::new('LinkedIn (Opcional)', TextInput::STYLE_SHORT)

61-69: Wrap respondWithMessage strings in MessageBuilder

Both the “missing introduction” guard and the catch block pass plain strings to respondWithMessage, which likely violates DiscordPHP’s Interaction::respondWithMessage(MessageBuilder $builder, ?bool $ephemeral) signature.

Import MessageBuilder and wrap these calls, e.g.:

 use Discord\Builders\Components\TextInput;
+use Discord\Builders\MessageBuilder;
@@
-        if (! $this->memberProvider?->user?->information) {
-            $interaction->respondWithMessage(
-                'Parece que você ainda não completou sua apresentação. Use o comando `/introduction` para continuar.',
-                true);
+        if (! $this->memberProvider?->user?->information) {
+            $interaction->respondWithMessage(
+                MessageBuilder::new()->setContent(
+                    'Parece que você ainda não completou sua apresentação. Use o comando `/introduction` para continuar.'
+                ),
+                true
+            );
@@
-        } catch (Throwable $throwable) {
-            $this->logger()->error($throwable->getMessage());
-
-            $interaction->respondWithMessage('Erro ao persistir dados', true);
-        }
+        } catch (Throwable $throwable) {
+            $this->logger()->error($throwable->getMessage());
+
+            $interaction->respondWithMessage(
+                MessageBuilder::new()->setContent('Erro ao persistir dados'),
+                true
+            );
+        }
What is the PHP method signature of `Discord\Parts\Interactions\Interaction::respondWithMessage` in current DiscordPHP?

Also applies to: 162-166


126-136: Editing profile will currently wipe any existing birthdate

Here:

'birthdate' => $components->get('custom_id', 'birthdate')?->value ?? null,

the modal does not define a birthdate input, so this always evaluates to null. Combined with UpdateProfile::handle() calling information()->update($profileDTO->toProfile()), any run of /edit-profile will overwrite an existing birthdate with null.

A minimal fix is to preserve the current value from the user’s information instead of reading from components:

             $payload = UpdateProfileDTO::fromPayload([
                 'tenant_id' => $this->memberProvider->tenant_id,
                 'provider' => $this->memberProvider->provider,
                 'provider_id' => $interaction->user->id,
@@
-                'github_url' => $components->get('custom_id', 'github_url')->value,
-                'birthdate' => $components->get('custom_id', 'birthdate')?->value ?? null,
+                'github_url' => $components->get('custom_id', 'github_url')->value,
+                'birthdate' => $this->memberProvider?->user?->information?->birthdate,
                 'about' => $components->get('custom_id', 'about')->value,
             ]);

If you intend to let users change birthdate via Discord too, a follow‑up would be to add a TextInput for birthdate to the modal (with documented format) and map from components instead.

🧹 Nitpick comments (4)
app-modules/bot-discord/src/Events/WelcomeMember.php (1)

22-22: Address the TODO: Hardcoded channel ID should be configurable.

The channel ID is hardcoded with a TODO comment. Consider moving this to environment configuration or a tenant-specific setting to avoid code changes when the channel needs to change.

Do you want me to open an issue to track making this configurable?

app-modules/bot-discord/src/Events/MessageReceivedEvent.php (1)

50-54: Consider structured logging instead of string concatenation.

Laravel's Log facade supports context arrays which are better for log aggregation and searching:

         } catch (Throwable $throwable) {
-            $this->logger()->error(
-                sprintf('%s | File: %s | Line: %s | Trace: %s', $throwable->getMessage(), $throwable->getFile(), $throwable->getLine(), $throwable->getTraceAsString()),
-            );
+            $this->logger()->error($throwable->getMessage(), [
+                'file' => $throwable->getFile(),
+                'line' => $throwable->getLine(),
+                'trace' => $throwable->getTraceAsString(),
+            ]);
         }
app-modules/message/src/Actions/NewMessage.php (1)

45-52: Silent error swallowing may cause message loss without visibility.

Catching Throwable and only logging means message persistence failures are silently dropped. For a Discord bot, this may be acceptable (messages are transient), but consider whether:

  1. Failed messages should be queued for retry
  2. Metrics/alerts should be triggered on persistent failures

If silent failure is intentional, document it:

-        } catch (Throwable $throwable) {
+        } catch (Throwable $throwable) {
+            // Intentionally catching all exceptions to prevent Discord event handler failures.
+            // Message persistence failures are logged but not propagated.
             Log::error('NewMessage failed', [
app-modules/bot-discord/src/SlashCommands/IntroductionCommand.php (1)

102-108: Avoid using fake() for modal default content

Using fake()->paragraph(10) as the default value makes the field large and random on every invocation, and risks bumping against the 1000‑char limit. A short, static example sentence as default (or no default, just the placeholder) is safer and clearer for users.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cc13c3e and eba8c94.

📒 Files selected for processing (8)
  • app-modules/bot-discord/src/Actions/UserCharacterResolver.php (1 hunks)
  • app-modules/bot-discord/src/DTO/ResolvedUserCharacter.php (1 hunks)
  • app-modules/bot-discord/src/Events/MessageReceivedEvent.php (1 hunks)
  • app-modules/bot-discord/src/Events/WelcomeMember.php (1 hunks)
  • app-modules/bot-discord/src/SlashCommands/EditProfileCommand.php (1 hunks)
  • app-modules/bot-discord/src/SlashCommands/IntroductionCommand.php (1 hunks)
  • app-modules/bot-discord/src/SlashCommands/ProfileCommand.php (1 hunks)
  • app-modules/message/src/Actions/NewMessage.php (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app-modules/bot-discord/src/SlashCommands/ProfileCommand.php
🧰 Additional context used
🧬 Code graph analysis (3)
app-modules/bot-discord/src/Actions/UserCharacterResolver.php (4)
app-modules/bot-discord/src/DTO/ResolvedUserCharacter.php (1)
  • ResolvedUserCharacter (11-20)
app-modules/character/src/Models/Character.php (1)
  • Character (28-128)
app-modules/provider/src/Models/Provider.php (2)
  • Provider (28-94)
  • model (49-52)
app-modules/user/src/Models/User.php (2)
  • address (57-60)
  • information (82-85)
app-modules/bot-discord/src/DTO/ResolvedUserCharacter.php (2)
app-modules/character/src/Models/Character.php (1)
  • Character (28-128)
app-modules/provider/src/Models/Provider.php (1)
  • Provider (28-94)
app-modules/bot-discord/src/SlashCommands/EditProfileCommand.php (4)
app-modules/user/src/Actions/UpdateProfile.php (1)
  • UpdateProfile (12-31)
app-modules/user/src/DTO/UpdateProfileDTO.php (2)
  • UpdateProfileDTO (9-49)
  • fromPayload (23-36)
app-modules/bot-discord/src/SlashCommands/AbstractSlashCommand.php (1)
  • AbstractSlashCommand (16-55)
app-modules/user/src/Models/User.php (1)
  • information (82-85)
🪛 PHPMD (2.15.0)
app-modules/bot-discord/src/Events/WelcomeMember.php

20-20: Avoid unused parameters such as '$discord'. (undefined)

(UnusedFormalParameter)

app-modules/bot-discord/src/Events/MessageReceivedEvent.php

27-27: Avoid unused parameters such as '$discord'. (undefined)

(UnusedFormalParameter)

🔇 Additional comments (2)
app-modules/bot-discord/src/DTO/ResolvedUserCharacter.php (1)

11-19: LGTM! Clean DTO implementation.

The value object correctly encapsulates the resolution result with typed properties. Consider marking the class as readonly for full immutability (PHP 8.2+), but this is optional.

app-modules/message/src/Actions/NewMessage.php (1)

23-43: Good refactor: Cleaner separation of concerns.

Delegating user/character resolution to UserCharacterResolver improves maintainability and reusability. The flow is now clearer:

  1. Resolve user/character/provider
  2. Calculate experience
  3. Update character
  4. Persist message

Ensure UserCharacterResolver is wrapped in a transaction (as noted in my other comment) for atomicity.

Comment thread app-modules/bot-discord/src/Actions/UserCharacterResolver.php
Comment thread app-modules/bot-discord/src/Actions/UserCharacterResolver.php
Comment thread app-modules/bot-discord/src/Events/MessageReceivedEvent.php
Comment thread app-modules/bot-discord/src/Events/WelcomeMember.php
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: 0

♻️ Duplicate comments (2)
app-modules/bot-discord/src/Actions/UserCharacterResolver.php (2)

27-55: Wrap the multi-step new-user creation in a DB transaction to avoid partial records

User, Address, Information, Character, and Provider are all created in sequence with no transaction. If any step after User::create() fails, you can end up with inconsistent or orphaned data (e.g., User without Character/Provider), especially under failure or retry scenarios.

You can make this atomic by wrapping the whole branch in a transaction and returning the DTO from inside the closure, e.g.:

+use Illuminate\Support\Facades\DB;
@@
-        if (! $providerEntity) {
-            $user = User::query()->create([
+        if (! $providerEntity) {
+            return DB::transaction(function () use ($provider, $providerId, $username, $tenantId) {
+                $user = User::query()->create([
                     'id' => Uuid::uuid4()->toString(),
                     'username' => $username,
                     'name' => $username,
                     'is_donator' => false,
-            ]);
+                ]);
@@
-            $providerEntity = $user->providers()->create([
+                $providerEntity = $user->providers()->create([
                     'tenant_id' => $tenantId,
                     'model_type' => User::class,
                     'provider' => $provider,
                     'provider_id' => $providerId,
-            ]);
-
-            return new ResolvedUserCharacter(
-                user: $user,
-                provider: $providerEntity,
-                character: $character,
-                isNewUser: true
-            );
+                ]);
+
+                return new ResolvedUserCharacter(
+                    user: $user,
+                    provider: $providerEntity,
+                    character: $character,
+                    isNewUser: true,
+                );
+            });
         }

This keeps your resolve contract but ensures all-or-nothing persistence.


59-63: Handling existing users without a Character for this tenant

For existing providers you do:

$character = Character::query()
    ->where('tenant_id', $tenantId)
    ->where('user_id', $user->id)
    ->firstOrFail();

If the user exists (via this provider) but hasn’t yet got a Character for the current tenant_id, firstOrFail() will throw and break the resolution path. Given the method name (resolve) and the new-user branch behavior, it might be more useful to create the Character when missing, similar to the new-user flow, e.g.:

$character = Character::query()
    ->where('tenant_id', $tenantId)
    ->where('user_id', $user->id)
    ->first();

if (! $character) {
    $character = $user->character()->create([
        'tenant_id' => $tenantId,
    ]);
}

If you also care about distinguishing “new character for this tenant” from “existing character”, you may want to extend ResolvedUserCharacter with an isNewCharacter flag or derive that elsewhere.

🧹 Nitpick comments (3)
app-modules/bot-discord/src/DTO/ResolvedUserCharacter.php (1)

11-18: Consider making this DTO immutable to better express value-object semantics

Given this is just a data carrier, you may want to prevent accidental mutation by using readonly (PHP ≥ 8.1), e.g. readonly final class ResolvedUserCharacter or public readonly User $user etc. This keeps intent clear without changing current behavior.

app-modules/bot-discord/src/Actions/UserCharacterResolver.php (2)

16-21: Double-check int $tenantId matches your actual tenant_id column type

tenant_id on characters and providers often ends up as a UUID/string in multi-tenant setups (especially with HasUuids on related models). Here it’s typed as int, which may rely on implicit casting or be incorrect if the DB column is not numeric.

If tenant_id is a UUID or string, consider changing the signature to string $tenantId (or a UUID value object) to avoid subtle type mismatches.


22-25: Provider lookup ignores tenant and model_type — confirm this won’t cross tenants unexpectedly

The lookup:

$providerEntity = Provider::query()
    ->where('provider', $provider)
    ->where('provider_id', $providerId)
    ->first();

does not filter by tenant_id or model_type, even though you set both when creating the Provider. If the same provider_id can appear under multiple tenants, this may resolve users across tenants unintentionally.

Consider whether you should also filter by tenant_id (and possibly model_type), e.g.:

->where('tenant_id', $tenantId)
->where('model_type', User::class)

if that matches your intended data model.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eba8c94 and 4889e14.

📒 Files selected for processing (2)
  • app-modules/bot-discord/src/Actions/UserCharacterResolver.php (1 hunks)
  • app-modules/bot-discord/src/DTO/ResolvedUserCharacter.php (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app-modules/bot-discord/src/DTO/ResolvedUserCharacter.php (2)
app-modules/character/src/Models/Character.php (1)
  • Character (28-128)
app-modules/provider/src/Models/Provider.php (1)
  • Provider (28-94)

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: 1

🧹 Nitpick comments (1)
app-modules/bot-discord/src/SlashCommands/CodeCommand.php (1)

24-24: Improve the command description for clarity.

The description "The Code Command command." is redundant and uninformative. Consider a more descriptive message that explains the command's purpose.

Apply this diff:

-    protected $description = 'The Code Command command.';
+    protected $description = 'Shows how to format code in Discord messages.';
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4889e14 and dcdc809.

📒 Files selected for processing (1)
  • app-modules/bot-discord/src/SlashCommands/CodeCommand.php (1 hunks)

Comment thread app-modules/bot-discord/src/SlashCommands/CodeCommand.php
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: 0

🧹 Nitpick comments (3)
app-modules/provider/src/Models/Provider.php (1)

24-24: LGTM! Good documentation improvement.

The PHPDoc annotation correctly documents the existing tenant_id property and improves IDE support.

For consistency with other relationships in this model (user(), tokens(), messages()), consider adding a tenant() relationship method:

/**
 * @return BelongsTo<Tenant, $this>
 */
public function tenant(): BelongsTo
{
    return $this->belongsTo(Tenant::class);
}

This would allow eager loading and relationship queries like $provider->tenant or Provider::with('tenant').

composer.json (2)

18-49: New bot & framework dependencies: double‑check BrowserKit belongs in require

Adding he4rt/bot-discord and laracord/framework here fits the new Discord bot module and looks consistent with the rest of the he4rt stack. One thing to verify: symfony/browser-kit is pinned in require, but it is commonly used only for testing. If you don’t rely on it at runtime, consider moving it to require-dev to keep the production dependency surface smaller.


157-170: minimum-stability: dev + custom repos: confirm this is intentionally broad

Setting "minimum-stability": "dev" with "prefer-stable": true, plus adding a wildcard path repo for app-modules/* and a VCS repo for laracord-framework, is a valid monorepo/dev flow but slightly broad: Composer can now choose dev versions for non‑he4rt packages if constraints allow. If your only dev requirement is laracord/framework (or specific local modules), you might prefer keeping global minimum-stability at stable and relying on per‑package dev constraints instead. Please confirm this behavior matches what you want from composer update.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dcdc809 and f30f868.

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • app-modules/provider/src/Models/Provider.php (1 hunks)
  • bootstrap/providers.php (2 hunks)
  • composer.json (5 hunks)
🔇 Additional comments (1)
bootstrap/providers.php (1)

5-27: Registering DiscordServiceProvider at the end of the provider list looks fine

The import and registration are consistent with the other app providers, and placing the Discord provider after the core ones should work well as long as it only depends on services they already register. No issues from this diff.

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: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f30f868 and a642aa7.

📒 Files selected for processing (4)
  • app-modules/message/tests/Feature/NewMessageTest.php (3 hunks)
  • app-modules/message/tests/Unit/Actions/NewMessageTest.php (1 hunks)
  • app-modules/message/tests/Unit/Actions/PersistMessageTest.php (1 hunks)
  • app-modules/user/tests/Feature/UpdateProfileTest.php (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • app-modules/message/tests/Feature/NewMessageTest.php
⏰ 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). (1)
  • GitHub Check: Perform Pest Tests / Run
🔇 Additional comments (1)
app-modules/message/tests/Unit/Actions/PersistMessageTest.php (1)

17-26: LGTM - Test updated correctly for new DTO signature.

The test has been properly updated to include the providerUsername parameter and uses named arguments for clarity. The changes align with the updated NewMessageDTO constructor signature.

Comment thread app-modules/message/tests/Unit/Actions/NewMessageTest.php
Comment thread app-modules/user/tests/Feature/UpdateProfileTest.php
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: 1

🧹 Nitpick comments (1)
composer.json (1)

18-34: Review loose version constraints on he4rt/ packages with dev minimum-stability.*

All he4rt/* packages use >=1 constraints (lines 18, 16-34), which allow any major version. Combined with minimum-stability: dev, this could introduce unexpected major version changes without explicit opt-in.

Consider tightening constraints to ^X.Y.Z or ~X.Y.Z (e.g., ^1.0.0 instead of >=1) to prevent unintended major version jumps, especially if these are internal packages under active development.

Please review whether >=1 is intentional for all he4rt/* packages or if they should use more specific version ranges.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a642aa7 and 3a83b25.

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • .github/workflows/_phpstan.yml (1 hunks)
  • app-modules/bot-discord/src/SlashCommands/DynamicVoiceCommand.php (1 hunks)
  • app-modules/bot-discord/src/Tasks/DynamicVoiceTask.php (1 hunks)
  • composer.json (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • app-modules/bot-discord/src/SlashCommands/DynamicVoiceCommand.php
  • app-modules/bot-discord/src/Tasks/DynamicVoiceTask.php
🔇 Additional comments (4)
.github/workflows/_phpstan.yml (1)

49-51: I'll help you verify the review comment. Let me check if the .env.testing.example file exists in the repository.
<function_calls>

#!/bin/bash

Verify that .env.testing.example exists in the repository root

fd -t f '.env.testing.example' . --max-depth 1


</function_calls>

composer.json (3)

168-170: Verify VCS repository configuration for laracord-framework fork.

The custom VCS repository points to a fork at https://github.com/danielhe4rt/laracord-framework. Ensure:

  1. The repository is accessible and correctly configured
  2. This is an intentional architectural decision (not a temporary workaround)
  3. There's a plan to migrate back to the upstream package if/when the needed changes are merged

Please confirm the repository URL is correct and accessible. If this is a permanent fork, document why the upstream package doesn't meet requirements.


47-47: Verify symfony/browser-kit v7.0.8 compatibility with other dependencies.

The pinned version v7.0.8 is specific, which is good for reproducibility. However, verify it doesn't conflict with symfony versions required by:

  • laravel/framework: ^12.40.1
  • filament/filament: ^4.2.3
  • Other packages that may have implicit symfony dependencies

Run composer update --dry-run to verify no version conflicts exist before merging.


157-157: I'll verify the review comment concerns about minimum-stability: dev in the composer.json file. Let me gather information about the PR context, dependency strategy, and whether this is properly justified.
<function_calls>

Examine the full composer.json to understand dependency structure and configuration
#!/bin/bash

Read composer.json to see full dependency structure

cat composer.json | head -200


</function_calls>
<function_calls>

Check for PR description and comments that might explain the minimum-stability decision
#!/bin/bash

Check PR title, description, and comments using GitHub CLI

gh pr view --json body,title,comments


</function_calls>
<function_calls>

Look for CI/testing configuration files that might explain risk mitigation
#!/bin/bash

Find common CI configuration files

fd -t f '(.github|.gitlab|.circleci|phpunit|pytest|package.json)' --max-depth 3 | head -20


</function_calls>

Comment thread composer.json
@danielhe4rt danielhe4rt merged commit c8ce8d0 into 3.x Nov 26, 2025
6 checks passed
@danielhe4rt danielhe4rt deleted the feat/discord-bot branch November 26, 2025 16:34
@coderabbitai coderabbitai Bot mentioned this pull request Dec 8, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Dec 19, 2025
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.

5 participants