57898f939c
- create: auto-delete REJECTED→throw error; add tenantId filter - remove: forbid PUBLISHED bank deletion - removeItem: forbid PUBLISHED item deletion - generateQuestions: restrict to DRAFT status only - frontend: render MULTIPLE_CHOICE options/judgment/followupHints - frontend: add judgment and followupHints to QuestionBankItem type - add 12 service guard tests (109 total)
169 lines
6.3 KiB
TypeScript
169 lines
6.3 KiB
TypeScript
import { apiClient } from './apiClient';
|
|
|
|
export interface QuestionBank {
|
|
id: string;
|
|
name: string;
|
|
description?: string;
|
|
status: 'DRAFT' | 'PENDING_REVIEW' | 'PUBLISHED' | 'REJECTED';
|
|
templateId?: string | null;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export interface QuestionBankItem {
|
|
id: string;
|
|
bankId: string;
|
|
questionText: string;
|
|
questionType: 'SHORT_ANSWER' | 'MULTIPLE_CHOICE' | 'TRUE_FALSE';
|
|
options?: string[] | null;
|
|
correctAnswer?: string | null;
|
|
judgment?: string | null;
|
|
followupHints?: string[] | null;
|
|
keyPoints: string[];
|
|
difficulty: 'STANDARD' | 'ADVANCED' | 'SPECIALIST';
|
|
dimension: 'PROMPT' | 'LLM' | 'IDE' | 'DEV_PATTERN' | 'WORK_CAPABILITY';
|
|
basis?: string | null;
|
|
status: 'PENDING_REVIEW' | 'PUBLISHED';
|
|
createdAt: string;
|
|
}
|
|
|
|
export interface CreateQuestionBankDto {
|
|
name: string;
|
|
description?: string;
|
|
templateId?: string;
|
|
}
|
|
|
|
export interface CreateQuestionBankItemDto {
|
|
questionText: string;
|
|
questionType: 'SHORT_ANSWER' | 'MULTIPLE_CHOICE' | 'TRUE_FALSE';
|
|
options?: string[];
|
|
correctAnswer?: string;
|
|
keyPoints: string[];
|
|
difficulty: 'STANDARD' | 'ADVANCED' | 'SPECIALIST';
|
|
dimension: 'PROMPT' | 'LLM' | 'IDE' | 'DEV_PATTERN' | 'WORK_CAPABILITY';
|
|
}
|
|
|
|
export const questionBankService = {
|
|
async getBanks(): Promise<QuestionBank[]> {
|
|
const response = await apiClient.request('/question-banks', {});
|
|
if (!response.ok) throw new Error('Failed to fetch question banks');
|
|
const data = await response.json();
|
|
return Array.isArray(data) ? data : data.data || [];
|
|
},
|
|
|
|
async getBank(id: string): Promise<QuestionBank> {
|
|
const response = await apiClient.request(`/question-banks/${id}`, {});
|
|
if (!response.ok) throw new Error('Failed to fetch question bank');
|
|
return await response.json();
|
|
},
|
|
|
|
async createBank(data: CreateQuestionBankDto): Promise<QuestionBank> {
|
|
const response = await apiClient.post<QuestionBank>('/question-banks', data);
|
|
if (response.status >= 400) throw new Error('Failed to create question bank');
|
|
return response.data;
|
|
},
|
|
|
|
async updateBank(id: string, data: Partial<CreateQuestionBankDto>): Promise<QuestionBank> {
|
|
const response = await apiClient.request(`/question-banks/${id}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(data),
|
|
});
|
|
if (!response.ok) throw new Error('Failed to update question bank');
|
|
return await response.json();
|
|
},
|
|
|
|
async deleteBank(id: string): Promise<void> {
|
|
const response = await apiClient.request(`/question-banks/${id}`, { method: 'DELETE' });
|
|
if (!response.ok) throw new Error('Failed to delete question bank');
|
|
},
|
|
|
|
async submitForReview(id: string): Promise<QuestionBank> {
|
|
const response = await apiClient.request(`/question-banks/${id}/submit`, { method: 'PUT' });
|
|
if (!response.ok) throw new Error('Failed to submit for review');
|
|
return await response.json();
|
|
},
|
|
|
|
async approveBank(id: string): Promise<QuestionBank> {
|
|
const response = await apiClient.request(`/question-banks/${id}/review`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ approved: true }),
|
|
});
|
|
if (!response.ok) throw new Error('Failed to approve');
|
|
return await response.json();
|
|
},
|
|
|
|
async rejectBank(id: string): Promise<QuestionBank> {
|
|
const response = await apiClient.request(`/question-banks/${id}/review`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ approved: false }),
|
|
});
|
|
if (!response.ok) throw new Error('Failed to reject');
|
|
return await response.json();
|
|
},
|
|
|
|
async publishBank(id: string): Promise<QuestionBank> {
|
|
const response = await apiClient.request(`/question-banks/${id}/publish`, { method: 'PUT' });
|
|
if (!response.ok) throw new Error('Failed to publish');
|
|
return await response.json();
|
|
},
|
|
|
|
async getBankItems(bankId: string): Promise<QuestionBankItem[]> {
|
|
const response = await apiClient.request(`/question-banks/${bankId}`, {});
|
|
if (!response.ok) throw new Error('Failed to fetch items');
|
|
const data = await response.json();
|
|
return data.items || [];
|
|
},
|
|
|
|
async createItem(bankId: string, data: CreateQuestionBankItemDto): Promise<QuestionBankItem> {
|
|
const response = await apiClient.request(`/question-banks/${bankId}/items`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(data),
|
|
});
|
|
if (!response.ok) throw new Error('Failed to create item');
|
|
return await response.json();
|
|
},
|
|
|
|
async updateItem(bankId: string, itemId: string, data: Partial<CreateQuestionBankItemDto>): Promise<QuestionBankItem> {
|
|
const response = await apiClient.request(`/question-banks/${bankId}/items/${itemId}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(data),
|
|
});
|
|
if (!response.ok) throw new Error('Failed to update item');
|
|
return await response.json();
|
|
},
|
|
|
|
async deleteItem(bankId: string, itemId: string): Promise<void> {
|
|
const response = await apiClient.request(`/question-banks/${bankId}/items/${itemId}`, { method: 'DELETE' });
|
|
if (!response.ok) throw new Error('Failed to delete item');
|
|
},
|
|
|
|
async generateQuestions(bankId: string, count: number, knowledgeBaseContent: string): Promise<QuestionBankItem[]> {
|
|
const response = await apiClient.request(`/question-banks/${bankId}/generate`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ count, knowledgeBaseContent }),
|
|
});
|
|
if (!response.ok) {
|
|
const errBody = await response.text().catch(() => '');
|
|
let msg = 'Failed to generate questions';
|
|
try { const parsed = JSON.parse(errBody); if (parsed.message) msg = parsed.message; } catch {}
|
|
throw new Error(msg);
|
|
}
|
|
return await response.json();
|
|
},
|
|
|
|
async batchReviewItems(bankId: string, itemIds: string[], approved: boolean, comment?: string): Promise<QuestionBankItem[]> {
|
|
const response = await apiClient.request(`/question-banks/${bankId}/items/batch-review`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ itemIds, approved, comment }),
|
|
});
|
|
if (!response.ok) throw new Error('Failed to batch review items');
|
|
return await response.json();
|
|
},
|
|
}; |