feat: manage tenant help menu settings
This commit is contained in:
@@ -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' => ''];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 => [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Domains\Core\Tenant\Controllers\AdminApp\BrandController;
|
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 App\Domains\Core\Tenant\Controllers\AdminApp\WebsiteExtraController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
@@ -9,6 +10,9 @@ Route::prefix('v1/adminapp/tenant')
|
|||||||
->group(function (): void {
|
->group(function (): void {
|
||||||
Route::get('brand', [BrandController::class, 'show']);
|
Route::get('brand', [BrandController::class, 'show']);
|
||||||
Route::put('brand', [BrandController::class, 'update']);
|
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', [WebsiteExtraController::class, 'show']);
|
||||||
Route::get('website-extras/{websiteExtraCode}', [WebsiteExtraController::class, 'showExtra']);
|
Route::get('website-extras/{websiteExtraCode}', [WebsiteExtraController::class, 'showExtra']);
|
||||||
Route::put('website-extras/{websiteExtraCode}', [WebsiteExtraController::class, 'update']);
|
Route::put('website-extras/{websiteExtraCode}', [WebsiteExtraController::class, 'update']);
|
||||||
|
|||||||
176
tests/Feature/Tenant/AdminAppHelpMenuControllerTest.php
Normal file
176
tests/Feature/Tenant/AdminAppHelpMenuControllerTest.php
Normal 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,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user