All files / src/app/api/admin/support/tickets/bulk route.ts

0% Statements 0/163
100% Branches 0/0
0% Functions 0/1
0% Lines 0/163

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164                                                                                                                                                                                                                                                                                                                                       
export const dynamic = "force-dynamic";

/**
 * Admin Bulk Ticket Actions API
 * PATCH /api/admin/support/tickets/bulk - Bulk actions on tickets
 */

import { NextRequest, NextResponse } from 'next/server';
import { requireAdminRole, handleAuthError } from '@/lib/auth';
import { prisma } from '@/lib/prisma';
import { BulkTicketActionSchema } from '@/lib/validation/support-schemas';
import { TicketStatus, TicketPriority } from '@/types/support';
import { handleError } from '@/lib/error-handler';
import { logger } from '@/lib/logging';

export async function PATCH(request: NextRequest) {
  try {
    const session = await requireAdminRole();
    const adminId = Number(session.user.id);

    // Validate input
    const body = await request.json();
    const validationResult = BulkTicketActionSchema.safeParse(body);
    if (!validationResult.success) {
      return NextResponse.json(
        {
          success: false,
          error: 'Validation failed',
          details: validationResult.error.flatten().fieldErrors},
        { status: 400 }
      );
    }

    const { ticketIds, action, value } = validationResult.data;

    // Verify all tickets exist
    const existingTickets = await prisma.supportTicket.findMany({
      where: { id: { in: ticketIds } },
      select: { id: true }});

    if (existingTickets.length !== ticketIds.length) {
      return NextResponse.json(
        { success: false, error: 'Some tickets not found' },
        { status: 404 }
      );
    }

    let updateData: Record<string, unknown> = {};
    let updatedCount = 0;

    switch (action) {
      case 'close':
        updateData = {
          status: TicketStatus.CLOSED,
          closedAt: new Date()};
        break;

      case 'resolve':
        updateData = {
          status: TicketStatus.RESOLVED,
          resolvedAt: new Date()};
        break;

      case 'assign':
        if (value === null || typeof value === 'number') {
          // Verify agent exists if assigning
          if (value !== null) {
            const agent = await prisma.user.findUnique({
              where: { id: value as number },
              select: { id: true, role: true }});

            if (!agent || agent.role !== 'ADMIN') {
              return NextResponse.json(
                { success: false, error: 'Invalid agent' },
                { status: 400 }
              );
            }
          }
          updateData = { assignedToId: value };
        } else {
          return NextResponse.json(
            { success: false, error: 'Invalid agent ID for assign action' },
            { status: 400 }
          );
        }
        break;

      case 'change_priority':
        if (
          typeof value === 'string' &&
          Object.values(TicketPriority).includes(value as TicketPriority)
        ) {
          updateData = { priority: value };
        } else {
          return NextResponse.json(
            { success: false, error: 'Invalid priority value' },
            { status: 400 }
          );
        }
        break;

      case 'change_status':
        if (
          typeof value === 'string' &&
          Object.values(TicketStatus).includes(value as TicketStatus)
        ) {
          updateData = { status: value };

          // Set timestamps based on status
          if (value === TicketStatus.RESOLVED) {
            updateData.resolvedAt = new Date();
          } else if (value === TicketStatus.CLOSED) {
            updateData.closedAt = new Date();
          }
        } else {
          return NextResponse.json(
            { success: false, error: 'Invalid status value' },
            { status: 400 }
          );
        }
        break;

      default:
        return NextResponse.json(
          { success: false, error: 'Invalid action' },
          { status: 400 }
        );
    }

    // Perform bulk update
    const result = await prisma.supportTicket.updateMany({
      where: { id: { in: ticketIds } },
      data: updateData});

    updatedCount = result.count;

    // Create history entries for each ticket
    await prisma.ticketHistory.createMany({
      data: ticketIds.map((ticketId) => ({
        ticketId,
        action: `bulk_${action}`,
        oldValue: null,
        newValue: value?.toString() || null,
        performedBy: adminId}))});

    logger.info('Bulk ticket action performed', {
      category: 'ADMIN_SUPPORT',
      action,
      ticketCount: updatedCount,
      adminId});

    return NextResponse.json({
      success: true,
      message: `${updatedCount} ticket(s) updated`,
      updatedCount});
  } catch (error) {
    const authResponse = handleAuthError(error as Error);
    if (authResponse) return authResponse;

    logger.error('Error performing bulk ticket action', error instanceof Error ? error : new Error(String(error)), { category: 'ADMIN_SUPPORT' });
    return handleError(error);
  }
}