refactor(backend): reorganize domains into Core, Commerce, Ticketing and Shared

This commit is contained in:
2026-09-18 10:14:02 -03:00
parent 4659c1049d
commit 1241e1f7e8
425 changed files with 0 additions and 0 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\ScannerBootstrapRequest;
use App\Domains\Bootstrap\Resources\ScannerBootstrapResource;
use App\Domains\Bootstrap\Services\ScannerBootstrapService;
use App\Http\Controllers\Controller;
class ScannerBootstrapController extends Controller
{
public function __construct(protected ScannerBootstrapService $bootstrapService) {}
public function __invoke(ScannerBootstrapRequest $request): ScannerBootstrapResource
{
return ScannerBootstrapResource::make(
$this->bootstrapService->get((string) $request->validated('dominio'))
);
}
}

View File

@@ -0,0 +1,23 @@
<?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'),
(string) $request->validated('path'),
)
);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,66 @@
<?php
namespace App\Domains\Bootstrap\Requests;
use App\Domains\Tenant\Support\TenantDomainNormalizer;
use Closure;
use Illuminate\Foundation\Http\FormRequest;
class TenantBootstrapRequest extends FormRequest
{
protected bool $hasInvalidDomain = false;
protected bool $hasInvalidPath = false;
public function authorize(): bool
{
return true;
}
protected function prepareForValidation(): void
{
$rawDomain = $this->query('dominio', $this->route('dominio'));
$rawPath = $this->query('path', '/');
$normalizedDomain = TenantDomainNormalizer::normalize($rawDomain);
$normalizedPath = TenantDomainNormalizer::normalizePath($rawPath);
$this->hasInvalidDomain = TenantDomainNormalizer::hasValue($rawDomain)
&& $normalizedDomain === null;
$this->hasInvalidPath = ! is_string($rawPath) || $normalizedPath === null;
$this->merge([
'dominio' => $normalizedDomain,
'path' => $normalizedPath,
]);
}
/** @return array<string, mixed> */
public function rules(): array
{
return [
'dominio' => [
'bail',
function (string $attribute, mixed $value, Closure $fail): void {
if ($this->hasInvalidDomain) {
$fail("The {$attribute} field must contain a valid domain or URL.");
}
},
'required',
'string',
'max:255',
],
'path' => [
'bail',
function (string $attribute, mixed $value, Closure $fail): void {
if ($this->hasInvalidPath) {
$fail("The {$attribute} field must contain a valid URL path.");
}
},
'required',
'string',
'max:2048',
],
];
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Domains\Bootstrap\Resources;
use App\Domains\Tenant\Models\AdminWebsiteType;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/** @mixin array{website_type: AdminWebsiteType} */
class AdminAppBootstrapResource extends JsonResource
{
/** @return array<string, mixed> */
public function toArray(Request $request): array
{
/** @var AdminWebsiteType $websiteType */
$websiteType = $this->resource['website_type'];
return [
'admin_website_type_code' => $websiteType->codigo,
'site_title' => $websiteType->site_title,
'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),
'favicon' => $websiteType->favicon?->getTemporaryUrl(1440),
];
}
}

View File

@@ -0,0 +1,5 @@
<?php
namespace App\Domains\Bootstrap\Resources;
class ScannerBootstrapResource extends AdminAppBootstrapResource {}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Domains\Bootstrap\Services;
use App\Domains\Tenant\Models\AdminWebsiteType;
class AdminAppBootstrapService
{
/** @return array{website_type: AdminWebsiteType} */
public function get(string $domain): array
{
return [
'website_type' => AdminWebsiteType::query()
->with(['siteLogo', 'footerLogo', 'favicon'])
->where('dominio', $domain)
->firstOrFail(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Domains\Bootstrap\Services;
use App\Domains\Tenant\Models\AdminWebsiteType;
class ScannerBootstrapService
{
/** @return array{website_type: AdminWebsiteType} */
public function get(string $domain): array
{
return [
'website_type' => AdminWebsiteType::query()
->with(['siteLogo', 'footerLogo', 'favicon'])
->where('scanner_domain', $domain)
->firstOrFail(),
];
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Domains\Bootstrap\Services;
use App\Domains\Authorization\Enums\RoleCode;
use App\Domains\Tenant\Models\Tenant;
use App\Domains\Tenant\Services\TenantInformationService;
use App\Domains\Tenant\Support\TenantDomainNormalizer;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class TenantBootstrapService
{
public function __construct(protected TenantInformationService $tenantInformationService) {}
public function get(string $domain, string $path = '/'): Tenant
{
$candidateBasePaths = TenantDomainNormalizer::basePathCandidates($path);
$tenantsByBasePath = Tenant::query()
->where('dominio', $domain)
->whereIn('base_path', $candidateBasePaths)
->get()
->keyBy('base_path');
$tenant = collect($candidateBasePaths)
->map(fn (string $candidate): ?Tenant => $tenantsByBasePath->get($candidate))
->first(fn (?Tenant $candidate): bool => $candidate !== null);
if (! $tenant instanceof Tenant) {
throw (new ModelNotFoundException)->setModel(Tenant::class);
}
return $this->tenantInformationService->load(
$tenant,
[
'eventDateChanges',
'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,28 @@
# Dominio Bootstrap
## Propósito
Entrega la configuración inicial que necesitan la tienda y el panel administrativo antes de renderizar su interfaz.
## Flujos
- `TenantBootstrapService` resuelve un tenant desde el dominio solicitado y carga su información pública.
- `AdminAppBootstrapService` prepara el contexto inicial del panel administrativo para el tenant autenticado.
- Los controladores invocables transforman el resultado mediante `TenantResource` o `AdminAppBootstrapResource`.
## Endpoints
- `GET /tenants/bootstrap?dominio={hostname}&path={path}`: bootstrap público de la tienda. Resuelve la clave de tenant más específica que sea prefijo completo del path y usa el dominio raíz como fallback.
- Endpoint de bootstrap bajo `/v1/adminapp`, protegido por `auth:sanctum` y `adminapp.tenant`.
## Validación
`TenantBootstrapRequest` valida y normaliza por separado el hostname y el path recibidos. `AdminAppBootstrapRequest` reutiliza ese contrato para el panel.
## Dependencias
Depende principalmente de `Tenant` para resolver y cargar la tienda, y de los dominios que aportan datos al contexto administrativo.
## Consideraciones
Este dominio es un agregador de lectura. Debe mantenerse liviano y delegar la obtención de cada dato al dominio propietario.

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', TenantBootstrapController::class);
require __DIR__.'/adminapp.php';
require __DIR__.'/scanner.php';

View File

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