feat: Refactor bootstrap functionality by adding AdminApp and Tenant controllers, requests, resources, and services; remove deprecated event form components

This commit is contained in:
2026-08-03 13:37:37 -03:00
parent f4638bc984
commit 12697c2268
26 changed files with 207 additions and 283 deletions

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Domains\Bootstrap\Controllers;
use App\Domains\Bootstrap\Requests\AdminAppBootstrapRequest;
use App\Domains\Bootstrap\Resources\AdminAppBootstrapResource;
use App\Domains\Bootstrap\Services\AdminAppBootstrapService;
use App\Http\Controllers\Controller;
class AdminAppBootstrapController extends Controller
{
public function __construct(protected AdminAppBootstrapService $bootstrapService) {}
public function __invoke(AdminAppBootstrapRequest $request): AdminAppBootstrapResource
{
return AdminAppBootstrapResource::make(
$this->bootstrapService->get((string) $request->validated('dominio'))
);
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Domains\Bootstrap\Controllers;
use App\Domains\Bootstrap\Requests\TenantBootstrapRequest;
use App\Domains\Bootstrap\Services\TenantBootstrapService;
use App\Domains\Tenant\Resources\TenantResource;
use App\Http\Controllers\Controller;
class TenantBootstrapController extends Controller
{
public function __construct(protected TenantBootstrapService $bootstrapService) {}
public function __invoke(TenantBootstrapRequest $request): TenantResource
{
return TenantResource::make(
$this->bootstrapService->get((string) $request->validated('dominio'))
);
}
}

View File

@@ -0,0 +1,5 @@
<?php
namespace App\Domains\Bootstrap\Requests;
class AdminAppBootstrapRequest extends TenantBootstrapRequest {}

View File

@@ -1,12 +1,12 @@
<?php
namespace App\Domains\Tenant\Requests;
namespace App\Domains\Bootstrap\Requests;
use App\Domains\Tenant\Support\TenantDomainNormalizer;
use Closure;
use Illuminate\Foundation\Http\FormRequest;
class BootstrapTenantRequest extends FormRequest
class TenantBootstrapRequest extends FormRequest
{
protected bool $hasInvalidDomain = false;
@@ -23,14 +23,10 @@ class BootstrapTenantRequest extends FormRequest
$this->hasInvalidDomain = TenantDomainNormalizer::hasValue($rawDomain)
&& $normalizedDomain === null;
$this->merge([
'dominio' => $normalizedDomain,
]);
$this->merge(['dominio' => $normalizedDomain]);
}
/**
* @return array<string, mixed>
*/
/** @return array<string, mixed> */
public function rules(): array
{
return [

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Domains\Bootstrap\Resources;
use App\Domains\Tenant\Models\WebsiteType;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/** @mixin array{website_type: WebsiteType, social_media: Collection} */
class AdminAppBootstrapResource extends JsonResource
{
/** @return array<string, mixed> */
public function toArray(Request $request): array
{
/** @var WebsiteType $websiteType */
$websiteType = $this->resource['website_type'];
return [
'website_type_code' => $websiteType->codigo,
'primary_color' => $websiteType->primary_color,
'secondary_color' => $websiteType->secondary_color,
'danger_color' => $websiteType->danger_color,
'success_color' => $websiteType->success_color,
'warning_color' => $websiteType->warning_color,
'body_color' => $websiteType->body_color,
'darker_body_color' => $websiteType->darker_body_color,
'surface_color' => $websiteType->surface_color,
'background_color' => $websiteType->background_color,
'border_color' => $websiteType->border_color,
'login_header_footer_color' => $websiteType->login_header_footer_color,
'site_logo' => $websiteType->siteLogo?->getTemporaryUrl(1440),
'footer_logo' => $websiteType->footerLogo?->getTemporaryUrl(1440),
'forms' => [
[
'code' => 'event',
'social_media' => SocialMediaOptionResource::collection(
$this->resource['social_media']
),
],
],
];
}
}

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Domains\Forms\Resources;
namespace App\Domains\Bootstrap\Resources;
use App\Domains\Tenant\Models\SocialMedia;
use Illuminate\Http\Request;

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Domains\Bootstrap\Services;
use App\Domains\Tenant\Models\SocialMedia;
use App\Domains\Tenant\Models\WebsiteType;
use Illuminate\Database\Eloquent\Collection;
class AdminAppBootstrapService
{
/** @return array{website_type: WebsiteType, social_media: Collection<int, SocialMedia>} */
public function get(string $domain): array
{
return [
'website_type' => WebsiteType::query()
->with(['siteLogo', 'footerLogo'])
->where('dominio', $domain)
->firstOrFail(),
'social_media' => SocialMedia::query()->orderBy('id')->get(),
];
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Domains\Bootstrap\Services;
use App\Domains\Authorization\Enums\RoleCode;
use App\Domains\Tenant\Models\Tenant;
use App\Domains\Tenant\Services\TenantInformationService;
class TenantBootstrapService
{
public function __construct(protected TenantInformationService $tenantInformationService) {}
public function get(string $domain): Tenant
{
return $this->tenantInformationService->load(
Tenant::query()->where('dominio', $domain)->firstOrFail(),
[
'menues' => fn ($query) => $query->whereHas(
'roles',
fn ($query) => $query->where('codigo', RoleCode::User->value)
),
'categories' => fn ($query) => $query->orderBy('nombre'),
]
);
}
}

View File

@@ -0,0 +1,9 @@
<?php
use App\Domains\Bootstrap\Controllers\AdminAppBootstrapController;
use Illuminate\Support\Facades\Route;
Route::get(
'v1/adminapp/bootstrap/{dominio}',
AdminAppBootstrapController::class
);

View File

@@ -0,0 +1,9 @@
<?php
use App\Domains\Bootstrap\Controllers\TenantBootstrapController;
use Illuminate\Support\Facades\Route;
Route::get('tenants/bootstrap/{dominio}', TenantBootstrapController::class)
->where('dominio', '.*');
require __DIR__.'/adminapp.php';

View File

@@ -1,19 +0,0 @@
<?php
namespace App\Domains\Forms\Controllers\AdminApp;
use App\Domains\Forms\Resources\EventFormResource;
use App\Domains\Forms\Services\EventFormService;
use App\Http\Controllers\Controller;
class EventFormController extends Controller
{
public function __construct(protected EventFormService $eventFormService) {}
public function __invoke(): EventFormResource
{
return EventFormResource::make(
$this->eventFormService->get()
);
}
}

View File

@@ -1,19 +0,0 @@
<?php
namespace App\Domains\Forms\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class EventFormResource extends JsonResource
{
/** @return array<string, mixed> */
public function toArray(Request $request): array
{
return [
'social_media' => SocialMediaOptionResource::collection(
$this->resource['social_media']
),
];
}
}

View File

@@ -1,17 +0,0 @@
<?php
namespace App\Domains\Forms\Services;
use App\Domains\Tenant\Models\SocialMedia;
use Illuminate\Database\Eloquent\Collection;
class EventFormService
{
/** @return array{social_media: Collection<int, SocialMedia>} */
public function get(): array
{
return [
'social_media' => SocialMedia::query()->orderBy('id')->get(),
];
}
}

View File

@@ -1,10 +0,0 @@
<?php
use App\Domains\Forms\Controllers\AdminApp\EventFormController;
use Illuminate\Support\Facades\Route;
Route::prefix('v1/adminapp/forms')
->middleware(['auth:sanctum', 'adminapp.tenant'])
->group(function (): void {
Route::get('event', EventFormController::class);
});

View File

@@ -1,3 +0,0 @@
<?php
require __DIR__.'/adminapp.php';

View File

@@ -1,25 +0,0 @@
<?php
namespace App\Domains\Tenant\Controllers\AdminApp;
use App\Domains\Tenant\Models\WebsiteType;
use App\Domains\Tenant\Requests\BootstrapAdminAppRequest;
use App\Domains\Tenant\Resources\AdminApp\BootstrapAdminAppResource;
use App\Http\Controllers\Controller;
class BootstrapAdminAppController extends Controller
{
public function __invoke(
BootstrapAdminAppRequest $request
): BootstrapAdminAppResource {
/** @var string $domain */
$domain = $request->validated('dominio');
return BootstrapAdminAppResource::make(
WebsiteType::query()
->with(['siteLogo', 'footerLogo'])
->where('dominio', $domain)
->firstOrFail()
);
}
}

View File

@@ -1,38 +0,0 @@
<?php
namespace App\Domains\Tenant\Controllers;
use App\Domains\Authorization\Enums\RoleCode;
use App\Domains\Tenant\Models\Tenant;
use App\Domains\Tenant\Requests\BootstrapTenantRequest;
use App\Domains\Tenant\Resources\TenantResource;
use App\Domains\Tenant\Services\TenantInformationService;
use App\Http\Controllers\Controller;
class BootstrapTenantController extends Controller
{
public function __construct(
protected TenantInformationService $tenantInformationService
) {}
public function __invoke(BootstrapTenantRequest $request): TenantResource
{
/** @var string $dominio */
$dominio = $request->validated('dominio');
return TenantResource::make(
$this->tenantInformationService->load(
Tenant::query()
->where('dominio', $dominio)
->firstOrFail(),
[
'menues' => fn ($query) => $query->whereHas(
'roles',
fn ($query) => $query->where('codigo', RoleCode::User->value)
),
'categories' => fn ($query) => $query->orderBy('nombre'),
]
)
);
}
}

View File

@@ -1,5 +0,0 @@
<?php
namespace App\Domains\Tenant\Requests;
class BootstrapAdminAppRequest extends BootstrapTenantRequest {}

View File

@@ -1,36 +0,0 @@
<?php
namespace App\Domains\Tenant\Resources\AdminApp;
use App\Domains\Tenant\Models\WebsiteType;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* @mixin WebsiteType
*/
class BootstrapAdminAppResource extends JsonResource
{
/**
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'website_type_code' => $this->codigo,
'primary_color' => $this->primary_color,
'secondary_color' => $this->secondary_color,
'danger_color' => $this->danger_color,
'success_color' => $this->success_color,
'warning_color' => $this->warning_color,
'body_color' => $this->body_color,
'darker_body_color' => $this->darker_body_color,
'surface_color' => $this->surface_color,
'background_color' => $this->background_color,
'border_color' => $this->border_color,
'login_header_footer_color' => $this->login_header_footer_color,
'site_logo' => $this->siteLogo?->getTemporaryUrl(1440),
'footer_logo' => $this->footerLogo?->getTemporaryUrl(1440),
];
}
}

View File

@@ -1,14 +1,8 @@
<?php
use App\Domains\Tenant\Controllers\AdminApp\BootstrapAdminAppController;
use App\Domains\Tenant\Controllers\AdminApp\WebsiteExtraController;
use Illuminate\Support\Facades\Route;
Route::get(
'v1/adminapp/bootstrap/{dominio}',
BootstrapAdminAppController::class
);
Route::prefix('v1/adminapp/tenant')
->middleware(['auth:sanctum', 'adminapp.tenant'])
->group(function (): void {

View File

@@ -1,12 +1,8 @@
<?php
use App\Domains\Tenant\Controllers\BootstrapTenantController;
use App\Domains\Tenant\Controllers\TenantController;
use Illuminate\Support\Facades\Route;
Route::get('tenants/bootstrap/{dominio}', BootstrapTenantController::class)
->where('dominio', '.*');
Route::apiResource('tenants', TenantController::class);
require __DIR__.'/adminapp.php';

View File

@@ -11,4 +11,4 @@ require __DIR__.'/../app/Domains/Integration/routes/api.php';
require __DIR__.'/../app/Domains/Menu/routes/api.php';
require __DIR__.'/../app/Domains/Ticket/routes/api.php';
require __DIR__.'/../app/Domains/Event/routes/api.php';
require __DIR__.'/../app/Domains/Forms/routes/api.php';
require __DIR__.'/../app/Domains/Bootstrap/routes/api.php';

View File

@@ -1,66 +0,0 @@
<?php
namespace Tests\Feature\Forms;
use App\Domains\Auth\Models\User;
use App\Domains\Authorization\Enums\RoleCode;
use App\Domains\Tenant\Models\Tenant;
use App\Domains\Tenant\Models\WebsiteType;
use Database\Seeders\AuthorizationSeeder;
use Database\Seeders\SocialMediaSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Sanctum\Sanctum;
use Tests\TestCase;
class AdminAppEventFormControllerTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
$this->seed([AuthorizationSeeder::class, SocialMediaSeeder::class]);
WebsiteType::query()->create([
'codigo' => 'onticket',
'nombre' => 'OnTicket',
]);
}
public function test_authentication_is_required(): void
{
$this->getJson('/api/v1/adminapp/forms/event')->assertUnauthorized();
}
public function test_an_adminapp_user_can_get_the_event_form(): void
{
$tenant = Tenant::query()->create([
'codigo' => 'acme',
'nombre' => 'Acme',
'dominio' => 'acme.test',
'website_type_code' => 'onticket',
]);
Sanctum::actingAs(User::factory()->create([
'rol_codigo' => RoleCode::AdminApp->value,
'tenant_codigo' => $tenant->codigo,
]));
$this->getJson('/api/v1/adminapp/forms/event')
->assertOk()
->assertJsonCount(4, 'data.social_media')
->assertJsonPath('data.social_media.0.code', 'facebook')
->assertJsonPath('data.social_media.0.name', 'Facebook')
->assertJsonPath('data.social_media.0.icon', 'fa-brands fa-facebook')
->assertJsonPath('data.social_media.3.code', 'linkedin');
}
public function test_a_customer_cannot_get_the_event_form(): void
{
Sanctum::actingAs(User::factory()->create([
'rol_codigo' => RoleCode::User->value,
'tenant_codigo' => null,
]));
$this->getJson('/api/v1/adminapp/forms/event')->assertForbidden();
}
}

View File

@@ -4,6 +4,7 @@ namespace Tests\Feature\Tenant;
use App\Domains\Attachable\Models\Attachment;
use App\Domains\Tenant\Models\WebsiteType;
use Database\Seeders\SocialMediaSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
@@ -13,6 +14,7 @@ class BootstrapAdminAppControllerTest extends TestCase
public function test_it_publicly_bootstraps_the_admin_app_by_domain(): void
{
$this->seed(SocialMediaSeeder::class);
$footerLogo = Attachment::factory()->create();
WebsiteType::query()->create([
@@ -41,6 +43,10 @@ class BootstrapAdminAppControllerTest extends TestCase
->assertJsonPath('data.login_header_footer_color', '#313131')
->assertJsonPath('data.site_logo', null)
->assertJsonPath('data.footer_logo', $footerLogo->getTemporaryUrl(1440))
->assertJsonPath('data.forms.0.code', 'event')
->assertJsonCount(4, 'data.forms.0.social_media')
->assertJsonPath('data.forms.0.social_media.0.code', 'facebook')
->assertJsonPath('data.forms.0.social_media.3.code', 'linkedin')
->assertJsonMissingPath('data.codigo')
->assertJsonMissingPath('data.nombre')
->assertJsonMissingPath('data.dominio');

View File

@@ -0,0 +1,40 @@
<?php
namespace Tests\Unit\Bootstrap;
use App\Domains\Bootstrap\Resources\AdminAppBootstrapResource;
use App\Domains\Tenant\Models\SocialMedia;
use App\Domains\Tenant\Models\WebsiteType;
use Illuminate\Database\Eloquent\Collection;
use Tests\TestCase;
class AdminAppBootstrapResourceTest extends TestCase
{
public function test_it_exposes_the_website_type_and_forms(): void
{
$websiteType = new WebsiteType([
'codigo' => 'shopit',
'nombre' => 'ShopIt',
'dominio' => 'admin.shopit.test',
]);
$websiteType->setRelation('siteLogo', null);
$websiteType->setRelation('footerLogo', null);
$socialMedia = new SocialMedia([
'code' => 'instagram',
'name' => 'Instagram',
'icon' => 'fa-brands fa-instagram',
]);
$data = AdminAppBootstrapResource::make([
'website_type' => $websiteType,
'social_media' => new Collection([$socialMedia]),
])->resolve(request());
$this->assertSame('shopit', $data['website_type_code']);
$this->assertSame('event', $data['forms'][0]['code']);
$this->assertSame(
'instagram',
$data['forms'][0]['social_media']->resolve(request())[0]['code']
);
}
}

View File

@@ -1,25 +0,0 @@
<?php
namespace Tests\Unit\Tenant;
use App\Domains\Tenant\Models\WebsiteType;
use App\Domains\Tenant\Resources\AdminApp\BootstrapAdminAppResource;
use Tests\TestCase;
class BootstrapAdminAppResourceTest extends TestCase
{
public function test_it_exposes_the_website_type_code(): void
{
$websiteType = new WebsiteType([
'codigo' => 'shopit',
'nombre' => 'ShopIt',
'dominio' => 'admin.shopit.test',
]);
$websiteType->setRelation('siteLogo', null);
$data = BootstrapAdminAppResource::make($websiteType)->resolve(request());
$this->assertSame('shopit', $data['codigo']);
$this->assertSame('shopit', $data['website_type_code']);
}
}