export type CurriculumWeek = { week: number; title: string; tags: string[]; progress: number; state: string; topics: string[]; duration: string; github: string; detail: { overview: string; learningSteps?: Array<{ title: string; description: string; videoEnabled?: boolean; videoUrl?: string; concepts?: string[]; }>; topicDetails: string[]; topicVideoUrls?: string[]; practiceContents?: Array<{ title: string; description: string; }>; references?: CurriculumReference[]; exampleCodes?: CurriculumReference[]; deliverable: string; requiresSourceSubmission: boolean; }; modules?: CurriculumModule[]; visualizationPlatforms?: CurriculumVisualizationPlatform[]; }; export type CurriculumModule = { id: string; sortOrder: number; category: string; name: string; folderName: string; templateRepositoryUrl?: string | null; dockerImage?: string | null; enabled?: boolean; }; export type CurriculumReference = { id: string; kind?: string; title: string; description?: string | null; fileName: string; contentType?: string | null; fileSizeBytes?: number | string | null; week?: number | string | null; weekNumber?: number | string | null; downloadUrl: string; createdAt?: string; }; export type CurriculumVisualizationPlatform = { key: string; type: string; displayName: string; description?: string | null; config?: unknown; }; export type CurriculumTagOption = { key: string; label: string; }; export type InstructorCurriculumResponse = { course: { id: string; title: string; subtitle?: string | null; description?: string | null; organizationName: string; } | null; weeks: CurriculumWeek[]; setting: { groupId?: string | null; activeWeek: number; activeLearningStep?: string | null; } | null; tagOptions?: CurriculumTagOption[]; visualizationPlatforms?: CurriculumVisualizationPlatform[]; }; const LMS_API_BASE_URL = (import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:4400/api').replace(/\/+$/, ''); export async function getInstructorCurriculum(accessToken: string, groupId?: string | null, courseId?: string | null): Promise { const params = new URLSearchParams(); if (groupId) params.set('groupId', groupId); if (courseId) params.set('courseId', courseId); const query = params.toString() ? `?${params.toString()}` : ''; const response = await fetch(`${LMS_API_BASE_URL}/instructor/curriculum${query}`, { headers: { Authorization: `Bearer ${accessToken}`, }, }); if (!response.ok) throw new Error(`커리큘럼을 불러오지 못했습니다. (${response.status})`); return response.json() as Promise; } export async function updateInstructorCurriculum( accessToken: string, input: { courseId?: string | null; groupId?: string | null; activeWeek: number; activeLearningStep?: string | null; applyToStudents?: boolean; completed?: boolean; tagLabel?: string; title?: string; duration?: string; overview?: string; deliverable?: string; requiresSourceSubmission?: boolean; topics?: string[]; learningSteps?: Array<{ title?: string; description?: string; videoEnabled?: boolean; videoUrl?: string; concepts?: string[]; }>; topicDetails?: string[]; topicVideoUrls?: string[]; practiceContents?: Array<{ title?: string; description?: string; }>; disabledModuleIds?: string[]; visualizationPlatformKeys?: string[]; }, ): Promise { const response = await fetch(`${LMS_API_BASE_URL}/instructor/curriculum`, { method: 'PUT', headers: { Authorization: `Bearer ${accessToken}`, 'content-type': 'application/json', }, body: JSON.stringify(input), }); if (!response.ok) { const payload = await response.json().catch((): null => null); const message = payload && typeof payload === 'object' && 'message' in payload ? String(payload.message) : `커리큘럼을 적용하지 못했습니다. (${response.status})`; throw new Error(message); } return response.json() as Promise; } export async function deleteInstructorCurriculumWeek( accessToken: string, week: number, ): Promise { const response = await fetch(`${LMS_API_BASE_URL}/instructor/curriculum/weeks/${week}`, { method: 'DELETE', headers: { Authorization: `Bearer ${accessToken}`, }, }); if (!response.ok) { const payload = await response.json().catch((): null => null); const message = payload && typeof payload === 'object' && 'message' in payload ? String(payload.message) : `주차를 삭제하지 못했습니다. (${response.status})`; throw new Error(message); } return response.json() as Promise; } export async function uploadCurriculumReference( accessToken: string, input: { courseId?: string | null; week: number; title?: string; file: File; }, ): Promise { const formData = new FormData(); formData.set('file', input.file); formData.set('courseId', input.courseId ?? ''); formData.set('title', input.title?.trim() || input.file.name); const response = await fetch(`${LMS_API_BASE_URL}/instructor/curriculum/weeks/${input.week}/references`, { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, }, body: formData, }); if (!response.ok) { const payload = await response.json().catch((): null => null); const message = payload && typeof payload === 'object' && 'message' in payload ? String(payload.message) : `참고자료를 업로드하지 못했습니다. (${response.status})`; throw new Error(message); } return response.json() as Promise; } export async function uploadCurriculumExampleCode( accessToken: string, input: { courseId?: string | null; week: number; title?: string; file: File; }, ): Promise { const formData = new FormData(); formData.set('file', input.file); formData.set('courseId', input.courseId ?? ''); formData.set('title', input.title?.trim() || input.file.name); const response = await fetch(`${LMS_API_BASE_URL}/instructor/curriculum/weeks/${input.week}/example-codes`, { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, }, body: formData, }); if (!response.ok) { const payload = await response.json().catch((): null => null); const message = payload && typeof payload === 'object' && 'message' in payload ? String(payload.message) : `예제 코드를 업로드하지 못했습니다. (${response.status})`; throw new Error(message); } return response.json() as Promise; } export async function deleteCurriculumReference( accessToken: string, referenceId: string, courseId?: string | null, ): Promise { const query = courseId ? `?courseId=${encodeURIComponent(courseId)}` : ''; const response = await fetch(`${LMS_API_BASE_URL}/instructor/curriculum/references/${referenceId}${query}`, { method: 'DELETE', headers: { Authorization: `Bearer ${accessToken}`, }, }); if (!response.ok) { const payload = await response.json().catch((): null => null); const message = payload && typeof payload === 'object' && 'message' in payload ? String(payload.message) : `참고자료를 삭제하지 못했습니다. (${response.status})`; throw new Error(message); } return response.json() as Promise; }