feat(brand): implement update functionality and validation for brand details
This commit is contained in:
@@ -2,17 +2,30 @@
|
||||
|
||||
namespace App\Domains\Core\Tenant\Controllers\AdminApp;
|
||||
|
||||
use App\Domains\Core\Tenant\Requests\AdminApp\UpdateBrandRequest;
|
||||
use App\Domains\Core\Tenant\Resources\AdminApp\BrandResource;
|
||||
use App\Domains\Core\Tenant\Services\BrandService;
|
||||
use App\Domains\Core\Tenant\Services\TenantInformationService;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class BrandController extends Controller
|
||||
{
|
||||
public function __invoke(Request $request, TenantInformationService $tenantInformationService): BrandResource
|
||||
public function show(Request $request, TenantInformationService $tenantInformationService): BrandResource
|
||||
{
|
||||
$tenant = $request->user()->tenant()->firstOrFail();
|
||||
|
||||
return BrandResource::make($tenantInformationService->load($tenant));
|
||||
}
|
||||
|
||||
public function update(
|
||||
UpdateBrandRequest $request,
|
||||
BrandService $brandService,
|
||||
TenantInformationService $tenantInformationService,
|
||||
): BrandResource {
|
||||
$tenant = $request->user()->tenant()->firstOrFail();
|
||||
$brandService->update($tenant, $request->validated());
|
||||
|
||||
return BrandResource::make($tenantInformationService->load($tenant->fresh()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Tenant\Requests\AdminApp;
|
||||
|
||||
use App\Shared\Rules\CroppedImageOrBase64Rule;
|
||||
use App\Shared\Rules\ImageOrBase64Rule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateBrandRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
$shopit = $this->user()->tenant()->firstOrFail()->storefront_website_type_code === 'shopit';
|
||||
|
||||
return [
|
||||
'nombre' => ['required', 'string', 'max:255'],
|
||||
'primary_color' => ['required', 'regex:/^#[0-9a-fA-F]{6}$/'],
|
||||
'footer_bg_color' => ['required', 'regex:/^#[0-9a-fA-F]{6}$/'],
|
||||
'header_logo' => ['present', 'nullable', new ImageOrBase64Rule],
|
||||
'footer_logo' => ['present', 'nullable', new ImageOrBase64Rule],
|
||||
'banner_images' => [$shopit ? 'present' : 'prohibited', 'array', 'max:10'],
|
||||
'banner_images.*' => ['required', new CroppedImageOrBase64Rule],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,10 @@ class BrandResource extends JsonResource
|
||||
'header_bg_color' => $this->header_bg_color,
|
||||
'footer_color' => $this->footerColor(),
|
||||
'header_logo' => $this->headerLogo?->getTemporaryUrl(1440),
|
||||
'header_logo_key' => $this->headerLogo?->key,
|
||||
'footer_logo' => $this->footerLogo?->getTemporaryUrl(1440),
|
||||
'footer_logo_key' => $this->footerLogo?->key,
|
||||
'can_edit_carousel' => $this->canEditCarousel(),
|
||||
'banner_images' => $this->bannerImages(),
|
||||
];
|
||||
}
|
||||
@@ -48,7 +51,7 @@ class BrandResource extends JsonResource
|
||||
/** @return array<int, array<string, mixed>> */
|
||||
private function bannerImages(): array
|
||||
{
|
||||
if ($this->storefront_website_type_code !== 'shopit') {
|
||||
if (! $this->canEditCarousel()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -73,6 +76,7 @@ class BrandResource extends JsonResource
|
||||
->firstWhere('variant', AttachmentCrop::DESKTOP);
|
||||
|
||||
return [
|
||||
'key' => $image->key,
|
||||
'url' => ($desktopCrop?->croppedAttachment ?? $image)->getTemporaryUrl(1440),
|
||||
'original_url' => $image->getTemporaryUrl(1440),
|
||||
'crop' => $desktopCrop === null ? null : [
|
||||
@@ -84,4 +88,17 @@ class BrandResource extends JsonResource
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
private function canEditCarousel(): bool
|
||||
{
|
||||
if ($this->storefront_website_type_code !== 'shopit') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$extra = $this->websiteExtras->first(
|
||||
fn ($extra) => $extra->websiteTypeExtra->codigo === 'carousel'
|
||||
);
|
||||
|
||||
return $extra === null || $extra->is_enabled;
|
||||
}
|
||||
}
|
||||
|
||||
165
app/Domains/Core/Tenant/Services/BrandService.php
Normal file
165
app/Domains/Core/Tenant/Services/BrandService.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Core\Tenant\Services;
|
||||
|
||||
use App\Domains\Core\Tenant\Models\Tenant;
|
||||
use App\Shared\Attachable\Enums\AttachmentType;
|
||||
use App\Shared\Attachable\Models\Attachment;
|
||||
use App\Shared\Attachable\Services\AttachmentService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Throwable;
|
||||
|
||||
class BrandService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AttachmentService $attachmentService,
|
||||
private readonly WebsiteExtraService $websiteExtraService,
|
||||
) {}
|
||||
|
||||
/** @param array<string, mixed> $data */
|
||||
public function update(Tenant $tenant, array $data): void
|
||||
{
|
||||
$carousel = $tenant->storefront_website_type_code === 'shopit'
|
||||
? $tenant->websiteExtras()->whereHas('websiteTypeExtra',
|
||||
fn ($query) => $query->where('codigo', 'carousel'))->first()
|
||||
: null;
|
||||
$allowedBannerIds = is_array($carousel?->config) ? $carousel->config : [];
|
||||
if ($carousel !== null && ! $carousel->is_enabled && ($data['banner_images'] ?? []) !== []) {
|
||||
throw ValidationException::withMessages(['banner_images' => 'El carrusel está deshabilitado.']);
|
||||
}
|
||||
$seen = [];
|
||||
|
||||
foreach (['header_logo' => $tenant->header_logo_id, 'footer_logo' => $tenant->footer_logo_id] as $field => $id) {
|
||||
$this->existingImage($data[$field], $id === null ? [] : [$id], $field);
|
||||
}
|
||||
foreach ($data['banner_images'] ?? [] as $index => $item) {
|
||||
$image = is_array($item) ? $item['image'] : $item;
|
||||
$attachment = $this->existingImage($image, $allowedBannerIds, "banner_images.{$index}");
|
||||
if ($attachment !== null) {
|
||||
if (isset($seen[$attachment->id])) {
|
||||
throw ValidationException::withMessages(["banner_images.{$index}" => 'La imagen está repetida.']);
|
||||
}
|
||||
$seen[$attachment->id] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$created = [];
|
||||
|
||||
try {
|
||||
DB::transaction(function () use ($tenant, $data, $carousel, &$created): void {
|
||||
$header = $this->logo($data['header_logo'], $tenant->header_logo_id, $tenant, $created);
|
||||
$footer = $this->logo($data['footer_logo'], $tenant->footer_logo_id, $tenant, $created);
|
||||
|
||||
$tenant->update([
|
||||
'nombre' => trim($data['nombre']),
|
||||
'primary_color' => $data['primary_color'],
|
||||
'footer_bg_color' => $data['footer_bg_color'],
|
||||
'header_logo_id' => $header?->id,
|
||||
'footer_logo_id' => $footer?->id,
|
||||
]);
|
||||
|
||||
if ($tenant->storefront_website_type_code !== 'shopit'
|
||||
|| ($carousel !== null && ! $carousel->is_enabled)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$definition = $this->websiteExtraService->definitionForTenant($tenant, 'carousel');
|
||||
$ids = [];
|
||||
$recrops = [];
|
||||
foreach ($data['banner_images'] as $item) {
|
||||
$image = is_array($item) ? $item['image'] : $item;
|
||||
$attachment = $this->existingImage($image, $carousel?->config ?? [], 'banner_images');
|
||||
$crops = $this->crops($item);
|
||||
|
||||
if ($attachment === null) {
|
||||
$attachment = $crops === null
|
||||
? $this->attachmentService->store($image, "tenants/{$tenant->codigo}/extras/carousel")
|
||||
: $this->attachmentService->storeCroppedImageVariants(
|
||||
$image, "tenants/{$tenant->codigo}/extras/carousel", $crops
|
||||
);
|
||||
$created[] = $attachment;
|
||||
} elseif ($crops !== null) {
|
||||
$recrops[] = [$attachment, $crops];
|
||||
}
|
||||
$ids[] = $attachment->id;
|
||||
}
|
||||
|
||||
$tenant->websiteExtras()->updateOrCreate(
|
||||
['website_type_extra_id' => $definition->id],
|
||||
['config' => $ids]
|
||||
);
|
||||
|
||||
foreach ($recrops as [$attachment, $crops]) {
|
||||
$this->attachmentService->updateImageCropVariants($attachment, $crops);
|
||||
}
|
||||
});
|
||||
} catch (Throwable $error) {
|
||||
foreach ($created as $attachment) {
|
||||
$paths = [$attachment->path];
|
||||
foreach ($attachment->cropVariants as $crop) {
|
||||
if ($crop->croppedAttachment !== null) {
|
||||
$paths[] = $crop->croppedAttachment->path;
|
||||
}
|
||||
}
|
||||
Storage::disk('s3')->delete($paths);
|
||||
}
|
||||
throw $error;
|
||||
}
|
||||
}
|
||||
|
||||
/** @param array<int, int> $allowedIds */
|
||||
private function existingImage(mixed $value, array $allowedIds, string $field): ?Attachment
|
||||
{
|
||||
if (! is_string($value) || ! Str::isUuid($value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$attachment = Attachment::query()->where('key', $value)->first();
|
||||
if ($attachment === null || $attachment->type !== AttachmentType::Image
|
||||
|| ! in_array($attachment->id, $allowedIds, true)) {
|
||||
throw ValidationException::withMessages([$field => 'La imagen no pertenece a esta marca.']);
|
||||
}
|
||||
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
/** @param array<int, Attachment> $created */
|
||||
private function logo(mixed $value, ?int $currentId, Tenant $tenant, array &$created): ?Attachment
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$existing = $this->existingImage($value, $currentId === null ? [] : [$currentId], 'logo');
|
||||
if ($existing !== null) {
|
||||
return $existing;
|
||||
}
|
||||
|
||||
$attachment = $this->attachmentService->store($value, "tenants/{$tenant->codigo}/logos");
|
||||
$created[] = $attachment;
|
||||
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
/** @return array<string, array<string, mixed>>|null */
|
||||
private function crops(mixed $item): ?array
|
||||
{
|
||||
if (! is_array($item)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! isset($item['crop_horizontal'], $item['crop_vertical'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$crop = [
|
||||
'crop_horizontal' => $item['crop_horizontal'],
|
||||
'crop_vertical' => $item['crop_vertical'],
|
||||
];
|
||||
|
||||
return ['desktop' => $crop, 'mobile' => $crop];
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,8 @@ use Illuminate\Support\Facades\Route;
|
||||
Route::prefix('v1/adminapp/tenant')
|
||||
->middleware(['auth:sanctum', 'adminapp.tenant'])
|
||||
->group(function (): void {
|
||||
Route::get('brand', BrandController::class);
|
||||
Route::get('brand', [BrandController::class, 'show']);
|
||||
Route::put('brand', [BrandController::class, 'update']);
|
||||
Route::get('website-extras', [WebsiteExtraController::class, 'show']);
|
||||
Route::get('website-extras/{websiteExtraCode}', [WebsiteExtraController::class, 'showExtra']);
|
||||
Route::put('website-extras/{websiteExtraCode}', [WebsiteExtraController::class, 'update']);
|
||||
|
||||
Reference in New Issue
Block a user