Seamless Role Management: Discord and Laravel Unite

Seamless Role Management: Discord and Laravel Unite

Being part of our GTA:RP server management team, I've come to realize the importance of effective organization and communication. With a diverse team of about 30 individuals and a multi-level hierarchy, maintaining roles and permissions consistently across platforms was quite a puzzle. Here's how, in my role as the Development Team Lead, I stumbled upon a simple yet effective solution that uses Discord roles to seamlessly manage our Laravel Nova Backoffice.

Solving the Role Puzzle

Our GTA:RP server community is like a well-oiled machine, with different roles like Head Admins, Team Leads, and Team Members working together. However, keeping track of roles and permissions between Discord and our Laravel Nova Backoffice wasn't as smooth as we wanted it to be. The discrepancies caused confusion, leading us to think about a better way.

An Unexpected Solution - Discord Role Integration

In my quest to find a solution, I realized that the key might be in the very thing we were already using - Discord roles. Why not use them as the foundation for our Backoffice roles? This simple idea ignited the spark for an integration that promised to eliminate inconsistencies and streamline our workflow.

Practical Benefits:

  1. Uniformity Across Platforms: By integrating Discord roles into our Backoffice, we achieved a unified roles system that eliminated any mismatch.
  2. Time Saved, Errors Reduced: The days of manually managing roles were gone, replaced by an automated process that saved time and minimized errors.
  3. Simpler Onboarding: Adding new members became hassle-free – correctly assigning Discord roles automatically set up their Backoffice roles.
  4. Boosted Security: Aligning Backoffice roles with Discord roles tightened our community's security, ensuring the right access for the right people.
  5. Adapting to Change: As our community evolved, our integration adapted smoothly, accommodating changes without a hitch.

Implementing Discord Role Integration

Let's take a look at how we implemented the integration using code snippets. Below is how we defined the relevant Discord roles using enums:

php
enum DiscordRole: string
{
    case LEAD_ADMINISTRATOR = 'xxxxxxxxxxxxxxxxx';  // Anonymized role ID
    // ... other roles
    case SUPPORTER = 'xxxxxxxxxxxxxxxxx';  // Anonymized role ID
}

Here's the method on the User model to retrieve the Discord roles and check if the user has any of the given roles:

php
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    // ... other methods

    /**
     * @return Collection<array-key, DiscordRole>
     */
    public function getDiscordRolesAttribute(): Collection
    {
        if ($this->discord_id === null) {
            return collect();
        }

        return rescue(
            callback: fn () => Cache::swr(
                key: $this->getCacheKey('discord_roles'),
                tts: CarbonInterval::minute()->totalSeconds,
                ttl: CarbonInterval::day()->totalSeconds,
                callback: function (): Collection {
                    $member = app(DiscordConnector::class)->guild()->getGuildMember(
                        guildId: new Snowflake(config('services.discord.guild_id')),
                        userId: new Snowflake($this->discord_id),
                    );

                    return collect($member->roles)
                        ->map(fn (string $id) => DiscordRole::tryFrom($id))
                        ->filter()
                        ->values();
                }
            ),
            rescue: collect(),
        );
    }

    public function hasDiscordRole(DiscordRole ...$roles): bool
    {
        return $this->discord_roles->contains(fn (DiscordRole $role): bool => in_array($role, $roles, true));
    }
}

In case you wonder about the logic within the User->getDiscordRolesAttribute() method, it uses a Discord SDK I'm working on right now. It's powered by Saloon and provides access to the Discord API with simple HTTP API calls instead of the websocket approach.

Here's an example policy that uses the Discord role check to control access within our Laravel Nova Backoffice:

php
class PostPolicy
{
    public function viewAny(AuthUser $auth): bool
    {
        return $auth->hasDiscordRole(
            DiscordRole::LEAD_SUPPORTER,
            DiscordRole::ADMINISTRATOR,
        );
    }

    // ... other methods
}

By integrating these checks, our Backoffice access was seamlessly controlled based on the users' Discord roles.

Continuing the Journey

The beauty of this integration lies in its simplicity and practicality. It's not a masterpiece, but it's a testament to how ingenuity and teamwork can lead to effective solutions in our unique community.

Conclusion

Our GTA:RP server community's journey is a reminder that innovation doesn't have to be complex to be effective. Integrating Discord roles with Laravel Nova Backoffice is a solution that has helped us work better together, without any unnecessary showmanship. As we move forward, I'm excited to see how this integration will continue to evolve alongside our community, proving that sometimes the simplest solutions are the most valuable.