Add admin request deletion with soft delete and cleanup

Implements admin ability to delete requests with soft delete, media file cleanup, and seeding-aware torrent management. Adds new API endpoint, frontend confirmation dialog, and request actions dropdown. Updates database schema with deletedAt and deletedBy fields, and ensures all queries filter out deleted requests. Documentation added for feature and user flow.
This commit is contained in:
kikootwo
2025-12-22 20:24:43 -05:00
parent bba4af7398
commit 174e9f05b6
26 changed files with 1936 additions and 200 deletions
@@ -15,6 +15,7 @@ export async function GET(request: NextRequest) {
const activeDownloads = await prisma.request.findMany({
where: {
status: 'downloading',
deletedAt: null,
},
include: {
audiobook: {
+10 -2
View File
@@ -22,13 +22,18 @@ export async function GET(request: NextRequest) {
failedLast30Days,
totalUsers,
] = await Promise.all([
// Total requests (all time)
prisma.request.count(),
// Total requests (all time, only active)
prisma.request.count({
where: {
deletedAt: null,
},
}),
// Active downloads (downloading status)
prisma.request.count({
where: {
status: 'downloading',
deletedAt: null,
},
}),
@@ -41,6 +46,7 @@ export async function GET(request: NextRequest) {
completedAt: {
gte: thirtyDaysAgo,
},
deletedAt: null,
},
}),
@@ -51,6 +57,7 @@ export async function GET(request: NextRequest) {
updatedAt: {
gte: thirtyDaysAgo,
},
deletedAt: null,
},
}),
@@ -103,6 +110,7 @@ async function checkSystemHealth(): Promise<{
updatedAt: {
lt: oneDayAgo,
},
deletedAt: null,
},
});
+77
View File
@@ -0,0 +1,77 @@
/**
* Component: Admin Request Management API
* Documentation: documentation/admin-features/request-deletion.md
*/
import { NextRequest, NextResponse } from 'next/server';
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
import { deleteRequest } from '@/lib/services/request-delete.service';
/**
* DELETE /api/admin/requests/[id]
* Soft delete a request with intelligent cleanup (admin only)
*
* This endpoint:
* 1. Validates admin authorization
* 2. Soft deletes the request (sets deletedAt timestamp)
* 3. Deletes media files from the title folder
* 4. Handles torrents based on seeding configuration:
* - Unlimited seeding (0): Keeps torrent, stops monitoring
* - Seeding complete: Deletes torrent + files
* - Still seeding: Keeps torrent for cleanup job
* 5. Allows re-requesting the same audiobook after deletion
*/
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
return requireAuth(request, async (req: AuthenticatedRequest) => {
return requireAdmin(req, async () => {
try {
if (!req.user) {
return NextResponse.json(
{ error: 'Unauthorized', message: 'User not authenticated' },
{ status: 401 }
);
}
const { id } = await params;
// Perform soft delete with cleanup
const result = await deleteRequest(id, req.user.id);
if (!result.success) {
return NextResponse.json(
{
error: result.error || 'DeleteFailed',
message: result.message,
},
{ status: result.error === 'NotFound' ? 404 : 500 }
);
}
// Return detailed result
return NextResponse.json({
success: true,
message: result.message,
details: {
filesDeleted: result.filesDeleted,
torrentsRemoved: result.torrentsRemoved,
torrentsKeptSeeding: result.torrentsKeptSeeding,
torrentsKeptUnlimited: result.torrentsKeptUnlimited,
},
});
} catch (error) {
console.error('[Admin] Failed to delete request:', error);
return NextResponse.json(
{
error: 'DeleteError',
message: 'Failed to delete request',
details: error instanceof Error ? error.message : 'Unknown error',
},
{ status: 500 }
);
}
});
});
}
+4 -1
View File
@@ -11,8 +11,11 @@ export async function GET(request: NextRequest) {
return requireAuth(request, async (req: AuthenticatedRequest) => {
return requireAdmin(req, async () => {
try {
// Get recent requests
// Get recent requests (only active, non-deleted)
const recentRequests = await prisma.request.findMany({
where: {
deletedAt: null,
},
include: {
audiobook: {
select: {