Feat: Moving File Handling To Core

This commit is contained in:
2026-09-25 13:47:58 +03:00
parent 540da1af24
commit a51e9456d6
11 changed files with 134 additions and 218 deletions
@@ -2,33 +2,32 @@
namespace App\Http\Controllers;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Validator;
use Modules\Core\File\Http\Controllers\UploadFileController;
/**
* Stores the shopper's photo for a product custom field of type `file` (see
* boboko-core's Product::$custom_fields) the moment it's picked on the product
* page — before add-to-cart, see custom-field-upload-controller.js.
*
* Which files are acceptable is a per-site decision (this site: photographs),
* so it lives here in the storefront, not in the checkout module. The module's
* add-to-cart endpoint only ever receives the opaque `reference` returned
* below — an encrypted {disk, path, name, mime} payload, so a shopper can
* neither forge a reference to some other private file nor read the path —
* and copies it onto the cart line's meta (see Checkout\CartController::
* customFieldsMeta()).
* Which files are acceptable (extensions, size) is a per-site decision — this
* site: photographs — so it lives here as this app's own policy, extending
* boboko-core's Modules\Core\File\Http\Controllers\UploadFileController for
* the actual store()/validate()/respond() mechanics. The module's add-to-cart
* endpoint only ever receives the stored File row's own `id` — FileService is
* the single source of truth for disk/path/name/mime, never duplicated into
* cart/order line meta (see Checkout\CartController::customFieldsMeta()). A
* shopper can't point a cart line at someone else's file: CartController only
* accepts an id that is both unowned and tagged with this exact PURPOSE.
*
* Stored on the private `local` disk: these are customers' personal photos,
* never reachable by a public URL. Uploads nobody added to a cart are removed
* by the custom-fields:prune-uploads command (see PruneCustomFieldUploads).
* never reachable by a public URL except through FileService's own signed
* download route. Uploads nobody adds to a cart are removed by core's
* `boboko:file:prune-unowned custom-field-upload` command.
*/
class CustomFieldUploadController extends Controller
class CustomFieldUploadController extends UploadFileController
{
public const DISK = 'local';
public const DIRECTORY = 'custom-field-uploads';
public const PURPOSE = 'custom-field-upload';
public const MAX_KILOBYTES = 10240;
@@ -42,33 +41,22 @@ public static function accept(): string
return '.'.implode(',.', self::EXTENSIONS);
}
public function store(string $locale, Request $request): JsonResponse
protected function purpose(): string
{
// `label` is the admin-authored field label, only used as the
// :attribute in the validation message shown next to that field.
$validator = Validator::make(
$request->all(),
['file' => ['required', 'file', 'mimes:'.implode(',', self::EXTENSIONS), 'max:'.self::MAX_KILOBYTES]],
[],
['file' => (string) $request->input('label', 'file')],
);
return self::PURPOSE;
}
if ($validator->fails()) {
return response()->json(['error' => $validator->errors()->first('file')], 422);
}
protected function validationRules(Request $request): array
{
return [
'file' => ['required', 'file', 'mimes:'.implode(',', self::EXTENSIONS), 'max:'.self::MAX_KILOBYTES],
];
}
$file = $request->file('file');
$path = $file->store(self::DIRECTORY, self::DISK);
abort_if($path === false, 500);
return response()->json([
'reference' => Crypt::encryptString(json_encode([
'disk' => self::DISK,
'path' => $path,
'name' => $file->getClientOriginalName(),
'mime' => $file->getMimeType(),
])),
]);
// `label` is the admin-authored field label, only used as the
// :attribute in the validation message shown next to that field.
protected function validationAttributes(Request $request): array
{
return ['file' => (string) $request->input('label', 'file')];
}
}