151 lines
5.8 KiB
PHP
151 lines
5.8 KiB
PHP
<?php
|
|
|
|
/**
|
|
* File Revision Controller — client API
|
|
*
|
|
* @author Ball Studios <https://git.balls.studio>
|
|
* @license MIT
|
|
*/
|
|
|
|
namespace Pterodactyl\Addons\AdvancedAdmin\Http\Controllers\Client;
|
|
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Routing\Controller;
|
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
|
use Pterodactyl\Addons\AdvancedAdmin\Models\FileRevision;
|
|
use Pterodactyl\Addons\AdvancedAdmin\Services\Revisions\DiffService;
|
|
use Pterodactyl\Addons\AdvancedAdmin\Services\Revisions\RevisionService;
|
|
use Pterodactyl\Addons\AdvancedAdmin\Services\Revisions\RevisionStorageService;
|
|
use Pterodactyl\Models\Server;
|
|
|
|
class FileRevisionController extends Controller
|
|
{
|
|
public function __construct(
|
|
private readonly RevisionService $revisionService,
|
|
private readonly DiffService $diffService,
|
|
private readonly RevisionStorageService $storage,
|
|
) {}
|
|
|
|
/** GET /api/client/servers/{server}/addons/files/revisions?path= */
|
|
public function index(Request $request, Server $server): JsonResponse
|
|
{
|
|
$this->requirePermission($request, $server, 'file.read');
|
|
|
|
$path = $request->string('path', '')->toString();
|
|
abort_if(empty($path), 422, 'path parameter is required.');
|
|
|
|
$path = $this->storage->guardFilePath($path);
|
|
$pathHash = $this->storage->pathHash($server->id, $path);
|
|
|
|
$revisions = FileRevision::query()
|
|
->where('server_id', $server->id)
|
|
->where('path_hash', $pathHash)
|
|
->with('author:id,username,email')
|
|
->orderByDesc('revision_number')
|
|
->paginate(25);
|
|
|
|
return response()->json($revisions);
|
|
}
|
|
|
|
/** GET /api/client/servers/{server}/addons/files/revisions/{revision} */
|
|
public function show(Request $request, Server $server, FileRevision $revision): JsonResponse
|
|
{
|
|
$this->requirePermission($request, $server, 'file.read');
|
|
$this->assertBelongsToServer($revision, $server);
|
|
|
|
return response()->json(['data' => $revision->load('author:id,username,email')]);
|
|
}
|
|
|
|
/** GET /api/client/servers/{server}/addons/files/revisions/{revision}/diff */
|
|
public function diff(Request $request, Server $server, FileRevision $revision): JsonResponse
|
|
{
|
|
$this->requirePermission($request, $server, 'file.read');
|
|
$this->assertBelongsToServer($revision, $server);
|
|
|
|
$content = $this->storage->retrieve($revision->storage_key);
|
|
|
|
// If binary, return a placeholder diff
|
|
if ($this->storage->isBinary($content)) {
|
|
return response()->json(['binary' => true, 'lines' => []]);
|
|
}
|
|
|
|
// Fetch previous revision for comparison
|
|
$previous = FileRevision::query()
|
|
->where('server_id', $server->id)
|
|
->where('path_hash', $revision->path_hash)
|
|
->where('revision_number', $revision->revision_number - 1)
|
|
->first();
|
|
|
|
$oldContent = $previous ? $this->storage->retrieve($previous->storage_key) : '';
|
|
|
|
$lines = $this->diffService->diff($oldContent, $content, basename($revision->file_path));
|
|
return response()->json(['binary' => false, 'lines' => $lines]);
|
|
}
|
|
|
|
/** GET /api/client/servers/{server}/addons/files/revisions/{revision}/download */
|
|
public function download(Request $request, Server $server, FileRevision $revision): StreamedResponse
|
|
{
|
|
$this->requirePermission($request, $server, 'file.read');
|
|
$this->assertBelongsToServer($revision, $server);
|
|
|
|
$content = $this->storage->retrieve($revision->storage_key);
|
|
$filename = basename($revision->file_path) . ".rev{$revision->revision_number}";
|
|
|
|
return response()->streamDownload(function () use ($content) {
|
|
echo $content;
|
|
}, $filename, ['Content-Type' => 'application/octet-stream']);
|
|
}
|
|
|
|
/** POST /api/client/servers/{server}/addons/files/revisions/{revision}/restore */
|
|
public function restore(Request $request, Server $server, FileRevision $revision): JsonResponse
|
|
{
|
|
$this->requirePermission($request, $server, 'file.update');
|
|
$this->assertBelongsToServer($revision, $server);
|
|
|
|
$content = $this->revisionService->restore($revision, $request->user()->id);
|
|
|
|
return response()->json([
|
|
'restored' => true,
|
|
'content' => $content,
|
|
'revision' => $revision->revision_number,
|
|
]);
|
|
}
|
|
|
|
/** DELETE /api/client/servers/{server}/addons/files/revisions/{revision} */
|
|
public function destroy(Request $request, Server $server, FileRevision $revision): JsonResponse
|
|
{
|
|
// Only admins can manually delete revisions
|
|
abort_unless($request->user()?->root_admin, 403, 'Admin access required.');
|
|
$this->assertBelongsToServer($revision, $server);
|
|
|
|
$this->revisionService->delete($revision);
|
|
return response()->json(['deleted' => true]);
|
|
}
|
|
|
|
// ─── Helpers ──────────────────────────────────────────────────────────────
|
|
|
|
private function requirePermission(Request $request, Server $server, string $permission): void
|
|
{
|
|
$user = $request->user();
|
|
abort_unless($user, 401);
|
|
|
|
$isOwner = $server->owner_id === $user->id;
|
|
$isAdmin = $user->root_admin;
|
|
|
|
if (!$isOwner && !$isAdmin) {
|
|
$subuser = $server->subusers()->where('user_id', $user->id)->first();
|
|
abort_unless(
|
|
$subuser && in_array($permission, $subuser->permissions ?? [], true),
|
|
403,
|
|
"Permission '{$permission}' required."
|
|
);
|
|
}
|
|
}
|
|
|
|
private function assertBelongsToServer(FileRevision $revision, Server $server): void
|
|
{
|
|
abort_unless($revision->server_id === $server->id, 404);
|
|
}
|
|
}
|