Feat: Upload Controller and Prune Commands Extraction from 3dealer

This commit is contained in:
2026-09-25 13:48:42 +03:00
parent 6025ea4304
commit f1a0322d3f
3 changed files with 113 additions and 0 deletions
@@ -0,0 +1,32 @@
<?php
namespace Modules\Core\File\Commands;
use Illuminate\Console\Command;
use Modules\Core\File\Services\FileService;
/**
* Generic wrapper around FileService::pruneUnowned() — see that method's
* own docblock for what "unowned" means and why the grace period exists.
* Any caller (3dealer's product custom-field photo uploads today, some
* other future upload feature tomorrow, in this app or another consuming
* app) schedules this once per purpose string it stores files under; this
* command itself has no opinion about what any given purpose means.
*/
class PruneUnownedFilesCommand extends Command
{
protected $signature = 'boboko:file:prune-unowned {purpose} {--hours=24 : Only delete unowned files older than this}';
protected $description = 'Delete unowned files of a given purpose past their grace period';
public function handle(FileService $files): int
{
$purpose = $this->argument('purpose');
$deleted = $files->pruneUnowned($purpose, now()->subHours((int) $this->option('hours')));
$this->info("Deleted {$deleted} unowned file(s) of purpose \"{$purpose}\".");
return self::SUCCESS;
}
}
@@ -0,0 +1,76 @@
<?php
namespace Modules\Core\File\Http\Controllers;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Validator;
use Modules\Core\File\Services\FileService;
/**
* The generic "accept an upload, validate it, store it via FileService,
* return its id" flow — what varies per use case (which extensions/sizes
* are acceptable) is deliberately NOT configurable here, and NEVER trusts
* anything the request itself claims about its own limits: a client could
* simply lie about them. purpose()/validationRules() are protected hooks
* a concrete subclass overrides instead — an ordinary PHP method a caller
* writes once per upload policy, not request input, so the actual limit
* enforced is always whatever server-side code says it is. Different
* products can even need different limits (a 3D-print reference photo
* vs. a video upload, say) — that's still a subclass's own store()
* override deciding which rule set applies to a given request, not
* something this base class or a shared config file could express.
*/
abstract class UploadFileController extends Controller
{
/**
* The File row's `purpose` tag (see Models\File) — also the storage
* directory it lands under (FileService::store()'s single $purpose
* param doubles as both).
*/
abstract protected function purpose(): string;
/**
* Laravel validation rules for the incoming request, keyed exactly as
* $request->all() would be. Must include a 'file' rule accepting an
* uploaded file — this class always reads the file from that key.
*
* @return array<string, array<int, mixed>>
*/
abstract protected function validationRules(Request $request): array;
public function store(Request $request, FileService $files): JsonResponse
{
$validator = Validator::make(
$request->all(),
$this->validationRules($request),
$this->validationMessages($request),
$this->validationAttributes($request),
);
if ($validator->fails()) {
return response()->json(['error' => $validator->errors()->first('file')], 422);
}
$file = $files->store($request->file('file'), $this->purpose());
return response()->json(['file_id' => $file->id]);
}
/**
* @return array<string, string>
*/
protected function validationMessages(Request $request): array
{
return [];
}
/**
* @return array<string, string>
*/
protected function validationAttributes(Request $request): array
{
return [];
}
}
+5
View File
@@ -9,6 +9,7 @@ use InvalidArgumentException;
use Modules\Core\Cart\Events\CartLineAdded;
use Modules\Core\Checkout\Events\OrderPlaced;
use Modules\Core\File\Adapters\LocalFileAdapter;
use Modules\Core\File\Commands\PruneUnownedFilesCommand;
use Modules\Core\File\Contracts\FileAdapterInterface;
use Modules\Core\File\Listeners\AttachCustomFieldFileToCartLine;
use Modules\Core\File\Listeners\TransferCustomFieldFileOwnership;
@@ -42,5 +43,9 @@ class FileServiceProvider extends ServiceProvider
Event::listen(CartLineAdded::class, AttachCustomFieldFileToCartLine::class);
Event::listen(OrderPlaced::class, TransferCustomFieldFileOwnership::class);
if ($this->app->runningInConsole()) {
$this->commands([PruneUnownedFilesCommand::class]);
}
}
}