// Course Response structure export interface CourseResponse { id: number; category_id: number; title: { en: string; th: string; }; slug: string; description: { en: string; th: string; }; thumbnail_url: string | null; price: string; is_free: boolean; have_certificate: boolean; status: 'DRAFT' | 'PENDING' | 'APPROVED' | 'REJECTED'; approved_by: number | null; approved_at: string | null; rejection_reason: string | null; created_at: string; created_by: number; updated_at: string; updated_by: number | null; } export interface CoursesListResponse { code: number; message: string; data: CourseResponse[]; total: number; } // Helper function to get auth token from cookie const getAuthToken = (): string => { const tokenCookie = useCookie('token'); return tokenCookie.value || ''; }; // Helper function for making authenticated requests with auto refresh const authRequest = async ( url: string, options: { method?: 'GET' | 'POST' | 'PUT' | 'DELETE'; body?: any; } = {} ): Promise => { const config = useRuntimeConfig(); const makeRequest = async (token: string) => { return await $fetch(url, { baseURL: config.public.apiBaseUrl as string, method: options.method || 'GET', headers: { Authorization: `Bearer ${token}` }, body: options.body }); }; try { const token = getAuthToken(); return await makeRequest(token); } catch (error: any) { // If 401, try to refresh token if (error.response?.status === 401) { const { handleUnauthorized } = await import('~/utils/authFetch'); const newToken = await handleUnauthorized(); if (newToken) { return await makeRequest(newToken); } // Redirect to login navigateTo('/login'); } throw error; } }; // Mock courses data const MOCK_COURSES: CourseResponse[] = [ { id: 1, category_id: 1, title: { en: 'JavaScript Fundamentals', th: 'พื้นฐาน JavaScript' }, slug: 'javascript-fundamentals', description: { en: 'Learn JavaScript fundamentals from scratch', th: 'เรียนรู้พื้นฐาน JavaScript ตั้งแต่เริ่มต้น' }, thumbnail_url: null, price: '0', is_free: true, have_certificate: true, status: 'APPROVED', approved_by: 1, approved_at: '2024-01-15T00:00:00Z', rejection_reason: null, created_at: '2024-01-15T00:00:00Z', created_by: 2, updated_at: '2024-01-15T00:00:00Z', updated_by: null }, { id: 2, category_id: 2, title: { en: 'React for Beginners', th: 'React สำหรับผู้เริ่มต้น' }, slug: 'react-for-beginners', description: { en: 'Build modern web apps with React', th: 'สร้างเว็บแอปพลิเคชันด้วย React' }, thumbnail_url: null, price: '990', is_free: false, have_certificate: true, status: 'PENDING', approved_by: null, approved_at: null, rejection_reason: null, created_at: '2024-02-01T00:00:00Z', created_by: 2, updated_at: '2024-02-01T00:00:00Z', updated_by: null }, { id: 3, category_id: 1, title: { en: 'TypeScript Masterclass', th: 'TypeScript ขั้นสูง' }, slug: 'typescript-masterclass', description: { en: 'Master TypeScript for better JavaScript development', th: 'เรียนรู้ TypeScript เพื่อพัฒนา JavaScript ได้ดียิ่งขึ้น' }, thumbnail_url: null, price: '1290', is_free: false, have_certificate: true, status: 'DRAFT', approved_by: null, approved_at: null, rejection_reason: null, created_at: '2024-02-15T00:00:00Z', created_by: 2, updated_at: '2024-02-15T00:00:00Z', updated_by: null } ]; export const instructorService = { async getCourses(): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return MOCK_COURSES; } const response = await authRequest('/api/instructors/courses'); return response.data; }, async createCourse(data: CreateCourseRequest): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return { ...MOCK_COURSES[0], id: Date.now(), ...data, price: String(data.price), // Convert number to string to match CourseResponse type status: 'DRAFT', created_at: new Date().toISOString(), updated_at: new Date().toISOString() } as CourseResponse; } // Clean data - remove empty thumbnail_url const cleanedData = { ...data }; if (!cleanedData.thumbnail_url) { delete cleanedData.thumbnail_url; } const response = await authRequest<{ code: number; data: CourseResponse }>('/api/instructors/courses', { method: 'POST', body: { data: cleanedData } }); return response.data; }, async getCourseById(courseId: number): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return MOCK_COURSE_DETAIL; } const response = await authRequest<{ code: number; data: CourseDetailResponse }>(`/api/instructors/courses/${courseId}`); return response.data; }, async updateCourse(courseId: number, data: CreateCourseRequest): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return { ...MOCK_COURSES[0], id: courseId, ...data, price: String(data.price) } as CourseResponse; } const response = await authRequest<{ code: number; data: CourseResponse }>(`/api/instructors/courses/${courseId}`, { method: 'PUT', body: { data } }); return response.data; }, async deleteCourse(courseId: number): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return; } await authRequest(`/api/instructors/courses/${courseId}`, { method: 'DELETE' }); }, async sendForReview(courseId: number): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return; } await authRequest(`/api/instructors/courses/send-review/${courseId}`, { method: 'POST' }); }, async getChapters(courseId: number): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return MOCK_COURSE_DETAIL.chapters; } // Get chapters from course detail endpoint const response = await authRequest<{ code: number; data: { chapters: ChapterResponse[] } }>( `/api/instructors/courses/${courseId}` ); return response.data.chapters || []; }, async createChapter(courseId: number, data: CreateChapterRequest): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return { ...MOCK_COURSE_DETAIL.chapters[0], id: Date.now(), ...data, lessons: [] }; } const response = await authRequest<{ code: number; data: ChapterResponse }>( `/api/instructors/courses/${courseId}/chapters`, { method: 'POST', body: data } ); return response.data; }, async updateChapter(courseId: number, chapterId: number, data: CreateChapterRequest): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return { ...MOCK_COURSE_DETAIL.chapters[0], id: chapterId, ...data }; } const response = await authRequest<{ code: number; data: ChapterResponse }>( `/api/instructors/courses/${courseId}/chapters/${chapterId}`, { method: 'PUT', body: data } ); return response.data; }, async deleteChapter(courseId: number, chapterId: number): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return; } await authRequest( `/api/instructors/courses/${courseId}/chapters/${chapterId}`, { method: 'DELETE' } ); }, async reorderChapter(courseId: number, chapterId: number, sortOrder: number): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 300)); return; } await authRequest( `/api/instructors/courses/${courseId}/chapters/${chapterId}/reorder`, { method: 'PUT', body: { sort_order: sortOrder } } ); }, // Lesson CRUD async getLesson(courseId: number, chapterId: number, lessonId: number): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return MOCK_COURSE_DETAIL.chapters[0].lessons[0] as LessonDetailResponse; } const response = await authRequest<{ code: number; data: LessonDetailResponse }>( `/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}` ); return response.data; }, async createLesson(courseId: number, chapterId: number, data: CreateLessonRequest): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return { ...MOCK_COURSE_DETAIL.chapters[0].lessons[0], id: Date.now(), ...data }; } const response = await authRequest<{ code: number; data: LessonResponse }>( `/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons`, { method: 'POST', body: data } ); return response.data; }, async updateLesson(courseId: number, chapterId: number, lessonId: number, data: UpdateLessonRequest): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return { ...MOCK_COURSE_DETAIL.chapters[0].lessons[0], id: lessonId, ...data }; } const response = await authRequest<{ code: number; data: LessonResponse }>( `/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}`, { method: 'PUT', body: data } ); return response.data; }, async deleteLesson(courseId: number, chapterId: number, lessonId: number): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 500)); return; } await authRequest( `/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}`, { method: 'DELETE' } ); }, async reorderLesson(courseId: number, chapterId: number, lessonId: number, sortOrder: number): Promise { const config = useRuntimeConfig(); const useMockData = config.public.useMockData as boolean; if (useMockData) { await new Promise(resolve => setTimeout(resolve, 300)); return; } await authRequest( `/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/reorder`, { method: 'PUT', body: { lesson_id: lessonId, sort_order: sortOrder } } ); } }; // Create course request export interface CreateCourseRequest { category_id: number; title: { en: string; th: string; }; slug: string; description: { en: string; th: string; }; thumbnail_url?: string | null; price: number; is_free: boolean; have_certificate: boolean; } // Course detail with chapters and lessons export interface CourseDetailResponse extends CourseResponse { chapters: ChapterResponse[]; } export interface ChapterResponse { id: number; course_id: number; title: { en: string; th: string }; description: { en: string; th: string }; sort_order: number; is_published: boolean; created_at: string; updated_at: string; lessons: LessonResponse[]; } export interface LessonResponse { id: number; chapter_id: number; title: { en: string; th: string }; content: { en: string; th: string } | null; type: 'VIDEO' | 'QUIZ' | 'DOCUMENT'; duration_minutes: number; sort_order: number; is_sequential: boolean; prerequisite_lesson_ids: number[] | null; require_pass_quiz: boolean; is_published: boolean; created_at: string; updated_at: string; quiz: QuizResponse | null; } export interface QuizResponse { id: number; lesson_id: number; title: { en: string; th: string }; description: { en: string; th: string }; passing_score: number; time_limit: number; shuffle_questions: boolean; shuffle_choices: boolean; show_answers_after_completion: boolean; } export interface CreateChapterRequest { title: { th: string; en: string }; description: { th: string; en: string }; sort_order?: number; is_published?: boolean; } export interface CreateLessonRequest { title: { th: string; en: string }; content?: { th: string; en: string } | null; type: 'VIDEO' | 'QUIZ'; sort_order?: number; } export interface UpdateLessonRequest { title: { th: string; en: string }; content?: { th: string; en: string } | null; } export interface AttachmentResponse { id: number; lesson_id: number; file_name: string; file_path: string; file_size: number; mime_type: string; description: { th: string; en: string }; sort_order: number; created_at: string; } export interface LessonDetailResponse extends LessonResponse { attachments: AttachmentResponse[]; } // Mock course detail const MOCK_COURSE_DETAIL: CourseDetailResponse = { ...MOCK_COURSES[0], chapters: [ { id: 1, course_id: 1, title: { en: 'Chapter 1: Getting Started', th: 'บทที่ 1: เริ่มต้น' }, description: { en: 'Introduction to JavaScript', th: 'แนะนำ JavaScript' }, sort_order: 1, is_published: true, created_at: '2024-01-15T00:00:00Z', updated_at: '2024-01-15T00:00:00Z', lessons: [ { id: 1, chapter_id: 1, title: { en: 'What is JavaScript', th: 'JavaScript คืออะไร' }, content: { en: 'Introduction', th: 'แนะนำ' }, type: 'VIDEO', duration_minutes: 15, sort_order: 1, is_sequential: true, prerequisite_lesson_ids: null, require_pass_quiz: false, is_published: true, created_at: '2024-01-15T00:00:00Z', updated_at: '2024-01-15T00:00:00Z', quiz: null }, { id: 2, chapter_id: 1, title: { en: 'Variables', th: 'ตัวแปร' }, content: { en: 'Learn variables', th: 'เรียนรู้ตัวแปร' }, type: 'VIDEO', duration_minutes: 20, sort_order: 2, is_sequential: true, prerequisite_lesson_ids: null, require_pass_quiz: false, is_published: true, created_at: '2024-01-15T00:00:00Z', updated_at: '2024-01-15T00:00:00Z', quiz: null }, { id: 3, chapter_id: 1, title: { en: 'Chapter 1 Quiz', th: 'แบบทดสอบบทที่ 1' }, content: null, type: 'QUIZ', duration_minutes: 10, sort_order: 3, is_sequential: true, prerequisite_lesson_ids: null, require_pass_quiz: true, is_published: true, created_at: '2024-01-15T00:00:00Z', updated_at: '2024-01-15T00:00:00Z', quiz: { id: 1, lesson_id: 3, title: { en: 'Chapter 1 Quiz', th: 'แบบทดสอบบทที่ 1' }, description: { en: 'Test your knowledge', th: 'ทดสอบความรู้' }, passing_score: 70, time_limit: 10, shuffle_questions: true, shuffle_choices: true, show_answers_after_completion: true } } ] }, { id: 2, course_id: 1, title: { en: 'Chapter 2: Functions', th: 'บทที่ 2: ฟังก์ชัน' }, description: { en: 'Learn about functions', th: 'เรียนรู้เกี่ยวกับฟังก์ชัน' }, sort_order: 2, is_published: true, created_at: '2024-01-15T00:00:00Z', updated_at: '2024-01-15T00:00:00Z', lessons: [ { id: 4, chapter_id: 2, title: { en: 'Creating Functions', th: 'การสร้างฟังก์ชัน' }, content: { en: 'How to create functions', th: 'วิธีสร้างฟังก์ชัน' }, type: 'VIDEO', duration_minutes: 25, sort_order: 1, is_sequential: true, prerequisite_lesson_ids: null, require_pass_quiz: false, is_published: true, created_at: '2024-01-15T00:00:00Z', updated_at: '2024-01-15T00:00:00Z', quiz: null } ] } ] };