libra/docs/stories/story-5.3-post-deletion.md

4.8 KiB

Story 5.3: Post Deletion

Epic Reference

Epic 5: Posts/Blog System

User Story

As an admin, I want to permanently delete posts, So that I can remove outdated or incorrect content from the website.

Story Context

Existing System Integration

  • Integrates with: posts table
  • Technology: Livewire Volt
  • Follows pattern: Permanent delete pattern
  • Touch points: Post management dashboard
  • Files to modify: Post management Volt component from Story 5.2

Acceptance Criteria

Delete Functionality

  • Delete button on post list (primary location)
  • Delete button on post edit page (secondary location)
  • Confirmation modal dialog before deletion
  • Permanent deletion (no soft delete per PRD)
  • Success message after deletion
  • Redirect to post list after deletion from edit page

Restrictions

  • Admin-only access (middleware protection)

Audit Trail

  • Audit log entry preserved
  • Old values stored in log

Quality Requirements

  • Clear warning in confirmation
  • Bilingual messages
  • Tests for deletion

Technical Notes

Delete Confirmation Modal

<flux:modal wire:model="showDeleteModal">
    <flux:heading>{{ __('admin.delete_post') }}</flux:heading>

    <flux:callout variant="danger">
        {{ __('admin.delete_post_warning') }}
    </flux:callout>

    <p class="my-4">
        {{ __('admin.deleting_post', ['title' => $postToDelete?->title]) }}
    </p>

    <div class="flex gap-3">
        <flux:button wire:click="$set('showDeleteModal', false)">
            {{ __('common.cancel') }}
        </flux:button>
        <flux:button variant="danger" wire:click="confirmDelete">
            {{ __('admin.delete_permanently') }}
        </flux:button>
    </div>
</flux:modal>

Delete Button on Edit Page

Add delete button to the post edit form (from Story 5.1) when editing an existing post:

@if($post?->exists)
    <flux:button variant="danger" wire:click="delete({{ $post->id }})">
        {{ __('admin.delete_post') }}
    </flux:button>
@endif

Delete Logic

public ?Post $postToDelete = null;
public bool $showDeleteModal = false;

public function delete(int $id): void
{
    $this->postToDelete = Post::findOrFail($id);
    $this->showDeleteModal = true;
}

public function confirmDelete(): void
{
    if (!$this->postToDelete) {
        return;
    }

    // Create audit log BEFORE deletion
    AdminLog::create([
        'admin_id' => auth()->id(),
        'action_type' => 'delete',
        'target_type' => 'post',
        'target_id' => $this->postToDelete->id,
        'old_values' => $this->postToDelete->toArray(),
        'ip_address' => request()->ip(),
    ]);

    // Permanently delete
    $this->postToDelete->delete();

    $this->showDeleteModal = false;
    $this->postToDelete = null;

    session()->flash('success', __('messages.post_deleted'));
}

Testing

use App\Models\Post;
use App\Models\AdminLog;
use App\Models\User;
use Livewire\Volt\Volt;

it('permanently deletes post', function () {
    $admin = User::factory()->create();
    $post = Post::factory()->create();

    Volt::test('admin.posts.index')
        ->actingAs($admin)
        ->call('delete', $post->id)
        ->call('confirmDelete');

    expect(Post::find($post->id))->toBeNull();
});

it('shows confirmation modal before deletion', function () {
    $admin = User::factory()->create();
    $post = Post::factory()->create();

    Volt::test('admin.posts.index')
        ->actingAs($admin)
        ->call('delete', $post->id)
        ->assertSet('showDeleteModal', true)
        ->assertSet('postToDelete.id', $post->id);
});

it('creates audit log on deletion with old values', function () {
    $admin = User::factory()->create();
    $post = Post::factory()->create(['title_en' => 'Test Title']);

    Volt::test('admin.posts.index')
        ->actingAs($admin)
        ->call('delete', $post->id)
        ->call('confirmDelete');

    $log = AdminLog::where('target_type', 'post')
        ->where('target_id', $post->id)
        ->where('action_type', 'delete')
        ->first();

    expect($log)->not->toBeNull()
        ->and($log->old_values)->toHaveKey('title_en', 'Test Title');
});

it('requires admin authentication to delete', function () {
    $post = Post::factory()->create();

    $this->delete(route('admin.posts.destroy', $post))
        ->assertRedirect(route('login'));
});

Definition of Done

  • Delete button shows confirmation
  • Confirmation explains permanence
  • Post deleted from database
  • Audit log created with old values
  • Success message displayed
  • Tests pass
  • Code formatted with Pint

Dependencies

  • Story 5.1: Post creation
  • Story 5.2: Post management dashboard

Estimation

Complexity: Low Estimated Effort: 1-2 hours