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 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 | 4x 4x 4x 4x 2x 2x 2x 2x 1x 2x 2x 2x 1x 2x 1x 2x 1x 2x 1x 4x 3x 3x 3x 1x 3x 2x 2x 1x 3x 1x 3x 2x 1x 2x 2x 3x 2x 3x 2x 2x 1x 2x 1x 4x | /**
* Topic Service (Story 5.2)
*
* HTTP client for Topic Management APIs
* Features:
* - Get topics with filtering and pagination (GET /api/v1/topics)
* - Get topic by ID (GET /api/v1/topics/{id})
* - Create new topic (POST /api/v1/topics)
* - Override staleness score (PUT /api/v1/topics/{id}/override-staleness)
* - Get similar topics (GET /api/v1/topics/{id}/similar)
* - Select topic for event (POST /api/v1/events/{eventCode}/topics)
* - JWT authentication via interceptors
* - Error handling with correlation IDs
*/
import apiClient from '@/services/api/apiClient';
import type {
Topic,
TopicListResponse,
CreateTopicRequest,
OverrideStalenesRequest,
TopicFilters,
SelectTopicForEventRequest,
TopicSelectionResponse,
TopicUsageHistory,
} from '@/types/topic.types';
// API base paths
const TOPICS_API_PATH = '/topics';
const EVENTS_API_PATH = '/events';
/**
* Topic Service
*
* Handles all HTTP requests for topic management
*/
class TopicService {
/**
* Get topics with filtering and pagination (AC1)
*
* @param filters Optional filters for category, status, pagination, etc.
* @returns Paginated list of topics
* @throws Error if network failure or unauthorized
*/
async getTopics(filters?: TopicFilters): Promise<TopicListResponse> {
const params: Record<string, string | number> = {};
if (filters) {
// Build filter JSON for category/status filtering
const filterObj: Record<string, unknown> = {};
Eif (filters.category) {
filterObj.category = filters.category;
}
if (filters.status) {
filterObj.status = filters.status;
}
Eif (Object.keys(filterObj).length > 0) {
params.filter = JSON.stringify(filterObj);
}
if (filters.sort) {
params.sort = filters.sort;
}
if (filters.page) {
params.page = filters.page;
}
if (filters.limit) {
params.limit = filters.limit;
}
if (filters.include) {
params.include = filters.include;
}
}
const response = await apiClient.get<TopicListResponse>(TOPICS_API_PATH, { params });
return response.data;
}
/**
* Get topic by ID with optional includes
*
* @param id Topic UUID
* @param include Comma-separated resources to include (e.g., "similarity,history")
* @returns Topic details
* @throws Error if topic not found or network failure
*/
async getTopicById(id: string, include?: string): Promise<Topic> {
const params: Record<string, string> = {};
if (include) {
params.include = include;
}
const response = await apiClient.get<Topic>(`${TOPICS_API_PATH}/${id}`, { params });
return response.data;
}
/**
* Create new topic (AC8)
*
* @param request Topic creation request
* @returns Created topic
* @throws Error if validation fails or unauthorized
*/
async createTopic(request: CreateTopicRequest): Promise<Topic> {
const response = await apiClient.post<Topic>(TOPICS_API_PATH, request);
return response.data;
}
/**
* Update existing topic (Story 5.2a - Edit Topic Feature)
*
* @param id Topic UUID
* @param request Topic update request
* @returns Updated topic
* @throws Error if topic not found, validation fails, or unauthorized
*/
async updateTopic(id: string, request: CreateTopicRequest): Promise<Topic> {
const response = await apiClient.put<Topic>(`${TOPICS_API_PATH}/${id}`, request);
return response.data;
}
/**
* Delete topic (Story 5.2a - Delete Topic Feature)
* Only allowed if topic has never been used (no events attached).
*
* @param id Topic UUID
* @throws Error if topic not found, has been used, or unauthorized
*/
async deleteTopic(id: string): Promise<void> {
await apiClient.delete(`${TOPICS_API_PATH}/${id}`);
}
/**
* Override staleness score with justification (AC7)
*
* @param id Topic UUID
* @param request Override request with staleness score and justification
* @returns Updated topic
* @throws Error if topic not found or validation fails
*/
async overrideStaleness(id: string, request: OverrideStalenesRequest): Promise<Topic> {
const response = await apiClient.put<Topic>(
`${TOPICS_API_PATH}/${id}/override-staleness`,
request
);
return response.data;
}
/**
* Get similar topics (>70% similarity) for duplicate detection (AC5)
*
* @param id Topic UUID
* @returns List of similar topics
* @throws Error if topic not found or network failure
*/
async getSimilarTopics(id: string): Promise<Topic[]> {
const response = await apiClient.get<Topic[]>(`${TOPICS_API_PATH}/${id}/similar`);
return response.data;
}
/**
* Get topic usage history for heat map visualization (AC2)
*
* @param id Topic UUID
* @returns List of usage history records ordered by date descending
* @throws Error if topic not found or network failure
*/
async getTopicUsageHistory(id: string): Promise<TopicUsageHistory[]> {
const response = await apiClient.get<TopicUsageHistory[]>(
`${TOPICS_API_PATH}/${id}/usage-history`
);
return response.data;
}
/**
* Select topic for event and transition workflow state (AC14)
*
* @param eventCode Event code (e.g., "BATbern56")
* @param request Request with topicCode and optional justification (generated DTO)
* @returns TopicSelectionResponse with event and topic details (generated DTO)
* @throws Error if event/topic not found, invalid state, or unauthorized
*/
async selectTopicForEvent(
eventCode: string,
request: SelectTopicForEventRequest
): Promise<TopicSelectionResponse> {
const response = await apiClient.post<TopicSelectionResponse>(
`${EVENTS_API_PATH}/${eventCode}/topics`,
request
);
return response.data;
}
/**
* Recalculate staleness scores for all topics (maintenance endpoint)
*
* @returns Success message
* @throws Error if unauthorized (ORGANIZER only)
*/
async recalculateStaleness(): Promise<string> {
const response = await apiClient.post<string>(`${TOPICS_API_PATH}/recalculate-staleness`);
return response.data;
}
/**
* Calculate similarity scores for all topics (maintenance endpoint, AC4)
*
* @returns Success message
* @throws Error if unauthorized (ORGANIZER only)
*/
async calculateSimilarities(): Promise<string> {
const response = await apiClient.post<{ message?: string }>(
`${TOPICS_API_PATH}/calculate-similarities`
);
return response.data.message ?? '';
}
}
// Export singleton instance
export const topicService = new TopicService();
// Export class for testing
export default TopicService;
|