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

11 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


Dev Agent Record

Status

Ready for Review

Agent Model Used

Claude Opus 4.5

File List

Modified:

  • resources/views/livewire/admin/posts/index.blade.php - Added confirmation modal with postToDelete, showDeleteModal, confirmDelete(), cancelDelete() methods
  • resources/views/livewire/admin/posts/edit.blade.php - Added delete button and confirmation modal with delete(), confirmDelete(), cancelDelete() methods, redirect after deletion
  • lang/en/posts.php - Added delete modal translations: delete_post, delete_post_warning, deleting_post, delete_permanently
  • lang/ar/posts.php - Added Arabic delete modal translations
  • tests/Feature/Admin/PostManagementTest.php - Updated and added 10 delete-related tests

Debug Log References

None - no issues encountered

Completion Notes

  • Implemented proper confirmation modal pattern (replacing simple wire:confirm)
  • Both index and edit pages now have delete functionality with confirmation modals
  • Edit page redirects to index after successful deletion
  • Audit log captures old_values before deletion for audit trail
  • All 48 tests in PostManagementTest pass
  • Full regression suite passes (392 tests)

Change Log

Change Description
Modal-based delete Replaced browser confirm dialog with Flux UI modal for better UX
Edit page delete Added delete button to edit page with redirect to index
Bilingual support Added EN/AR translations for delete modal messages
Test coverage Added 10 new tests covering modal flow, cancel, and audit log

QA Results

Review Date: 2025-12-27

Reviewed By: Quinn (Test Architect)

Risk Assessment

Risk Level: Low - Simple delete functionality with well-contained scope

  • No auth/payment/security files modified (admin middleware already exists)
  • 10 delete-related tests added
  • Diff < 500 lines
  • Story has 6 acceptance criteria (near threshold but manageable)

Code Quality Assessment

Overall: Excellent - The implementation follows Laravel/Livewire best practices with proper separation of concerns.

Strengths:

  1. Transaction Safety: Index page uses DB::transaction() with lockForUpdate() for race condition prevention
  2. Audit Trail: Proper AdminLog entries created BEFORE deletion with old_values captured
  3. Modal UX Pattern: Flux UI modal with proper confirmation flow (delete → modal → confirmDelete)
  4. Bilingual Support: Complete EN/AR translations for all delete-related messages
  5. Error Handling: Graceful handling when post not found (both index and edit pages)
  6. Redirect Flow: Edit page properly redirects to index after deletion

Minor Observations:

  1. Edit page confirmDelete() doesn't use DB::transaction() - acceptable for single operation but inconsistent with index page pattern
  2. Edit page doesn't use lockForUpdate() - lower risk since already viewing the specific post

Requirements Traceability

AC # Acceptance Criteria Test Coverage Status
1 Delete button on post list admin can delete post from index with confirmation modal
2 Delete button on post edit page admin can delete post from edit page, edit page shows delete button
3 Confirmation modal dialog before deletion delete modal shows before deletion, edit page delete shows confirmation modal
4 Permanent deletion (no soft delete) expect(Post::find($postId))->toBeNull()
5 Success message after deletion Session flash 'success' in both components
6 Redirect to post list after deletion from edit page assertRedirect(route('admin.posts.index'))
7 Admin-only access (middleware) non-admin cannot access posts index, guest cannot access posts index
8 Audit log entry preserved with old values delete post creates audit log with old values, edit page delete creates audit log
9 Clear warning in confirmation Flux callout with delete_post_warning translation
10 Bilingual messages EN + AR lang files both have delete modal translations

Test Architecture Assessment

Coverage: Complete - All acceptance criteria have corresponding tests

Test Distribution:

  • Index delete flow: 5 tests
  • Edit page delete flow: 4 tests
  • Authorization: 4 tests (shared with other story tests)

Test Quality:

  • Uses Volt::test() pattern correctly
  • Proper factory usage with User::factory()->admin()
  • Tests both happy path and edge cases (cancel, not found)
  • Audit log assertions verify old_values content

Refactoring Performed

None required - code quality is good.

Compliance Check

  • Coding Standards: ✓ Class-based Volt components, Flux UI components used
  • Project Structure: ✓ Files in correct locations
  • Testing Strategy: ✓ Feature tests with Pest, proper Volt::test usage
  • All ACs Met: ✓ All 10 acceptance criteria verified

Improvements Checklist

  • Modal-based confirmation implemented (Flux UI)
  • Transaction safety on index page
  • Audit log with old_values before deletion
  • Bilingual translations complete
  • Edit page redirect after deletion
  • Cancel functionality restores state
  • Consider: Add transaction wrapper to edit page confirmDelete() for consistency (optional, low priority)

Security Review

  • ✓ Admin middleware protects routes
  • ✓ No SQL injection risk (using Eloquent)
  • ✓ CSRF protection via Livewire
  • ✓ No XSS concerns (user-generated content not involved in delete flow)

Performance Considerations

  • lockForUpdate() used on index page to prevent race conditions
  • ✓ Single delete operation - no N+1 concerns
  • ✓ Audit log creation is synchronous but appropriate for admin actions

Files Modified During Review

None - no modifications required.

Gate Status

Gate: PASSdocs/qa/gates/5.3-post-deletion.yml

✓ Ready for Done - All acceptance criteria met, comprehensive test coverage, proper audit trail, bilingual support complete. Implementation is clean and follows established patterns.