Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: fully migrate hash management to filament #2975

Merged
Merged
21 changes: 20 additions & 1 deletion app/Filament/Resources/GameHashResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
use App\Models\GameHash;
use App\Models\System;
use Filament\Forms\Form;
use Filament\Infolists\Infolist;
use Filament\Resources\Pages\Page;
use Filament\Support\Enums\FontFamily;
use Filament\Tables;
use Filament\Tables\Table;
Expand All @@ -26,6 +28,14 @@ class GameHashResource extends Resource

protected static ?int $navigationSort = 40;

public static function infolist(Infolist $infolist): Infolist
{
return $infolist
->schema([
// TODO
]);
}

public static function form(Form $form): Form
{
return $form
Expand All @@ -43,7 +53,7 @@ public static function table(Table $table): Table
->label('MD5')
->searchable()
->fontFamily(FontFamily::Mono)
->url(fn (GameHash $record): string => route('game.hash.manage', ['game' => $record->game]))
->url(fn (GameHash $record): string => route('filament.admin.resources.games.hashes', ['record' => $record->game]))
->toggleable(isToggledHiddenByDefault: false),

Tables\Columns\TextColumn::make('game.title')
Expand Down Expand Up @@ -108,13 +118,22 @@ public static function getRelations(): array
];
}

public static function getRecordSubNavigation(Page $page): array
{
return $page->generateNavigationItems([
Pages\AuditLog::class,
]);
}

public static function getPages(): array
{
return [
'index' => Pages\Index::route('/'),
'audit-log' => Pages\AuditLog::route('/{record}/audit-log'),
// TODO
// 'create' => Pages\Create::route('/create'),
// 'edit' => Pages\Edit::route('/{record}/edit'),
// 'view' => Pages\Details::route('/{record}'),
];
}

Expand Down
11 changes: 11 additions & 0 deletions app/Filament/Resources/GameHashResource/Pages/AuditLog.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace App\Filament\Resources\GameHashResource\Pages;

use App\Filament\Pages\ResourceAuditLog;
use App\Filament\Resources\GameHashResource;

class AuditLog extends ResourceAuditLog
{
protected static string $resource = GameHashResource::class;
}
19 changes: 19 additions & 0 deletions app/Filament/Resources/GameHashResource/Pages/Details.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace App\Filament\Resources\GameHashResource\Pages;

use App\Filament\Resources\GameHashResource;
use Filament\Resources\Pages\ViewRecord;

class Details extends ViewRecord
{
protected static string $resource = GameHashResource::class;

protected function getHeaderActions(): array
{
return [
];
}
}
4 changes: 2 additions & 2 deletions app/Filament/Resources/GameResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
use App\Filament\Resources\GameResource\RelationManagers\AchievementSetsRelationManager;
use App\Filament\Resources\GameResource\RelationManagers\AchievementsRelationManager;
use App\Filament\Resources\GameResource\RelationManagers\CoreSetAuthorshipCreditsRelationManager;
use App\Filament\Resources\GameResource\RelationManagers\GameHashesRelationManager;
use App\Filament\Resources\GameResource\RelationManagers\LeaderboardsRelationManager;
use App\Filament\Resources\GameResource\RelationManagers\MemoryNotesRelationManager;
use App\Filament\Rules\ExistsInForumTopics;
Expand Down Expand Up @@ -476,7 +475,6 @@ public static function getRelations(): array
AchievementsRelationManager::class,
AchievementSetsRelationManager::class,
LeaderboardsRelationManager::class,
GameHashesRelationManager::class,
MemoryNotesRelationManager::class,
CoreSetAuthorshipCreditsRelationManager::class,
];
Expand All @@ -486,6 +484,7 @@ public static function getRecordSubNavigation(Page $page): array
{
return $page->generateNavigationItems([
Pages\Details::class,
Pages\Hashes::class,
Pages\AuditLog::class,
]);
}
Expand All @@ -497,6 +496,7 @@ public static function getPages(): array
'create' => Pages\Create::route('/create'),
'view' => Pages\Details::route('/{record}'),
'edit' => Pages\Edit::route('/{record}/edit'),
'hashes' => Pages\Hashes::route('/{record}/hashes'),
'audit-log' => Pages\AuditLog::route('/{record}/audit-log'),
];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,53 @@

declare(strict_types=1);

namespace App\Filament\Resources\GameResource\RelationManagers;
namespace App\Filament\Resources\GameResource\Pages;

use App\Community\Enums\ArticleType;
use App\Filament\Resources\GameHashResource;
use App\Filament\Resources\GameResource;
use App\Models\Comment;
use App\Models\Game;
use App\Models\GameHash;
use App\Models\User;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Resources\Pages\ManageRelatedRecords;
use Filament\Support\Enums\FontFamily;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use Livewire;

class GameHashesRelationManager extends RelationManager
class Hashes extends ManageRelatedRecords
{
protected static string $resource = GameResource::class;

protected static string $relationship = 'hashes';

public static function canViewForRecord(Model $ownerRecord, string $pageClass): bool
protected static ?string $navigationIcon = 'fas-file-archive';

public static function canAccess(array $arguments = []): bool
{
/** @var User $user */
$user = Auth::user();

return $user->can('manage', GameHash::class);
}

public function form(Form $form): Form
public static function getNavigationBadge(): ?string
{
return $form
->schema([]);
return (string) Livewire::current()->getRecord()->hashes->count();
}

public function table(Table $table): Table
{
// TODO migrate to filament-comments
$nonAutomatedCommentsCount = Comment::where('ArticleType', ArticleType::GameHash)
->where('ArticleID', $this->ownerRecord->id)
->where('ArticleID', $this->getOwnerRecord()->id)
->notAutomated()
->count();

return $table
->recordTitleAttribute('name')
->columns([
Tables\Columns\TextColumn::make('name')
->label('File Name'),
Expand All @@ -71,10 +75,15 @@ public function table(Table $table): Table
Tables\Actions\Action::make('view-comments')
->color($nonAutomatedCommentsCount > 0 ? 'info' : 'gray')
->label("View Comments ({$nonAutomatedCommentsCount})")
->url(route('game.hashes.comment.index', ['game' => $this->ownerRecord->id])),
->url(route('game.hashes.comment.index', ['game' => $this->getOwnerRecord()->id])),
])
->actions([
Tables\Actions\Action::make('audit-log')
->url(fn ($record) => GameHashResource::getUrl('audit-log', ['record' => $record]))
->icon('fas-clock-rotate-left'),

Tables\Actions\EditAction::make()
->modalHeading(fn (GameHash $record) => "Edit game hash {$record->md5}")
->form([
Forms\Components\Section::make()
->description("
Expand Down Expand Up @@ -102,7 +111,20 @@ public function table(Table $table): Table
->label('Resource Page URL')
->helperText('Do not link to a commercially-sold ROM. Link to a specific No Intro, Redump, RHDN, SMWCentral, itch.io, etc. page.')
->activeUrl(),
]),
])
->afterStateUpdated(function ($state, $old, $record) {
$changedAttributes = [];
foreach ($state as $key => $value) {
if (!isset($old[$key]) || $old[$key] !== $value) {
$key = match ($key) {
'name' => 'Name',
'labels' => 'Labels',
default => $key,
};
$changedAttributes[$key] = $value;
}
}
}),
]),

Tables\Actions\Action::make('unlink')
Expand Down Expand Up @@ -147,7 +169,6 @@ public function table(Table $table): Table
->event('unlinkedHash')
->log('Unlinked hash');

// TODO remove this after deleting 'game.hash.manage' route
addArticleComment(
"Server",
ArticleType::GameHash,
Expand All @@ -161,6 +182,10 @@ public function table(Table $table): Table

return $user->can('forceDelete', $gameHash);
}),
]);
])
->bulkActions([

])
->paginated(false);
}
}
5 changes: 0 additions & 5 deletions app/Models/GameHash.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,6 @@ class GameHash extends BaseModel
'regions' => 'json',
];

public function getRouteKeyName(): string
{
return 'hash';
}

// == logging

public function getActivitylogOptions(): LogOptions
Expand Down
86 changes: 2 additions & 84 deletions app/Platform/Controllers/GameHashController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,14 @@

namespace App\Platform\Controllers;

use App\Community\Enums\ArticleType;
use App\Data\UserPermissionsData;
use App\Http\Controller;
use App\Models\Game;
use App\Models\GameHash;
use App\Models\User;
use App\Platform\Data\GameData;
use App\Platform\Data\GameHashData;
use App\Platform\Data\GameHashesPagePropsData;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Inertia\Inertia;
use Inertia\Response as InertiaResponse;

Expand Down Expand Up @@ -48,89 +44,11 @@ public function edit(GameHash $gameHash): void
{
}

public function update(Request $request, GameHash $gameHash): JsonResponse
public function update(Request $request, GameHash $gameHash): void
{
$this->authorize('update', $this->resourceClass());

$input = $request->validate([
'name' => 'required|string',
'labels' => 'required|string',
'patch_url' => [
'nullable',
'url',
'regex:/^https:\/\/github\.com\/RetroAchievements\/RAPatches\/raw\/(?:refs\/heads\/)?main\/.*\.(zip|7z)$/i',
],
'source' => 'nullable|url',
]);

$originalAttributes = $gameHash->getOriginal();
$updatedAttributes = [
'name' => $input['name'],
'labels' => $input['labels'],
'patch_url' => $input['patch_url'] ?? null,
'source' => $input['source'] ?? null,
];

$changedAttributes = array_filter($updatedAttributes, function ($value, $key) use ($originalAttributes) {
return $originalAttributes[$key] != $value;
}, ARRAY_FILTER_USE_BOTH);
$isChanging = !empty($changedAttributes);

if (!$isChanging) {
return response()->json(['message' => 'No changes were made.'], 200);
}

$gameHash->update($updatedAttributes);

/** @var User $user */
$user = Auth::user();
$this->logGameHashUpdate($gameHash, $changedAttributes, $user);

return response()->json(['message' => __('legacy.success.update')]);
}

public function destroy(GameHash $gameHash): JsonResponse
public function destroy(GameHash $gameHash): void
{
$gameId = $gameHash->game_id;
$hash = $gameHash->md5;
$user = Auth::user()->User;

$wasDeleted = $gameHash->forceDelete();

if (!$wasDeleted) {
return response()->json(['message' => 'Failed to delete the game hash.'], 500);
}

// Log the hash deletion.
addArticleComment("Server", ArticleType::GameHash, $gameId, "$hash unlinked by $user");

return response()->json(['message' => __('legacy.success.delete')]);
}

private function logGameHashUpdate(GameHash $gameHash, array $changedAttributes, User $user): void
{
$commentParts = ["{$gameHash->md5} updated by {$user->User}."];

foreach ($changedAttributes as $attribute => $newValue) {
$newValueDisplay = $newValue ?? 'None';

switch ($attribute) {
case 'Name':
$commentParts[] = "File Name: \"{$newValueDisplay}\".";
break;
case 'Labels':
$commentParts[] = "Label: \"{$newValueDisplay}\".";
break;
case 'patch_url':
$commentParts[] = $newValue ? "RAPatches URL updated to: {$newValue}." : "RAPatches URL removed.";
break;
case 'source':
$commentParts[] = $newValue ? "Resource Page URL updated to: {$newValue}." : "Resource Page URL removed.";
break;
}
}

$comment = implode(' ', $commentParts);
addArticleComment("Server", ArticleType::GameHash, $gameHash->game_id, $comment);
}
}
2 changes: 0 additions & 2 deletions app/Platform/RouteServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,6 @@ protected function mapWebRoutes(): void
Route::post('ticket', [TriggerTicketApiController::class, 'store'])->name('api.ticket.store');
});

Route::resource('game-hash', GameHashController::class)->parameters(['game-hash' => 'gameHash'])->only(['update', 'destroy']);

Route::get('games/resettable', [PlayerGameController::class, 'resettableGames'])->name('player.games.resettable');
Route::get('game/{game}/achievements/resettable', [PlayerGameController::class, 'resettableGameAchievements'])->name('player.game.achievements.resettable');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ export const HashesMainRoot: FC = memo(() => {
<div className="flex flex-col gap-5">
{can.manageGameHashes ? (
<a
href={route('game.hash.manage', { game: game.id })}
// For performance reasons, Filament routes are not handled by Ziggy's `route()` function.
href={`/manage/games/${game.id}/hashes`}
className={baseButtonVariants({
size: 'sm',
className: 'flex items-center gap-1 sm:max-w-fit',
Expand Down
Loading