feat: manage tenant help menu settings

This commit is contained in:
2026-09-29 16:35:07 -03:00
parent 94db48467e
commit 0303803f75
4 changed files with 301 additions and 0 deletions

View File

@@ -0,0 +1,90 @@
<?php
namespace App\Domains\Core\Tenant\Controllers\AdminApp;
use App\Domains\Core\Tenant\Requests\AdminApp\UpdateHelpMenuContentRequest;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class HelpMenuController extends Controller
{
private const MENU_CODES = [
'help.faq',
'help.terms-and-conditions',
];
public function index(Request $request): JsonResponse
{
$tenant = $request->user()->tenant()->firstOrFail();
$menus = $tenant->menues()
->whereIn('menues.code', self::MENU_CODES)
->get()
->keyBy('code');
return response()->json([
'data' => collect(self::MENU_CODES)->mapWithKeys(
fn (string $code): array => [$code => [
'code' => $code,
'is_enabled' => (bool) ($menus->get($code)?->pivot?->is_enabled ?? false),
'static_content' => $menus->get($code)?->pivot?->static_content
?? $this->emptyContent($code),
]]
),
]);
}
public function toggle(
Request $request,
string $menuCode,
): JsonResponse {
abort_unless(in_array($menuCode, self::MENU_CODES, true), 404);
$tenant = $request->user()->tenant()->firstOrFail();
$tenantMenu = $tenant->menues()
->where('menues.code', $menuCode)
->firstOrFail();
$isEnabled = ! $tenantMenu->pivot->is_enabled;
$tenant->menues()->updateExistingPivot($menuCode, [
'is_enabled' => $isEnabled,
]);
return response()->json([
'data' => [
'code' => $tenantMenu->code,
'is_enabled' => $isEnabled,
],
]);
}
public function updateContent(
UpdateHelpMenuContentRequest $request,
string $menuCode,
): JsonResponse {
abort_unless(in_array($menuCode, self::MENU_CODES, true), 404);
$tenant = $request->user()->tenant()->firstOrFail();
$tenantMenu = $tenant->menues()
->where('menues.code', $menuCode)
->firstOrFail();
$staticContent = $request->validated('static_content');
$tenant->menues()->updateExistingPivot($menuCode, [
'static_content' => $staticContent,
]);
return response()->json([
'data' => [
'code' => $tenantMenu->code,
'is_enabled' => (bool) $tenantMenu->pivot->is_enabled,
'static_content' => $staticContent,
],
]);
}
private function emptyContent(string $menuCode): array
{
return $menuCode === 'help.faq' ? [] : ['html' => ''];
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Domains\Core\Tenant\Requests\AdminApp;
use Illuminate\Foundation\Http\FormRequest;
class UpdateHelpMenuContentRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
/** @return array<string, mixed> */
public function rules(): array
{
return match ($this->route('menuCode')) {
'help.faq' => [
'static_content' => ['required', 'array'],
'static_content.*.pregunta' => ['required', 'string'],
'static_content.*.respuesta' => ['required', 'string'],
'static_content.*.is_active' => ['required', 'boolean'],
],
'help.terms-and-conditions' => [
'static_content' => ['required', 'array'],
'static_content.html' => ['present', 'nullable', 'string'],
],
default => [],
};
}
}

View File

@@ -1,6 +1,7 @@
<?php
use App\Domains\Core\Tenant\Controllers\AdminApp\BrandController;
use App\Domains\Core\Tenant\Controllers\AdminApp\HelpMenuController;
use App\Domains\Core\Tenant\Controllers\AdminApp\WebsiteExtraController;
use Illuminate\Support\Facades\Route;
@@ -9,6 +10,9 @@ Route::prefix('v1/adminapp/tenant')
->group(function (): void {
Route::get('brand', [BrandController::class, 'show']);
Route::put('brand', [BrandController::class, 'update']);
Route::get('help-menus', [HelpMenuController::class, 'index']);
Route::patch('help-menus/{menuCode}/toggle', [HelpMenuController::class, 'toggle']);
Route::put('help-menus/{menuCode}/content', [HelpMenuController::class, 'updateContent']);
Route::get('website-extras', [WebsiteExtraController::class, 'show']);
Route::get('website-extras/{websiteExtraCode}', [WebsiteExtraController::class, 'showExtra']);
Route::put('website-extras/{websiteExtraCode}', [WebsiteExtraController::class, 'update']);

View File

@@ -0,0 +1,176 @@
<?php
namespace Tests\Feature\Tenant;
use App\Domains\Core\Auth\Models\User;
use App\Domains\Core\Authorization\Enums\RoleCode;
use App\Domains\Core\Menu\Models\Menu;
use App\Domains\Core\Tenant\Models\Tenant;
use Database\Seeders\AuthorizationSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Sanctum\Sanctum;
use Tests\TestCase;
class AdminAppHelpMenuControllerTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
$this->seed(AuthorizationSeeder::class);
}
public function test_authentication_is_required(): void
{
$this->getJson('/api/v1/adminapp/tenant/help-menus')
->assertUnauthorized();
}
public function test_adminapp_user_can_read_help_menu_states(): void
{
[$tenant] = $this->tenantWithHelpMenus();
Sanctum::actingAs($this->createAdminAppUser($tenant));
$response = $this->getJson('/api/v1/adminapp/tenant/help-menus')
->assertOk();
$states = $response->json('data');
$this->assertFalse($states['help.faq']['is_enabled']);
$this->assertTrue($states['help.terms-and-conditions']['is_enabled']);
$this->assertSame('¿Cómo compro?', $states['help.faq']['static_content'][0]['pregunta']);
$this->assertSame(['html' => ''], $states['help.terms-and-conditions']['static_content']);
$this->assertArrayNotHasKey('help.contact', $states);
}
public function test_toggling_a_help_menu_preserves_its_static_content(): void
{
[$tenant, $faq, $content] = $this->tenantWithHelpMenus();
Sanctum::actingAs($this->createAdminAppUser($tenant));
$this->patchJson('/api/v1/adminapp/tenant/help-menus/help.faq/toggle')
->assertOk()
->assertJsonPath('data.code', 'help.faq')
->assertJsonPath('data.is_enabled', true);
$pivot = $tenant->menues()->where('menues.code', $faq->code)->firstOrFail()->pivot;
$this->assertTrue($pivot->is_enabled);
$this->assertSame($content, $pivot->static_content);
}
public function test_an_unrelated_menu_cannot_be_toggled_from_the_help_endpoint(): void
{
[$tenant] = $this->tenantWithHelpMenus();
Sanctum::actingAs($this->createAdminAppUser($tenant));
$this->patchJson('/api/v1/adminapp/tenant/help-menus/catalog/toggle')
->assertNotFound();
}
public function test_contact_menu_cannot_be_toggled(): void
{
[$tenant] = $this->tenantWithHelpMenus();
Sanctum::actingAs($this->createAdminAppUser($tenant));
$this->patchJson('/api/v1/adminapp/tenant/help-menus/help.contact/toggle')
->assertNotFound();
$this->assertTrue(
$tenant->menues()->where('menues.code', 'help.contact')->firstOrFail()->pivot->is_enabled
);
}
public function test_adminapp_user_can_update_faq_content_without_changing_its_state(): void
{
[$tenant] = $this->tenantWithHelpMenus();
Sanctum::actingAs($this->createAdminAppUser($tenant));
$content = [[
'pregunta' => '¿Cuál es el horario?',
'respuesta' => 'De lunes a viernes.',
'is_active' => true,
]];
$this->putJson('/api/v1/adminapp/tenant/help-menus/help.faq/content', [
'static_content' => $content,
])
->assertOk()
->assertJsonPath('data.is_enabled', false)
->assertJsonPath('data.static_content.0.pregunta', '¿Cuál es el horario?');
$pivot = $tenant->menues()->where('menues.code', 'help.faq')->firstOrFail()->pivot;
$this->assertFalse($pivot->is_enabled);
$this->assertSame($content, $pivot->static_content);
}
public function test_adminapp_user_can_update_terms_content(): void
{
[$tenant] = $this->tenantWithHelpMenus();
Sanctum::actingAs($this->createAdminAppUser($tenant));
$this->putJson('/api/v1/adminapp/tenant/help-menus/help.terms-and-conditions/content', [
'static_content' => ['html' => '<p>Nuevos términos</p>'],
])
->assertOk()
->assertJsonPath('data.static_content.html', '<p>Nuevos términos</p>');
$this->assertSame(
['html' => '<p>Nuevos términos</p>'],
$tenant->menues()
->where('menues.code', 'help.terms-and-conditions')
->firstOrFail()
->pivot
->static_content
);
}
/** @return array{Tenant, Menu, array<int, array<string, mixed>>} */
private function tenantWithHelpMenus(): array
{
$tenant = Tenant::query()->create([
'codigo' => 'acme',
'nombre' => 'Acme',
'dominio' => 'acme.test',
]);
$content = [[
'pregunta' => '¿Cómo compro?',
'respuesta' => 'Desde el catálogo.',
'is_active' => true,
]];
$faq = Menu::query()->create([
'code' => 'help.faq',
'label' => 'Preguntas frecuentes',
'content_type' => Menu::CONTENT_TYPE_STATIC,
'static_content_schema' => ['*.pregunta' => 'required|string'],
'route' => '/ayuda/preguntas-frecuentes',
]);
$contact = Menu::query()->create([
'code' => 'help.contact',
'label' => 'Contacto',
'route' => '/ayuda/contacto',
]);
$terms = Menu::query()->create([
'code' => 'help.terms-and-conditions',
'label' => 'Términos y condiciones',
'route' => '/ayuda/terminos-y-condiciones',
]);
$tenant->menues()->attach($faq->code, [
'static_content' => $content,
'is_enabled' => false,
]);
$tenant->menues()->attach($contact->code, ['is_enabled' => true]);
$tenant->menues()->attach($terms->code, ['is_enabled' => true]);
return [$tenant, $faq, $content];
}
private function createAdminAppUser(Tenant $tenant): User
{
return User::factory()->create([
'rol_codigo' => RoleCode::AdminApp->value,
'tenant_codigo' => $tenant->codigo,
]);
}
}