aspirecloud/app/Actions/Admin/API/V1/BulkImport.php
2025-10-05 13:48:12 -06:00

94 lines
3 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Actions\Admin\API\V1;
use App\Http\JsonLines;
use App\Http\JsonResponses;
use App\Models\WpOrg\ClosedPlugin;
use App\Models\WpOrg\Plugin;
use App\Models\WpOrg\Theme;
use Exception;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Pipeline\Pipeline;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use function Safe\ini_set;
class BulkImport
{
use JsonResponses;
use JsonLines;
public function __invoke(Request $request, Pipeline $pipeline): JsonResponse
{
ini_set('memory_limit', '2G');
ini_set('max_execution_time', '300');
$currentLine = 0;
$imported = 0;
$errors = [];
$request_info = ['userid' => auth()->user()?->id, 'ip' => $request->ip()];
Log::info("Beginning bulk import", $request_info);
foreach ($this->lazyJsonLines($request) as $metadata) {
$currentLine++;
try {
$model = DB::transaction(fn() => $this->loadOne($metadata));
assert($model instanceof Model); // strip 'mixed' type from DB::transaction
Log::debug(
"Imported {$model->slug}",
['slug' => $model->slug, 'version' => $model->version, 'type' => $model->getMorphClass()],
);
$imported++;
} catch (Exception $e) {
$errors[$currentLine] = $e->getMessage();
}
}
Log::info("Bulk import complete", [...$request_info, 'imported' => $imported, 'errors' => $errors]);
return $errors ? $this->error(compact('errors', 'imported')) : $this->success(['imported' => $imported]);
}
/** @param array<string, mixed> $metadata */
private function loadOne(array $metadata): Plugin|ClosedPlugin|Theme
{
$sync_meta = $metadata['aspiresync_meta'];
$type = $sync_meta['type'];
$status = $sync_meta['status'];
$class = match ($type) {
'plugin' => match ($status) {
'open' => Plugin::class,
'closed' => ClosedPlugin::class,
default => throw new Exception("Unknown plugin status: {$status}"),
},
'theme' => match ($status) {
'open' => Theme::class,
// Closed themes don't seem to be a thing, they're just 404 in the API
default => throw new Exception("Unknown theme status: {$status}"),
},
default => throw new Exception("Unknown plugin type: {$type}"),
};
$slug = $metadata['slug'];
assert(is_a($class, Model::class, true)); // @phpstan-ignore function.alreadyNarrowedType (that's the POINT!)
if ($type === 'plugin') {
Plugin::where('slug', $slug)->delete();
ClosedPlugin::where('slug', $slug)->delete();
} elseif ($type === 'theme') {
Theme::where('slug', $slug)->delete();
}
return $class::fromSyncMetadata($metadata);
}
}