Skip to content

Commit

Permalink
feat(user): fully support the Visible Role field (#3087)
Browse files Browse the repository at this point in the history
wescopeland authored Jan 23, 2025

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent f0cf8db commit ea7a001
Showing 24 changed files with 401 additions and 56 deletions.
9 changes: 5 additions & 4 deletions app/Community/Components/UserCard.php
Original file line number Diff line number Diff line change
@@ -63,6 +63,7 @@ function () use ($username): ?array {
return $foundUser ? [
...$foundUser->toArray(),
'isMuted' => $foundUser->isMuted(),
'visibleRoleName' => $foundUser->visible_role?->name,
] : null;
}
);
@@ -72,7 +73,7 @@ private function buildAllCardViewValues(string $username, array $rawUserData): a
{
$cardBioData = $this->buildCardBioData($rawUserData);
$cardRankData = $this->buildCardRankData($username, $rawUserData['RAPoints'], $rawUserData['RASoftcorePoints'], $rawUserData['Untracked'] ? true : false);
$cardRoleData = $this->buildCardRoleData($username, $rawUserData['Permissions']);
$cardRoleData = $this->buildCardRoleData($username, $rawUserData['visibleRoleName']);

return array_merge($cardBioData, $cardRankData, $cardRoleData);
}
@@ -147,10 +148,10 @@ private function buildCardRankData(string $username, int $hardcorePoints, int $s
);
}

private function buildCardRoleData(string $username, int $permissions): array
private function buildCardRoleData(string $username, ?string $visibleRoleName): array
{
$canShowUserRole = $permissions >= Permissions::JuniorDeveloper;
$roleLabel = Permissions::toString($permissions);
$canShowUserRole = $visibleRoleName !== null;
$roleLabel = $visibleRoleName ? __('permission.role.' . $visibleRoleName) : null;

$useExtraNamePadding =
$canShowUserRole
39 changes: 39 additions & 0 deletions app/Community/Concerns/ActsAsCommunityMember.php
Original file line number Diff line number Diff line change
@@ -16,9 +16,11 @@
use App\Models\UserComment;
use App\Models\UserGameListEntry;
use App\Models\UserRelation;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Auth;
use Spatie\Permission\Models\Role as SpatieRole;

// TODO organize accessors, relations, and scopes

@@ -28,6 +30,34 @@ public static function bootActsAsCommunityMember(): void
{
}

public function getVisibleRoleAttribute(): ?SpatieRole
{
// Load the user's displayable roles.
if ($this->relationLoaded('roles')) {
/** @var Collection<int, SpatieRole> $displayableRoles */
$displayableRoles = $this->roles->where('display', '>', 0);
} else {
/** @var Collection<int, SpatieRole> $displayableRoles */
$displayableRoles = $this->displayableRoles()->get();
}

// If user has an explicitly set visible_role_id, we'll try to show it.
// However, we need to verify it's still a valid displayable role for the
// user (it's possible they lost the role at some point).
if ($this->visible_role_id !== null) {
/** @var SpatieRole|null $explicitRole */
$explicitRole = $displayableRoles->find($this->visible_role_id);
if ($explicitRole) {
return $explicitRole;
}
}

// Otherwise, fall back to highest ordered displayable role.
// For most users, this will return null.
/** @var SpatieRole|null */
return $displayableRoles->first();
}

/**
* @return HasMany<UserActivity>
*/
@@ -50,6 +80,15 @@ public function gameListEntries(?string $type = null): HasMany
return $query;
}

/**
* @return BelongsToMany<SpatieRole>
*/
public function displayableRoles(): BelongsToMany
{
/** @var BelongsToMany<SpatieRole> */
return $this->roles()->where('display', '>', 0);
}

/**
* @return BelongsToMany<User>
*/
20 changes: 19 additions & 1 deletion app/Community/Controllers/UserSettingsController.php
Original file line number Diff line number Diff line change
@@ -18,14 +18,17 @@
use App\Community\Requests\UpdatePasswordRequest;
use App\Community\Requests\UpdateProfileRequest;
use App\Community\Requests\UpdateWebsitePrefsRequest;
use App\Data\RoleData;
use App\Data\UserData;
use App\Data\UserPermissionsData;
use App\Enums\Permissions;
use App\Enums\UserPreference;
use App\Http\Controller;
use App\Models\Role;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Inertia\Inertia;
use Inertia\Response as InertiaResponse;
@@ -39,6 +42,10 @@ public function show(): InertiaResponse
/** @var User $user */
$user = Auth::user();

$user->load(['roles' => function ($query) {
$query->where('display', '>', 0);
}]);

$userSettings = UserData::fromUser($user)->include(
'apiKey',
'deleteRequested',
@@ -54,7 +61,18 @@ public function show(): InertiaResponse
'updateMotto'
);

$props = new UserSettingsPagePropsData($userSettings, $can);
/** @var Collection<int, Role> $displayableRoles */
$displayableRoles = $user->roles;

$mappedRoles = $displayableRoles->map(fn ($role) => RoleData::fromRole($role))
->values()
->all();

$props = new UserSettingsPagePropsData(
$userSettings,
$can,
$mappedRoles,
);

return Inertia::render('settings', $props);
}
3 changes: 3 additions & 0 deletions app/Community/Data/UpdateProfileData.php
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ class UpdateProfileData extends Data
public function __construct(
public string $motto,
public bool $userWallActive,
public ?int $visibleRoleId,
) {
}

@@ -20,6 +21,7 @@ public static function fromRequest(UpdateProfileRequest $request): self
return new self(
motto: $request->motto,
userWallActive: $request->userWallActive,
visibleRoleId: $request->visibleRoleId,
);
}

@@ -28,6 +30,7 @@ public function toArray(): array
return [
'Motto' => $this->motto,
'UserWallActive' => $this->userWallActive,
'visible_role_id' => $this->visibleRoleId,
];
}
}
3 changes: 3 additions & 0 deletions app/Community/Data/UserSettingsPagePropsData.php
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@

namespace App\Community\Data;

use App\Data\RoleData;
use App\Data\UserData;
use App\Data\UserPermissionsData;
use Spatie\LaravelData\Data;
@@ -15,6 +16,8 @@ class UserSettingsPagePropsData extends Data
public function __construct(
public UserData $userSettings,
public UserPermissionsData $can,
/** @var RoleData[] */
public array $displayableRoles,
) {
}
}
1 change: 1 addition & 0 deletions app/Community/Requests/UpdateProfileRequest.php
Original file line number Diff line number Diff line change
@@ -31,6 +31,7 @@ public function rules(): array
return [
'motto' => 'nullable|string|max:50',
'userWallActive' => 'nullable|boolean',
'visibleRoleId' => 'nullable|integer',
];
}

27 changes: 27 additions & 0 deletions app/Data/RoleData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace App\Data;

use Spatie\LaravelData\Data;
use Spatie\Permission\Models\Role;
use Spatie\TypeScriptTransformer\Attributes\TypeScript;

#[TypeScript('Role')]
class RoleData extends Data
{
public function __construct(
public int $id,
public string $name,
) {
}

public static function fromRole(Role $role): self
{
return new self(
id: $role->id,
name: $role->name,
);
}
}
10 changes: 5 additions & 5 deletions app/Data/UserData.php
Original file line number Diff line number Diff line change
@@ -4,7 +4,6 @@

namespace App\Data;

use App\Enums\Permissions;
use App\Models\User;
use App\Platform\Enums\PlayerPreferredMode;
use Illuminate\Support\Carbon;
@@ -24,6 +23,8 @@ public function __construct(
public Lazy|string|null $apiKey = null,
public Lazy|string|null $deleteRequested = null,
public Lazy|Carbon|null $deletedAt = null,
/** @var RoleData[] */
public Lazy|array|null $displayableRoles = null,
public Lazy|string|null $emailAddress = null,
public Lazy|int $id = 0,
public Lazy|bool $isMuted = false,
@@ -39,7 +40,7 @@ public function __construct(
public Lazy|int|null $unreadMessageCount = null,
public Lazy|string|null $username = null,
public Lazy|bool|null $userWallActive = null,
public Lazy|string|null $visibleRole = null,
public Lazy|RoleData|null $visibleRole = null,
public Lazy|int|null $websitePrefs = null,

#[TypeScriptType([
@@ -64,8 +65,6 @@ public static function fromRecentForumTopic(array $topic): self

public static function fromUser(User $user): self
{
$legacyPermissions = (int) $user->getAttribute('Permissions');

return new self(
// == eager fields
displayName: $user->display_name,
@@ -75,6 +74,7 @@ public static function fromUser(User $user): self
apiKey: Lazy::create(fn () => $user->APIKey),
deletedAt: Lazy::create(fn () => $user->Deleted ? Carbon::parse($user->Deleted) : null),
deleteRequested: Lazy::create(fn () => $user->DeleteRequested),
displayableRoles: Lazy::create(fn () => $user->displayableRoles),
emailAddress: Lazy::create(fn () => $user->EmailAddress),
mutedUntil: Lazy::create(fn () => $user->muted_until),
id: Lazy::create(fn () => $user->id),
@@ -97,7 +97,7 @@ public static function fromUser(User $user): self
unreadMessageCount: Lazy::create(fn () => $user->UnreadMessageCount),
username: Lazy::create(fn () => $user->username),
userWallActive: Lazy::create(fn () => $user->UserWallActive),
visibleRole: Lazy::create(fn () => $legacyPermissions > 1 ? Permissions::toString($legacyPermissions) : null),
visibleRole: Lazy::create(fn () => $user->visible_role ? RoleData::fromRole($user->visible_role) : null),
websitePrefs: Lazy::create(fn () => $user->websitePrefs),
);
}
3 changes: 2 additions & 1 deletion app/Http/Middleware/HandleInertiaRequests.php
Original file line number Diff line number Diff line change
@@ -48,14 +48,15 @@ public function share(Request $request): array
'isNew',
'legacyPermissions',
'locale',
'mutedUntil',
'playerPreferredMode',
'points',
'pointsSoftcore',
'mutedUntil',
'preferences',
'roles',
'unreadMessageCount',
'username',
'visibleRole',
'websitePrefs',
),
] : null,
1 change: 1 addition & 0 deletions app/Models/User.php
Original file line number Diff line number Diff line change
@@ -164,6 +164,7 @@ class User extends Authenticatable implements CommunityMember, Developer, HasLoc
'Untracked',
'User', // fillable for registration
'UserWallActive',
'visible_role_id',
'websitePrefs',
];

1 change: 1 addition & 0 deletions database/factories/UserFactory.php
Original file line number Diff line number Diff line change
@@ -44,6 +44,7 @@ public function definition(): array
'Untracked' => 0,
'UserWallActive' => 1,
'muted_until' => null,
'visible_role_id' => null,

// nullable
'APIKey' => 'apiKey',
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class() extends Migration {
public function up(): void
{
Schema::table('UserAccounts', function (Blueprint $table) {
$table->unsignedBigInteger('visible_role_id')->nullable()->after('display_name');

$table->foreign('visible_role_id')
->references('id')
->on('auth_roles')
->onDelete('set null');

$table->index('visible_role_id');
});
}

public function down(): void
{
Schema::table('UserAccounts', function (Blueprint $table) {
$table->dropForeign(['visible_role_id']);
$table->dropIndex(['visible_role_id']);
$table->dropColumn('visible_role_id');
});
}
};
4 changes: 2 additions & 2 deletions lang/en/permission.php
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
return [
'role' => [
Role::ROOT => 'Root',
Role::ADMINISTRATOR => __('Administrator'),
Role::ADMINISTRATOR => __('Admin'),

// moderation & platform staff roles

@@ -23,7 +23,7 @@
Role::ARTIST => __('Artist'),
Role::WRITER => __('Writer'),
Role::GAME_EDITOR => __('Game Editor'),
Role::PLAY_TESTER => __('Play Tester'),
Role::PLAY_TESTER => __('Playtester'),

// community staff roles

24 changes: 23 additions & 1 deletion lang/en_US.json
Original file line number Diff line number Diff line change
@@ -641,5 +641,27 @@
"news-category.technical": "Technical",
"Don't ask for links to copyrighted ROMs. Don't share links to copyrighted ROMs.": "Don't ask for links to copyrighted ROMs. Don't share links to copyrighted ROMs.",
"Start new topic": "Start new topic",
"enter your new topic's title...": "enter your new topic's title..."
"enter your new topic's title...": "enter your new topic's title...",
"administrator": "Admin",
"game-hash-manager": "Hash Manager",
"release-manager": "Release Manager",
"developer": "Developer",
"developer-junior": "Junior Developer",
"artist": "Artist",
"writer": "Writer",
"play-tester": "Playtester",
"moderator": "Moderator",
"forum-manager": "Forum Manager",
"ticket-manager": "Ticket Manager",
"news-manager": "News Manager",
"event-manager": "Event Manager",
"founder": "Founder",
"architect": "Architect",
"engineer": "Engineer",
"developer-retired": "Developer (retired)",
"game-editor": "Game Editor",
"team-account": "Team Account",
"dev-compliance": "Developer Compliance",
"code-reviewer": "Code Reviewer",
"community-manager": "Community Manager"
}
Loading

0 comments on commit ea7a001

Please sign in to comment.