All files / src/services speakerStatusService.ts

100% Statements 9/9
100% Branches 2/2
100% Functions 3/3
100% Lines 9/9

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                                                  1x                                                       15x         15x         8x                           7x       3x                                 7x       4x         1x        
/**
 * Speaker Status Service (Story 5.4)
 *
 * HTTP client for Speaker Status Management APIs
 * Features:
 * - Update speaker status with workflow validation
 * - Get speaker status history with audit trail
 * - Get status dashboard summary with acceptance metrics
 * - JWT authentication via interceptors
 * - Error handling with correlation IDs
 */
 
import apiClient from '@/services/api/apiClient';
import type { components } from '@/types/generated/speakers-api.types';
import type { SpeakerWorkflowState } from '@/types/speakerPool.types';
 
// Type aliases for generated types
type UpdateStatusRequest = Omit<components['schemas']['UpdateStatusRequest'], 'newStatus'> & {
  newStatus: SpeakerWorkflowState;
};
type SpeakerStatusResponse = components['schemas']['SpeakerStatusResponse'];
type StatusHistoryItem = components['schemas']['StatusHistoryItem'];
type StatusSummaryResponse = components['schemas']['StatusSummaryResponse'];
 
// API base path
const EVENTS_API_PATH = '/events';
 
/**
 * Speaker Status Service
 *
 * Handles all HTTP requests for speaker status management
 */
class SpeakerStatusService {
  /**
   * Update speaker status.
   *
   * Valid transitions are defined in ADR-009 ยง0.2 (target model: 8-state
   * machine). Invalid transitions are rejected server-side; DECLINED is
   * reachable from every non-terminal state.
   *
   * @param eventCode Event code (e.g., "BATbern56")
   * @param speakerId Speaker pool UUID
   * @param newStatus New workflow state
   * @param reason Optional reason for status change (max 2000 characters)
   * @returns Updated speaker status with change details
   * @throws Error if validation fails (400), speaker not found (404), or invalid transition (422)
   */
  async updateStatus(
    eventCode: string,
    speakerId: string,
    newStatus: SpeakerWorkflowState,
    reason?: string
  ): Promise<SpeakerStatusResponse> {
    const request: UpdateStatusRequest = {
      newStatus,
      ...(reason && { reason }),
    };
 
    const response = await apiClient.put<SpeakerStatusResponse>(
      `${EVENTS_API_PATH}/${eventCode}/speakers/${speakerId}/status`,
      request
    );
 
    return response.data;
  }
 
  /**
   * Get speaker status history (AC3-4, AC15)
   *
   * Retrieves complete status change history with timestamps, organizers, and reasons
   *
   * @param eventCode Event code (e.g., "BATbern56")
   * @param speakerId Speaker pool UUID
   * @returns Array of status history items ordered by changedAt (newest first)
   * @throws Error if speaker not found (404) or unauthorized (401, 403)
   */
  async getStatusHistory(eventCode: string, speakerId: string): Promise<StatusHistoryItem[]> {
    const response = await apiClient.get<StatusHistoryItem[]>(
      `${EVENTS_API_PATH}/${eventCode}/speakers/${speakerId}/status/history`
    );
 
    return response.data;
  }
 
  /**
   * Get status dashboard summary (AC5-6, AC13)
   *
   * Returns aggregated speaker status metrics including:
   * - Status counts by workflow state
   * - Acceptance rate percentage
   * - Threshold status (min/max slots)
   * - Overflow detection
   *
   * @param eventCode Event code (e.g., "BATbern56")
   * @returns Status summary with counts, rates, and threshold indicators
   * @throws Error if event not found (404) or unauthorized (401, 403)
   */
  async getStatusSummary(eventCode: string): Promise<StatusSummaryResponse> {
    const response = await apiClient.get<StatusSummaryResponse>(
      `${EVENTS_API_PATH}/${eventCode}/speakers/status-summary`
    );
 
    return response.data;
  }
}
 
// Export singleton instance
export const speakerStatusService = new SpeakerStatusService();
 
// Export class for testing
export default SpeakerStatusService;