# Request Deletion (Admin Feature)

**Status:** ✅ Implemented

Admin feature for deleting requests with intelligent cleanup of media files and torrents.

## Overview

Allows admins to delete requests from the admin dashboard with smart handling of:
- Soft deletion (allows re-requesting)
- Media file cleanup
- Torrent seeding management
- Orphaned download tracking

## Key Features

1. **Soft Delete** - Preserves request history, allows re-requesting
2. **1:1 Request-to-Files** - No duplicate requests for same audiobook
3. **Seeding Awareness** - Keeps torrents seeding until requirements met
4. **Confirmation Dialog** - Prevents accidental deletions
5. **Automatic Cleanup** - Scheduled job handles orphaned downloads

## User Flow

### Admin Dashboard

1. Navigate to Admin Dashboard → Recent Requests table
2. Click "Delete" button next to request
3. Review confirmation dialog with details:
   - Request title
   - Actions that will be taken
   - Warning about re-requesting
4. Click "Delete" to confirm or "Cancel" to abort
5. Request deleted, UI updates automatically

## Technical Implementation

### Database Schema

**Soft Delete Fields:**
```prisma
model Request {
  // ... existing fields ...
  deletedAt  DateTime? @map("deleted_at")
  deletedBy  String?   @map("deleted_by")
}
```

**Unique Constraint:** Removed from schema, enforced in application code

### Deletion Logic Flow

**Service:** `src/lib/services/request-delete.service.ts`

**Steps:**

1. **Find Request**
   - Query: `deletedAt: null`
   - Return 404 if not found or already deleted

2. **Handle Downloads & Seeding**

   For each selected download:

   ```
   IF torrent not in qBittorrent:
     → Skip (already removed)

   ELSE IF unlimited seeding (0):
     → Log: "Keeping for unlimited seeding"
     → Do nothing (stop monitoring)
     → torrentsKeptUnlimited++

   ELSE IF download not completed:
     → Delete torrent + files
     → torrentsRemoved++

   ELSE:
     → Query actual seeding time
     → Calculate remaining = (target - actual)

     IF remaining > 0:
       → Log: "Keeping for X more minutes"
       → torrentsKeptSeeding++
     ELSE:
       → Delete torrent + files
       → torrentsRemoved++
   ```

3. **Delete Media Files**
   - Path: `[media_dir]/[author]/[title]/`
   - **ONLY deletes title folder** (not author folder)
   - Handles missing folders gracefully

4. **Delete from Library Backend**
   - **Audiobookshelf Mode:** Delete library item via API if `absItemId` exists
     - Prevents "ghost" entries in Audiobookshelf library
     - Only removes from ABS database, not files (already deleted in step 3)
   - **Plex Mode:** Delete library item via API if `plexGuid` exists
     - Queries plex_library table to get plexRatingKey from audiobook's plexGuid
     - Calls Plex DELETE `/library/metadata/{ratingKey}` endpoint with the ratingKey
     - Requires deletion enabled in Plex: Settings > Server > Library

5. **Delete plex_library Cache Records**
   - **Primary:** Delete by ASIN (same query as availability check)
     - `WHERE asin = audiobookAsin OR plexGuid CONTAINS audiobookAsin`
     - Ensures exact same record found during availability check gets deleted
   - **Fallback:** Delete by exact title/author (for legacy records without ASIN)
     - Only used if ASIN-based deletion finds no records
   - **Result:** Book immediately shows as NOT available, can be re-requested

6. **Clear Audiobook Linkage**
   - Reset audiobook.status to 'requested'
   - Clear plexGuid (Plex mode) or absItemId (ABS mode)

7. **Soft Delete Request**
   - UPDATE: `deletedAt = NOW(), deletedBy = adminUserId`
   - Preserves for audit trail and orphaned download tracking

### Cleanup Job Enhancement

**Processor:** `src/lib/processors/cleanup-seeded-torrents.processor.ts`

**Query:** Finds both active + soft-deleted requests

```typescript
where: {
  OR: [
    { status: ['available', 'downloaded'], deletedAt: null },
    { deletedAt: { not: null } }
  ]
}
```

**Behavior:**
- **Active requests:** Delete torrent when seeding complete
- **Soft-deleted requests:** Delete torrent + hard-delete request when seeding complete
- **Unlimited seeding:** Hard-delete orphaned request immediately (no monitoring)

### API Endpoint

**DELETE** `/api/admin/requests/:id`

**Authorization:** Admin only

**Request:** No body

**Response:**
```json
{
  "success": true,
  "message": "Request deleted successfully",
  "details": {
    "filesDeleted": true,
    "torrentsRemoved": 2,
    "torrentsKeptSeeding": 1,
    "torrentsKeptUnlimited": 0
  }
}
```

**Errors:**
- 401: Unauthorized (not logged in)
- 403: Forbidden (not admin)
- 404: Request not found or already deleted
- 500: Internal server error

### Frontend Components

**ConfirmDialog** (`src/app/admin/components/ConfirmDialog.tsx`)
- Reusable confirmation modal
- Props: title, message, confirmLabel, confirmVariant
- Supports danger (red) and primary (blue) variants

**RecentRequestsTable** (`src/app/admin/components/RecentRequestsTable.tsx`)
- Added "Actions" column with Delete button
- State management for confirmation dialog
- SWR cache invalidation after deletion
- Loading states during deletion

## Re-Requesting After Deletion

**Application-Level Uniqueness:**

All `prisma.request.findMany/findFirst` queries include:
```typescript
where: {
  // ... other conditions
  deletedAt: null  // Only active requests
}
```

**Re-Request Flow:**

1. User requests audiobook previously deleted
2. Query checks for existing request: `deletedAt: null`
3. No active request found → allowed to create new request
4. Old soft-deleted request remains in DB for audit

## Edge Cases Handled

1. ✅ **Torrent not in qBittorrent** - Skip deletion, continue with files
2. ✅ **Unlimited seeding (0)** - Keep in qBittorrent, hard-delete orphaned request
3. ✅ **Incomplete download** - Delete torrent + files immediately
4. ✅ **Seeding requirement met** - Delete torrent + files
5. ✅ **Still seeding** - Keep torrent, soft-delete request, cleanup job handles later
6. ✅ **Media folder not found** - Log and continue (already deleted)
7. ✅ **Multiple delete clicks** - Button disabled during deletion
8. ✅ **Network error** - Alert shown, request remains
9. ✅ **ABS library item deletion fails** - Log error, continue with soft delete
10. ✅ **No absItemId present** - Skip ABS deletion (not yet in library)
11. ✅ **Plex library item deletion fails** - Log error, continue with soft delete
12. ✅ **No plexGuid present** - Skip Plex deletion (not yet in library)
13. ✅ **Plex deletion not enabled in settings** - Log error, continue with soft delete
14. ✅ **Title mismatch in plex_library** - ASIN-based deletion handles title variations (e.g., "(Unabridged)" suffix)
15. ✅ **No ASIN available** - Falls back to exact title/author matching

## Fixed Issues ✅

**1. Book Shows "Available" After Deletion Until Library Scan**
- **Issue:** Deleted books remained "available" until the next library scan
- **Cause:** plex_library deletion used title/author matching, but availability check used ASIN matching
- **Impact:** Title variations (e.g., "Book Title" vs "Book Title (Unabridged)") caused plex_library records to persist
- **Fix:** Changed plex_library deletion to use ASIN-based matching (same as availability check)
- **Result:** Books immediately show as NOT available after deletion, can be re-requested right away

## File Structure

```
Backend:
- prisma/schema.prisma (deletedAt, deletedBy fields)
- src/lib/services/request-delete.service.ts (deletion logic)
- src/app/api/admin/requests/[id]/route.ts (DELETE endpoint)
- src/lib/processors/cleanup-seeded-torrents.processor.ts (orphaned cleanup)

Frontend:
- src/app/admin/components/ConfirmDialog.tsx (confirmation modal)
- src/app/admin/components/RecentRequestsTable.tsx (Delete button + logic)

Queries Updated (deletedAt: null filters):
- src/app/api/requests/route.ts (GET, POST)
- src/app/api/requests/[id]/route.ts (GET, PATCH)
- src/app/api/admin/requests/recent/route.ts (GET)
- src/app/api/admin/metrics/route.ts (GET)
- src/app/api/admin/downloads/active/route.ts (GET)
- src/lib/processors/*.ts (all processors)
```

## Configuration

**No new config required** - uses existing:
- `prowlarr_indexers` (seeding time per indexer)
- `media_dir` (file deletion path)

## Security

- **Authorization:** Admin role required
- **Audit Trail:** `deletedBy` tracks admin user ID
- **Soft Delete:** Preserves history, prevents permanent data loss
- **Confirmation Required:** Prevents accidental deletion

## Monitoring & Logging

**Logs:**
- `[RequestDelete]` prefix for deletion service
- `[CleanupSeededTorrents]` prefix for cleanup job
- Torrent status (removed/kept/unlimited)
- File deletion success/failure
- Orphaned request hard deletion

**Admin Dashboard:**
- Request count updates after deletion
- Recent requests table refreshes automatically
- Toast notifications (via console.log - can be enhanced)

## Future Enhancements

- Toast notifications instead of console.log
- Deletion history view (soft-deleted requests)
- Bulk delete operations
- Restore deleted requests (undo)
- Email notifications for deletions
- Deletion reason/notes field

## Related

- [Admin Dashboard](../admin-dashboard.md) - Dashboard overview
- [Scheduler](../backend/services/scheduler.md) - Cleanup job details
- [File Organization](../phase3/file-organization.md) - Media directory structure
- [qBittorrent](../phase3/qbittorrent.md) - Torrent management
