feat: del mock , crud add instructor , VerifyEmail
This commit is contained in:
parent
b2365a4c6a
commit
278bc17fa0
8 changed files with 345 additions and 1566 deletions
|
|
@ -29,8 +29,7 @@ export default defineNuxtConfig({
|
||||||
],
|
],
|
||||||
runtimeConfig: {
|
runtimeConfig: {
|
||||||
public: {
|
public: {
|
||||||
apiBaseUrl: process.env.NUXT_PUBLIC_API_BASE_URL,
|
apiBaseUrl: process.env.NUXT_PUBLIC_API_BASE_URL
|
||||||
useMockData: process.env.NUXT_PUBLIC_USE_MOCK_DATA === 'true'
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
devtools: { enabled: true },
|
devtools: { enabled: true },
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,7 @@
|
||||||
<q-tab-panel name="instructors" class="p-6">
|
<q-tab-panel name="instructors" class="p-6">
|
||||||
<div class="flex justify-between items-center mb-6">
|
<div class="flex justify-between items-center mb-6">
|
||||||
<h2 class="text-xl font-semibold text-gray-900">ผู้สอนในรายวิชา</h2>
|
<h2 class="text-xl font-semibold text-gray-900">ผู้สอนในรายวิชา</h2>
|
||||||
<q-btn color="primary" icon="person_add" label="เพิ่มผู้สอน" @click="showAddInstructorDialog = true" />
|
<q-btn v-if="isPrimaryInstructor" color="primary" icon="person_add" label="เพิ่มผู้สอน" @click="showAddInstructorDialog = true" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="loadingInstructors" class="flex justify-center py-10">
|
<div v-if="loadingInstructors" class="flex justify-center py-10">
|
||||||
|
|
@ -223,7 +223,7 @@
|
||||||
<q-item-label caption>{{ instructor.user.email }}</q-item-label>
|
<q-item-label caption>{{ instructor.user.email }}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
|
|
||||||
<q-item-section side>
|
<q-item-section side v-if="isPrimaryInstructor">
|
||||||
<q-btn flat dense round icon="more_vert">
|
<q-btn flat dense round icon="more_vert">
|
||||||
<q-menu>
|
<q-menu>
|
||||||
<q-item v-if="!instructor.is_primary" clickable v-close-popup @click="setPrimaryInstructor(instructor.user_id)">
|
<q-item v-if="!instructor.is_primary" clickable v-close-popup @click="setPrimaryInstructor(instructor.user_id)">
|
||||||
|
|
@ -469,14 +469,15 @@
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<q-select
|
<q-select
|
||||||
v-model="selectedUser"
|
v-model="selectedUser"
|
||||||
:options="filteredUsers"
|
:options="searchResults"
|
||||||
option-value="id"
|
option-value="id"
|
||||||
option-label="email"
|
option-label="email"
|
||||||
label="ค้นหาผู้ใช้ (Email หรือ Username)"
|
label="ค้นหาผู้สอน (Email หรือ Username)"
|
||||||
|
hint="พิมพ์อย่างน้อย 2 ตัวอักษรเพื่อค้นหา"
|
||||||
use-input
|
use-input
|
||||||
filled
|
filled
|
||||||
@filter="filterUsers"
|
@filter="filterUsers"
|
||||||
:loading="loadingUsers"
|
:loading="loadingSearch"
|
||||||
>
|
>
|
||||||
<template v-slot:option="scope">
|
<template v-slot:option="scope">
|
||||||
<q-item v-bind="scope.itemProps">
|
<q-item v-bind="scope.itemProps">
|
||||||
|
|
@ -521,9 +522,9 @@ import {
|
||||||
type ChapterResponse,
|
type ChapterResponse,
|
||||||
type AnnouncementResponse,
|
type AnnouncementResponse,
|
||||||
type CreateAnnouncementRequest,
|
type CreateAnnouncementRequest,
|
||||||
type CourseInstructorResponse
|
type CourseInstructorResponse,
|
||||||
|
type SearchInstructorResult
|
||||||
} from '~/services/instructor.service';
|
} from '~/services/instructor.service';
|
||||||
import { adminService, type AdminUserResponse } from '~/services/admin.service';
|
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'instructor',
|
layout: 'instructor',
|
||||||
|
|
@ -532,6 +533,7 @@ definePageMeta({
|
||||||
|
|
||||||
const $q = useQuasar();
|
const $q = useQuasar();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
const course = ref<CourseDetailResponse | null>(null);
|
const course = ref<CourseDetailResponse | null>(null);
|
||||||
|
|
@ -559,11 +561,11 @@ const announcementForm = ref<CreateAnnouncementRequest>({
|
||||||
const instructors = ref<CourseInstructorResponse[]>([]);
|
const instructors = ref<CourseInstructorResponse[]>([]);
|
||||||
const loadingInstructors = ref(false);
|
const loadingInstructors = ref(false);
|
||||||
const showAddInstructorDialog = ref(false);
|
const showAddInstructorDialog = ref(false);
|
||||||
const selectedUser = ref<AdminUserResponse | null>(null);
|
const selectedUser = ref<SearchInstructorResult | null>(null);
|
||||||
const users = ref<AdminUserResponse[]>([]);
|
const searchResults = ref<SearchInstructorResult[]>([]);
|
||||||
const filteredUsers = ref<AdminUserResponse[]>([]);
|
const loadingSearch = ref(false);
|
||||||
const loadingUsers = ref(false);
|
|
||||||
const addingInstructor = ref(false);
|
const addingInstructor = ref(false);
|
||||||
|
const searchQuery = ref('');
|
||||||
|
|
||||||
// Attachment handling
|
// Attachment handling
|
||||||
const fileInputRef = ref<HTMLInputElement | null>(null);
|
const fileInputRef = ref<HTMLInputElement | null>(null);
|
||||||
|
|
@ -582,6 +584,14 @@ const sortedChapters = computed(() => {
|
||||||
return course.value.chapters.slice().sort((a, b) => a.sort_order - b.sort_order);
|
return course.value.chapters.slice().sort((a, b) => a.sort_order - b.sort_order);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Check if current user is the primary instructor
|
||||||
|
const isPrimaryInstructor = computed(() => {
|
||||||
|
if (!authStore.user?.id) return false;
|
||||||
|
const currentUserId = parseInt(authStore.user.id);
|
||||||
|
const myInstructorRecord = instructors.value.find(i => i.user_id === currentUserId);
|
||||||
|
return myInstructorRecord?.is_primary === true;
|
||||||
|
});
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
const fetchCourse = async () => {
|
const fetchCourse = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|
@ -757,31 +767,40 @@ const fetchInstructors = async () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const filterUsers = async (val: string, update: (callback: () => void) => void) => {
|
let searchTimeout: NodeJS.Timeout | null = null;
|
||||||
if (users.value.length === 0) {
|
|
||||||
loadingUsers.value = true;
|
const filterUsers = (val: string, update: (callback: () => void) => void, abort: () => void) => {
|
||||||
try {
|
// Abort if query is too short
|
||||||
users.value = await adminService.getUsers();
|
if (val.length < 2) {
|
||||||
} catch (error) {
|
abort();
|
||||||
console.error('Failed to load users', error);
|
return;
|
||||||
} finally {
|
|
||||||
loadingUsers.value = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
update(() => {
|
// Clear previous timeout
|
||||||
const needle = val.toLowerCase();
|
if (searchTimeout) {
|
||||||
const existingInstructorIds = instructors.value.map(i => i.user_id);
|
clearTimeout(searchTimeout);
|
||||||
|
}
|
||||||
filteredUsers.value = users.value.filter(v => {
|
|
||||||
// Exclude existing instructors
|
// Debounce the search
|
||||||
if (existingInstructorIds.includes(v.id)) return false;
|
searchTimeout = setTimeout(async () => {
|
||||||
|
loadingSearch.value = true;
|
||||||
|
try {
|
||||||
|
const results = await instructorService.searchInstructors(val);
|
||||||
|
const existingInstructorIds = instructors.value.map(i => i.user_id);
|
||||||
|
|
||||||
// Filter by username or email
|
// Filter out existing instructors
|
||||||
return v.username.toLowerCase().indexOf(needle) > -1 ||
|
update(() => {
|
||||||
v.email.toLowerCase().indexOf(needle) > -1;
|
searchResults.value = results.filter(r => !existingInstructorIds.includes(r.id));
|
||||||
});
|
});
|
||||||
});
|
} catch (error) {
|
||||||
|
console.error('Failed to search instructors', error);
|
||||||
|
update(() => {
|
||||||
|
searchResults.value = [];
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
loadingSearch.value = false;
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
};
|
};
|
||||||
|
|
||||||
const addInstructor = async () => {
|
const addInstructor = async () => {
|
||||||
|
|
@ -790,7 +809,7 @@ const addInstructor = async () => {
|
||||||
addingInstructor.value = true;
|
addingInstructor.value = true;
|
||||||
try {
|
try {
|
||||||
const courseId = parseInt(route.params.id as string);
|
const courseId = parseInt(route.params.id as string);
|
||||||
const response = await instructorService.addInstructor(courseId, selectedUser.value.id);
|
const response = await instructorService.addInstructor(courseId, selectedUser.value.email);
|
||||||
|
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'positive',
|
type: 'positive',
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,11 @@
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="text-sm text-gray-600 mb-1">อีเมล</div>
|
<div class="text-sm text-gray-600 mb-1">อีเมล</div>
|
||||||
<div class="text-lg text-gray-900">{{ profile.email }}</div>
|
<div class="text-lg text-gray-900 flex items-center gap-2">
|
||||||
|
{{ profile.email }}
|
||||||
|
<q-badge v-if="profile.emailVerified" color="positive" label="ยืนยันแล้ว" />
|
||||||
|
<q-badge v-else color="warning" label="ยังไม่ยืนยัน" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -75,7 +79,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Action Buttons -->
|
<!-- Action Buttons -->
|
||||||
<div class="flex gap-3 mt-6">
|
<div class="flex flex-wrap gap-3 mt-6">
|
||||||
<q-btn
|
<q-btn
|
||||||
color="primary"
|
color="primary"
|
||||||
label="แก้ไขโปรไฟล์"
|
label="แก้ไขโปรไฟล์"
|
||||||
|
|
@ -89,6 +93,15 @@
|
||||||
icon="lock"
|
icon="lock"
|
||||||
@click="showPasswordModal = true"
|
@click="showPasswordModal = true"
|
||||||
/>
|
/>
|
||||||
|
<q-btn
|
||||||
|
v-if="!profile.emailVerified"
|
||||||
|
outline
|
||||||
|
color="orange"
|
||||||
|
label="ขอยืนยันอีเมล"
|
||||||
|
icon="mark_email_unread"
|
||||||
|
:loading="sendingVerification"
|
||||||
|
@click="handleSendVerificationEmail"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -289,6 +302,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
import { userService, type UserProfileResponse } from '~/services/user.service';
|
import { userService, type UserProfileResponse } from '~/services/user.service';
|
||||||
|
import { authService } from '~/services/auth.service';
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'instructor',
|
layout: 'instructor',
|
||||||
|
|
@ -305,11 +319,12 @@ const loading = ref(true);
|
||||||
const profile = ref({
|
const profile = ref({
|
||||||
fullName: '',
|
fullName: '',
|
||||||
email: '',
|
email: '',
|
||||||
|
emailVerified: false,
|
||||||
username: '',
|
username: '',
|
||||||
phone: '',
|
phone: '',
|
||||||
role: '',
|
role: '',
|
||||||
roleName: '',
|
roleName: '',
|
||||||
avatar: '👨🏫',
|
avatar: '',
|
||||||
avatarUrl: '' as string | null,
|
avatarUrl: '' as string | null,
|
||||||
createdAt: ''
|
createdAt: ''
|
||||||
});
|
});
|
||||||
|
|
@ -340,6 +355,9 @@ const passwordForm = ref({
|
||||||
confirmPassword: ''
|
confirmPassword: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Email verification
|
||||||
|
const sendingVerification = ref(false);
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
const getRoleLabel = (role: string) => {
|
const getRoleLabel = (role: string) => {
|
||||||
const labels: Record<string, string> = {
|
const labels: Record<string, string> = {
|
||||||
|
|
@ -495,6 +513,26 @@ const handleChangePassword = async () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSendVerificationEmail = async () => {
|
||||||
|
sendingVerification.value = true;
|
||||||
|
try {
|
||||||
|
const response = await authService.sendVerifyEmail();
|
||||||
|
$q.notify({
|
||||||
|
type: 'positive',
|
||||||
|
message: response.message || 'ส่งอีเมลยืนยันไปยังอีเมลของคุณแล้ว',
|
||||||
|
position: 'top'
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
$q.notify({
|
||||||
|
type: 'negative',
|
||||||
|
message: error.data?.message || 'เกิดข้อผิดพลาดในการส่งอีเมลยืนยัน',
|
||||||
|
position: 'top'
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
sendingVerification.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Watch edit modal
|
// Watch edit modal
|
||||||
watch(showEditModal, (newVal) => {
|
watch(showEditModal, (newVal) => {
|
||||||
if (newVal) {
|
if (newVal) {
|
||||||
|
|
@ -518,11 +556,12 @@ const fetchProfile = async () => {
|
||||||
profile.value = {
|
profile.value = {
|
||||||
fullName: `${data.profile.first_name} ${data.profile.last_name}`,
|
fullName: `${data.profile.first_name} ${data.profile.last_name}`,
|
||||||
email: data.email,
|
email: data.email,
|
||||||
|
emailVerified: !!data.email_verified_at,
|
||||||
username: data.username,
|
username: data.username,
|
||||||
phone: data.profile.phone || '',
|
phone: data.profile.phone || '',
|
||||||
role: data.role.code,
|
role: data.role.code,
|
||||||
roleName: data.role.name.th,
|
roleName: data.role.name.th,
|
||||||
avatar: '👨🏫',
|
avatar: '',
|
||||||
avatarUrl: data.profile.avatar_url,
|
avatarUrl: data.profile.avatar_url,
|
||||||
createdAt: data.created_at
|
createdAt: data.created_at
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
export default defineNuxtPlugin(() => {
|
|
||||||
const config = useRuntimeConfig();
|
|
||||||
|
|
||||||
if (!config.public.useMockData) {
|
|
||||||
return; // ใช้ API จริง
|
|
||||||
}
|
|
||||||
// Mock Data
|
|
||||||
const mockUsers = [
|
|
||||||
{ id: '1', email: 'instructor@test.com', fullName: 'อาจารย์ทดสอบ', role: 'INSTRUCTOR' },
|
|
||||||
{ id: '2', email: 'admin@test.com', fullName: 'ผู้ดูแลระบบ', role: 'ADMIN' }
|
|
||||||
];
|
|
||||||
const mockCourses = [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
title: 'Python เบื้องต้น',
|
|
||||||
description: 'เรียนรู้ Python จากพื้นฐาน',
|
|
||||||
instructorId: '1',
|
|
||||||
status: 'PUBLISHED',
|
|
||||||
thumbnail: '/images/python.jpg'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
title: 'JavaScript สำหรับเว็บ',
|
|
||||||
description: 'พัฒนาเว็บด้วย JavaScript',
|
|
||||||
instructorId: '1',
|
|
||||||
status: 'DRAFT',
|
|
||||||
thumbnail: '/images/javascript.jpg'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
// Mock API Functions
|
|
||||||
const mockApi = {
|
|
||||||
// Auth
|
|
||||||
login: async (email: string, password: string) => {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500)); // Simulate delay
|
|
||||||
const user = mockUsers.find(u => u.email === email);
|
|
||||||
if (user) {
|
|
||||||
return {
|
|
||||||
token: 'mock-jwt-token',
|
|
||||||
user
|
|
||||||
};
|
|
||||||
}
|
|
||||||
throw new Error('Invalid credentials');
|
|
||||||
},
|
|
||||||
// Courses
|
|
||||||
getCourses: async () => {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 300));
|
|
||||||
return mockCourses;
|
|
||||||
},
|
|
||||||
getCourse: async (id: string) => {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 300));
|
|
||||||
return mockCourses.find(c => c.id === id);
|
|
||||||
},
|
|
||||||
createCourse: async (data: any) => {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
const newCourse = {
|
|
||||||
id: String(mockCourses.length + 1),
|
|
||||||
...data,
|
|
||||||
status: 'DRAFT'
|
|
||||||
};
|
|
||||||
mockCourses.push(newCourse);
|
|
||||||
return newCourse;
|
|
||||||
},
|
|
||||||
// Users
|
|
||||||
getUsers: async () => {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 300));
|
|
||||||
return mockUsers;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
provide: {
|
|
||||||
mockApi
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
@ -158,468 +158,6 @@ export interface CourseDetailForReviewResponse {
|
||||||
data: CourseDetailForReview;
|
data: CourseDetailForReview;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock data for development
|
|
||||||
const MOCK_USERS: AdminUserResponse[] = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
username: 'admin',
|
|
||||||
email: 'admin@elearning.local',
|
|
||||||
created_at: '2024-01-01T00:00:00Z',
|
|
||||||
updated_at: '2024-01-01T00:00:00Z',
|
|
||||||
role: { code: 'ADMIN', name: { en: 'Administrator', th: 'ผู้ดูแลระบบ' } },
|
|
||||||
profile: {
|
|
||||||
prefix: { en: 'Mr.', th: 'นาย' },
|
|
||||||
first_name: 'Admin',
|
|
||||||
last_name: 'User',
|
|
||||||
avatar_url: null,
|
|
||||||
birth_date: null,
|
|
||||||
phone: '0812345678'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
username: 'instructor',
|
|
||||||
email: 'instructor@elearning.local',
|
|
||||||
created_at: '2024-01-01T00:00:00Z',
|
|
||||||
updated_at: '2024-01-01T00:00:00Z',
|
|
||||||
role: { code: 'INSTRUCTOR', name: { en: 'Instructor', th: 'ผู้สอน' } },
|
|
||||||
profile: {
|
|
||||||
prefix: { en: 'Mr.', th: 'นาย' },
|
|
||||||
first_name: 'John',
|
|
||||||
last_name: 'Instructor',
|
|
||||||
avatar_url: null,
|
|
||||||
birth_date: null,
|
|
||||||
phone: '0812345679'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
username: 'student',
|
|
||||||
email: 'student@elearning.local',
|
|
||||||
created_at: '2024-01-01T00:00:00Z',
|
|
||||||
updated_at: '2024-01-01T00:00:00Z',
|
|
||||||
role: { code: 'STUDENT', name: { en: 'Student', th: 'นักเรียน' } },
|
|
||||||
profile: {
|
|
||||||
prefix: { en: 'Ms.', th: 'นางสาว' },
|
|
||||||
first_name: 'Jane',
|
|
||||||
last_name: 'Student',
|
|
||||||
avatar_url: null,
|
|
||||||
birth_date: null,
|
|
||||||
phone: '0812345680'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// Mock pending courses data
|
|
||||||
const MOCK_PENDING_COURSES: PendingCourse[] = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
title: { th: 'พื้นฐาน JavaScript', en: 'JavaScript Fundamentals' },
|
|
||||||
slug: 'javascript-fundamentals',
|
|
||||||
description: { th: 'เรียนรู้ JavaScript ตั้งแต่เริ่มต้น', en: 'Learn JavaScript from scratch' },
|
|
||||||
thumbnail_url: null,
|
|
||||||
status: 'PENDING',
|
|
||||||
created_at: '2024-02-01T00:00:00Z',
|
|
||||||
updated_at: '2024-02-10T00:00:00Z',
|
|
||||||
created_by: 2,
|
|
||||||
creator: { id: 2, email: 'instructor@elearning.local', username: 'instructor' },
|
|
||||||
instructors: [
|
|
||||||
{
|
|
||||||
user_id: 2,
|
|
||||||
is_primary: true,
|
|
||||||
user: { id: 2, email: 'instructor@elearning.local', username: 'instructor' }
|
|
||||||
}
|
|
||||||
],
|
|
||||||
chapters_count: 5,
|
|
||||||
lessons_count: 15,
|
|
||||||
latest_submission: {
|
|
||||||
id: 1,
|
|
||||||
submitted_by: 2,
|
|
||||||
created_at: '2024-02-10T00:00:00Z',
|
|
||||||
submitter: { id: 2, email: 'instructor@elearning.local', username: 'instructor' }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
title: { th: 'React สำหรับผู้เริ่มต้น', en: 'React for Beginners' },
|
|
||||||
slug: 'react-for-beginners',
|
|
||||||
description: { th: 'เรียนรู้ React ตั้งแต่พื้นฐาน', en: 'Learn React from basics' },
|
|
||||||
thumbnail_url: null,
|
|
||||||
status: 'PENDING',
|
|
||||||
created_at: '2024-02-05T00:00:00Z',
|
|
||||||
updated_at: '2024-02-12T00:00:00Z',
|
|
||||||
created_by: 2,
|
|
||||||
creator: { id: 2, email: 'instructor@elearning.local', username: 'instructor' },
|
|
||||||
instructors: [
|
|
||||||
{
|
|
||||||
user_id: 2,
|
|
||||||
is_primary: true,
|
|
||||||
user: { id: 2, email: 'instructor@elearning.local', username: 'instructor' }
|
|
||||||
}
|
|
||||||
],
|
|
||||||
chapters_count: 8,
|
|
||||||
lessons_count: 24,
|
|
||||||
latest_submission: {
|
|
||||||
id: 2,
|
|
||||||
submitted_by: 2,
|
|
||||||
created_at: '2024-02-12T00:00:00Z',
|
|
||||||
submitter: { id: 2, email: 'instructor@elearning.local', username: 'instructor' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// Mock course detail for review
|
|
||||||
const MOCK_COURSE_DETAIL: CourseDetailForReview = {
|
|
||||||
id: 1,
|
|
||||||
title: { th: 'พื้นฐาน JavaScript', en: 'JavaScript Fundamentals' },
|
|
||||||
slug: 'javascript-fundamentals',
|
|
||||||
description: { th: 'เรียนรู้ JavaScript ตั้งแต่เริ่มต้น รวมถึง ES6+ features และ best practices', en: 'Learn JavaScript from scratch including ES6+ features and best practices' },
|
|
||||||
thumbnail_url: null,
|
|
||||||
price: 990,
|
|
||||||
is_free: false,
|
|
||||||
have_certificate: true,
|
|
||||||
status: 'PENDING',
|
|
||||||
created_at: '2024-02-01T00:00:00Z',
|
|
||||||
updated_at: '2024-02-10T00:00:00Z',
|
|
||||||
category: { id: 1, name: { th: 'การพัฒนาเว็บไซต์', en: 'Web Development' } },
|
|
||||||
creator: { id: 2, email: 'instructor@elearning.local', username: 'instructor' },
|
|
||||||
instructors: [
|
|
||||||
{
|
|
||||||
user_id: 2,
|
|
||||||
is_primary: true,
|
|
||||||
user: { id: 2, email: 'instructor@elearning.local', username: 'instructor' }
|
|
||||||
}
|
|
||||||
],
|
|
||||||
chapters: [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
title: { th: 'แนะนำ JavaScript', en: 'Introduction to JavaScript' },
|
|
||||||
sort_order: 1,
|
|
||||||
is_published: true,
|
|
||||||
lessons: [
|
|
||||||
{ id: 1, title: { th: 'JavaScript คืออะไร', en: 'What is JavaScript' }, type: 'VIDEO', sort_order: 1, is_published: true },
|
|
||||||
{ id: 2, title: { th: 'ติดตั้ง Development Environment', en: 'Setup Development Environment' }, type: 'VIDEO', sort_order: 2, is_published: true },
|
|
||||||
{ id: 3, title: { th: 'แบบทดสอบบทที่ 1', en: 'Chapter 1 Quiz' }, type: 'QUIZ', sort_order: 3, is_published: true }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
title: { th: 'Variables และ Data Types', en: 'Variables and Data Types' },
|
|
||||||
sort_order: 2,
|
|
||||||
is_published: true,
|
|
||||||
lessons: [
|
|
||||||
{ id: 4, title: { th: 'var, let และ const', en: 'var, let and const' }, type: 'VIDEO', sort_order: 1, is_published: true },
|
|
||||||
{ id: 5, title: { th: 'Primitive Data Types', en: 'Primitive Data Types' }, type: 'VIDEO', sort_order: 2, is_published: true }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
approval_history: [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
action: 'SUBMITTED',
|
|
||||||
comment: null,
|
|
||||||
created_at: '2024-02-10T10:00:00Z',
|
|
||||||
submitter: { id: 2, email: 'instructor@elearning.local', username: 'instructor' },
|
|
||||||
reviewer: null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
// Helper function to get auth token from cookie or localStorage
|
|
||||||
const getAuthToken = (): string => {
|
|
||||||
const tokenCookie = useCookie('token');
|
|
||||||
return tokenCookie.value || '';
|
|
||||||
};
|
|
||||||
|
|
||||||
export const adminService = {
|
|
||||||
async getUsers(): Promise<AdminUserResponse[]> {
|
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return MOCK_USERS;
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = getAuthToken();
|
|
||||||
const response = await $fetch<UsersListResponse>('/api/admin/usermanagement/users', {
|
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return response.data;
|
|
||||||
},
|
|
||||||
|
|
||||||
async getUserById(id: number): Promise<AdminUserResponse> {
|
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 300));
|
|
||||||
const user = MOCK_USERS.find(u => u.id === id);
|
|
||||||
if (!user) throw new Error('User not found');
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = getAuthToken();
|
|
||||||
const response = await $fetch<AdminUserResponse>(`/api/admin/usermanagement/users/${id}`, {
|
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
|
||||||
},
|
|
||||||
|
|
||||||
async updateUserRole(userId: number, roleId: number): Promise<ApiResponse<void>> {
|
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'User role updated successfully (Mock)',
|
|
||||||
data: undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = getAuthToken();
|
|
||||||
const response = await $fetch<ApiResponse<void>>(`/api/admin/usermanagement/role/${userId}`, {
|
|
||||||
method: 'PUT',
|
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`
|
|
||||||
},
|
|
||||||
body: {
|
|
||||||
id: userId,
|
|
||||||
role_id: roleId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
|
||||||
},
|
|
||||||
|
|
||||||
async deleteUser(userId: number): Promise<ApiResponse<void>> {
|
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'User deleted successfully (Mock)',
|
|
||||||
data: undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = getAuthToken();
|
|
||||||
const response = await $fetch<ApiResponse<void>>(`/api/admin/usermanagement/users/${userId}`, {
|
|
||||||
method: 'DELETE',
|
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
|
||||||
},
|
|
||||||
|
|
||||||
// ============ Pending Courses ============
|
|
||||||
async getPendingCourses(): Promise<PendingCourse[]> {
|
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return MOCK_PENDING_COURSES;
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = getAuthToken();
|
|
||||||
const response = await $fetch<PendingCoursesListResponse>('/api/admin/courses/pending', {
|
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return response.data;
|
|
||||||
},
|
|
||||||
|
|
||||||
async getCourseForReview(courseId: number): Promise<CourseDetailForReview> {
|
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return MOCK_COURSE_DETAIL;
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = getAuthToken();
|
|
||||||
const response = await $fetch<CourseDetailForReviewResponse>(`/api/admin/courses/${courseId}`, {
|
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return response.data;
|
|
||||||
},
|
|
||||||
|
|
||||||
async approveCourse(courseId: number, comment?: string): Promise<ApiResponse<void>> {
|
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Course approved successfully (Mock)',
|
|
||||||
data: undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = getAuthToken();
|
|
||||||
const response = await $fetch<ApiResponse<void>>(`/api/admin/courses/${courseId}/approve`, {
|
|
||||||
method: 'POST',
|
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`
|
|
||||||
},
|
|
||||||
body: { comment: comment || '' }
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
|
||||||
},
|
|
||||||
|
|
||||||
async rejectCourse(courseId: number, comment: string): Promise<ApiResponse<void>> {
|
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Course rejected successfully (Mock)',
|
|
||||||
data: undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = getAuthToken();
|
|
||||||
const response = await $fetch<ApiResponse<void>>(`/api/admin/courses/${courseId}/reject`, {
|
|
||||||
method: 'POST',
|
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`
|
|
||||||
},
|
|
||||||
body: { comment }
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
|
||||||
},
|
|
||||||
|
|
||||||
// ============ Categories ============
|
|
||||||
async getCategories(): Promise<CategoryResponse[]> {
|
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return MOCK_CATEGORIES;
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = getAuthToken();
|
|
||||||
const response = await $fetch<CategoriesListResponse>('/api/categories', {
|
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return response.data.categories;
|
|
||||||
},
|
|
||||||
|
|
||||||
async createCategory(data: CreateCategoryRequest): Promise<ApiResponse<CategoryResponse>> {
|
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Category created successfully (Mock)',
|
|
||||||
data: { ...MOCK_CATEGORIES[0], id: Date.now() }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = getAuthToken();
|
|
||||||
const response = await $fetch<ApiResponse<CategoryResponse>>('/api/admin/categories', {
|
|
||||||
method: 'POST',
|
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`
|
|
||||||
},
|
|
||||||
body: data
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
|
||||||
},
|
|
||||||
|
|
||||||
async updateCategory(id: number, data: UpdateCategoryRequest): Promise<ApiResponse<CategoryResponse>> {
|
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Category updated successfully (Mock)',
|
|
||||||
data: { ...MOCK_CATEGORIES[0], id }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = getAuthToken();
|
|
||||||
const response = await $fetch<ApiResponse<CategoryResponse>>(`/api/admin/categories/${id}`, {
|
|
||||||
method: 'PUT',
|
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`
|
|
||||||
},
|
|
||||||
body: data
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
|
||||||
},
|
|
||||||
|
|
||||||
async deleteCategory(id: number): Promise<ApiResponse<void>> {
|
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Category deleted successfully (Mock)',
|
|
||||||
data: undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = getAuthToken();
|
|
||||||
const response = await $fetch<ApiResponse<void>>(`/api/admin/categories/${id}`, {
|
|
||||||
method: 'DELETE',
|
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Category interfaces
|
// Category interfaces
|
||||||
export interface CategoryResponse {
|
export interface CategoryResponse {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -675,45 +213,183 @@ export interface UpdateCategoryRequest {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock categories
|
// Helper function to get auth token from cookie
|
||||||
const MOCK_CATEGORIES: CategoryResponse[] = [
|
const getAuthToken = (): string => {
|
||||||
{
|
const tokenCookie = useCookie('token');
|
||||||
id: 1,
|
return tokenCookie.value || '';
|
||||||
name: { en: 'Web Development', th: 'การพัฒนาเว็บไซต์' },
|
};
|
||||||
slug: 'web-development',
|
|
||||||
description: { en: 'Learn web development', th: 'หลักสูตรเกี่ยวกับการพัฒนาเว็บไซต์และเว็บแอปพลิเคชัน' },
|
export const adminService = {
|
||||||
icon: 'code',
|
async getUsers(): Promise<AdminUserResponse[]> {
|
||||||
sort_order: 1,
|
const config = useRuntimeConfig();
|
||||||
is_active: true,
|
const token = getAuthToken();
|
||||||
created_at: '2024-01-15T00:00:00Z',
|
const response = await $fetch<UsersListResponse>('/api/admin/usermanagement/users', {
|
||||||
created_by: 1,
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
updated_at: '2024-01-15T00:00:00Z',
|
headers: {
|
||||||
updated_by: null
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 2,
|
async getUserById(id: number): Promise<AdminUserResponse> {
|
||||||
name: { en: 'Mobile Development', th: 'การพัฒนาแอปพลิเคชันมือถือ' },
|
const config = useRuntimeConfig();
|
||||||
slug: 'mobile-development',
|
const token = getAuthToken();
|
||||||
description: { en: 'Learn mobile app development', th: 'หลักสูตรเกี่ยวกับการพัฒนาแอปพลิเคชันบนมือถือ' },
|
const response = await $fetch<AdminUserResponse>(`/api/admin/usermanagement/users/${id}`, {
|
||||||
icon: 'smartphone',
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
sort_order: 2,
|
headers: {
|
||||||
is_active: true,
|
Authorization: `Bearer ${token}`
|
||||||
created_at: '2024-01-20T00:00:00Z',
|
}
|
||||||
created_by: 1,
|
});
|
||||||
updated_at: '2024-01-20T00:00:00Z',
|
|
||||||
updated_by: null
|
return response;
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 3,
|
async updateUserRole(userId: number, roleId: number): Promise<ApiResponse<void>> {
|
||||||
name: { en: 'Database', th: 'ฐานข้อมูล' },
|
const config = useRuntimeConfig();
|
||||||
slug: 'database',
|
const token = getAuthToken();
|
||||||
description: { en: 'Learn database management', th: 'หลักสูตรเกี่ยวกับการออกแบบและจัดการฐานข้อมูล' },
|
const response = await $fetch<ApiResponse<void>>(`/api/admin/usermanagement/role/${userId}`, {
|
||||||
icon: 'storage',
|
method: 'PUT',
|
||||||
sort_order: 3,
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
is_active: true,
|
headers: {
|
||||||
created_at: '2024-02-01T00:00:00Z',
|
Authorization: `Bearer ${token}`
|
||||||
created_by: 1,
|
},
|
||||||
updated_at: '2024-02-01T00:00:00Z',
|
body: {
|
||||||
updated_by: null
|
id: userId,
|
||||||
|
role_id: roleId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteUser(userId: number): Promise<ApiResponse<void>> {
|
||||||
|
const config = useRuntimeConfig();
|
||||||
|
const token = getAuthToken();
|
||||||
|
const response = await $fetch<ApiResponse<void>>(`/api/admin/usermanagement/users/${userId}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
|
// ============ Pending Courses ============
|
||||||
|
async getPendingCourses(): Promise<PendingCourse[]> {
|
||||||
|
const config = useRuntimeConfig();
|
||||||
|
const token = getAuthToken();
|
||||||
|
const response = await $fetch<PendingCoursesListResponse>('/api/admin/courses/pending', {
|
||||||
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async getCourseForReview(courseId: number): Promise<CourseDetailForReview> {
|
||||||
|
const config = useRuntimeConfig();
|
||||||
|
const token = getAuthToken();
|
||||||
|
const response = await $fetch<CourseDetailForReviewResponse>(`/api/admin/courses/${courseId}`, {
|
||||||
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async approveCourse(courseId: number, comment?: string): Promise<ApiResponse<void>> {
|
||||||
|
const config = useRuntimeConfig();
|
||||||
|
const token = getAuthToken();
|
||||||
|
const response = await $fetch<ApiResponse<void>>(`/api/admin/courses/${courseId}/approve`, {
|
||||||
|
method: 'POST',
|
||||||
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
},
|
||||||
|
body: { comment: comment || '' }
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
|
async rejectCourse(courseId: number, comment: string): Promise<ApiResponse<void>> {
|
||||||
|
const config = useRuntimeConfig();
|
||||||
|
const token = getAuthToken();
|
||||||
|
const response = await $fetch<ApiResponse<void>>(`/api/admin/courses/${courseId}/reject`, {
|
||||||
|
method: 'POST',
|
||||||
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
},
|
||||||
|
body: { comment }
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
|
// ============ Categories ============
|
||||||
|
async getCategories(): Promise<CategoryResponse[]> {
|
||||||
|
const config = useRuntimeConfig();
|
||||||
|
const token = getAuthToken();
|
||||||
|
const response = await $fetch<CategoriesListResponse>('/api/categories', {
|
||||||
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data.categories;
|
||||||
|
},
|
||||||
|
|
||||||
|
async createCategory(data: CreateCategoryRequest): Promise<ApiResponse<CategoryResponse>> {
|
||||||
|
const config = useRuntimeConfig();
|
||||||
|
const token = getAuthToken();
|
||||||
|
const response = await $fetch<ApiResponse<CategoryResponse>>('/api/admin/categories', {
|
||||||
|
method: 'POST',
|
||||||
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
},
|
||||||
|
body: data
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateCategory(id: number, data: UpdateCategoryRequest): Promise<ApiResponse<CategoryResponse>> {
|
||||||
|
const config = useRuntimeConfig();
|
||||||
|
const token = getAuthToken();
|
||||||
|
const response = await $fetch<ApiResponse<CategoryResponse>>(`/api/admin/categories/${id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
},
|
||||||
|
body: data
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteCategory(id: number): Promise<ApiResponse<void>> {
|
||||||
|
const config = useRuntimeConfig();
|
||||||
|
const token = getAuthToken();
|
||||||
|
const response = await $fetch<ApiResponse<void>>(`/api/admin/categories/${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
];
|
};
|
||||||
|
|
|
||||||
|
|
@ -55,69 +55,12 @@ export interface ApiResponse<T> {
|
||||||
data: T;
|
data: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock data for development
|
|
||||||
const MOCK_USERS = [
|
|
||||||
{
|
|
||||||
email: 'admin@elearning.local',
|
|
||||||
password: 'password',
|
|
||||||
user: {
|
|
||||||
id: '1',
|
|
||||||
email: 'admin@elearning.local',
|
|
||||||
firstName: 'ผู้ดูแล',
|
|
||||||
lastName: 'ระบบ',
|
|
||||||
role: 'ADMIN'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
email: 'instructor@elearning.local',
|
|
||||||
password: 'password',
|
|
||||||
user: {
|
|
||||||
id: '2',
|
|
||||||
email: 'instructor@elearning.local',
|
|
||||||
firstName: 'อาจารย์',
|
|
||||||
lastName: 'ทดสอบ',
|
|
||||||
role: 'INSTRUCTOR'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
export const authService = {
|
export const authService = {
|
||||||
async login(email: string, password: string): Promise<LoginResponse> {
|
async login(email: string, password: string): Promise<LoginResponse> {
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
// Use Mock Data
|
|
||||||
if (useMockData) {
|
|
||||||
return this.mockLogin(email, password);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use Real API
|
|
||||||
return this.apiLogin(email, password);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Mock login for development
|
|
||||||
async mockLogin(email: string, password: string): Promise<LoginResponse> {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500)); // Simulate delay
|
|
||||||
|
|
||||||
const mockUser = MOCK_USERS.find(u => u.email === email);
|
|
||||||
|
|
||||||
if (!mockUser || mockUser.password !== password) {
|
|
||||||
throw new Error('อีเมลหรือรหัสผ่านไม่ถูกต้อง');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
token: 'mock-jwt-token',
|
|
||||||
refreshToken: 'mock-refresh-token',
|
|
||||||
user: mockUser.user
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
// Real API login
|
|
||||||
async apiLogin(email: string, password: string): Promise<LoginResponse> {
|
|
||||||
const config = useRuntimeConfig();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await $fetch<ApiLoginResponse>('/api/auth/login', {
|
const response = await $fetch<ApiResponse<ApiLoginResponse>>('/api/auth/login', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
body: {
|
body: {
|
||||||
|
|
@ -126,24 +69,26 @@ export const authService = {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const loginData = response.data;
|
||||||
|
|
||||||
// Check if user role is STUDENT - block login
|
// Check if user role is STUDENT - block login
|
||||||
if (response.user.role.code === 'STUDENT') {
|
if (loginData.user.role.code === 'STUDENT') {
|
||||||
throw new Error('ไม่สามารถเข้าสู่ระบบได้ ระบบนี้สำหรับผู้สอนและผู้ดูแลระบบเท่านั้น');
|
throw new Error('ไม่สามารถเข้าสู่ระบบได้ ระบบนี้สำหรับผู้สอนและผู้ดูแลระบบเท่านั้น');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform API response to frontend format
|
// Transform API response to frontend format
|
||||||
return {
|
return {
|
||||||
token: response.token,
|
token: loginData.token,
|
||||||
refreshToken: response.refreshToken,
|
refreshToken: loginData.refreshToken,
|
||||||
user: {
|
user: {
|
||||||
id: response.user.id.toString(),
|
id: loginData.user.id.toString(),
|
||||||
email: response.user.email,
|
email: loginData.user.email,
|
||||||
firstName: response.user.profile.first_name,
|
firstName: loginData.user.profile.first_name,
|
||||||
lastName: response.user.profile.last_name,
|
lastName: loginData.user.profile.last_name,
|
||||||
role: response.user.role.code,
|
role: loginData.user.role.code,
|
||||||
avatarUrl: response.user.profile.avatar_url
|
avatarUrl: loginData.user.profile.avatar_url
|
||||||
},
|
},
|
||||||
message: 'เข้าสู่ระบบสำเร็จ' // Note: Backend usually returns message too, but we can default it or use backend's
|
message: response.message || 'เข้าสู่ระบบสำเร็จ'
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// Re-throw custom errors (like STUDENT role block)
|
// Re-throw custom errors (like STUDENT role block)
|
||||||
|
|
@ -179,19 +124,7 @@ export const authService = {
|
||||||
|
|
||||||
async forgotPassword(email: string): Promise<ApiResponse<void>> {
|
async forgotPassword(email: string): Promise<ApiResponse<void>> {
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
// Mock: simulate sending email
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'ส่งลิงก์รีเซ็ตรหัสผ่านไปยังอีเมลของคุณแล้ว (Mock)',
|
|
||||||
data: undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Real API
|
|
||||||
const response = await $fetch<ApiResponse<void>>('/api/auth/reset-request', {
|
const response = await $fetch<ApiResponse<void>>('/api/auth/reset-request', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
|
|
@ -202,15 +135,7 @@ export const authService = {
|
||||||
|
|
||||||
async resetPassword(token: string, password: string): Promise<void> {
|
async resetPassword(token: string, password: string): Promise<void> {
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
// Mock: simulate reset password
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Real API
|
|
||||||
await $fetch('/api/auth/reset-password', {
|
await $fetch('/api/auth/reset-password', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
|
|
@ -220,15 +145,7 @@ export const authService = {
|
||||||
|
|
||||||
async registerInstructor(data: RegisterInstructorRequest): Promise<void> {
|
async registerInstructor(data: RegisterInstructorRequest): Promise<void> {
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
// Mock: simulate registration
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Real API
|
|
||||||
await $fetch('/api/auth/register-instructor', {
|
await $fetch('/api/auth/register-instructor', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
|
|
@ -238,28 +155,46 @@ export const authService = {
|
||||||
|
|
||||||
async refreshToken(currentRefreshToken: string): Promise<{ token: string; refreshToken: string }> {
|
async refreshToken(currentRefreshToken: string): Promise<{ token: string; refreshToken: string }> {
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
// Mock: return new tokens
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
token: 'mock-new-jwt-token-' + Date.now(),
|
|
||||||
refreshToken: 'mock-new-refresh-token-' + Date.now()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!currentRefreshToken) {
|
if (!currentRefreshToken) {
|
||||||
throw new Error('No refresh token available');
|
throw new Error('No refresh token available');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Real API
|
|
||||||
const response = await $fetch<{ token: string; refreshToken: string }>('/api/auth/refresh', {
|
const response = await $fetch<{ token: string; refreshToken: string }>('/api/auth/refresh', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
body: { refreshToken: currentRefreshToken }
|
body: { refreshToken: currentRefreshToken }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
|
async sendVerifyEmail(): Promise<ApiResponse<void>> {
|
||||||
|
const config = useRuntimeConfig();
|
||||||
|
const token = useCookie('token').value;
|
||||||
|
|
||||||
|
const response = await $fetch<ApiResponse<void>>('/api/user/send-verify-email', {
|
||||||
|
method: 'POST',
|
||||||
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
|
||||||
|
async verifyEmail(verificationToken: string): Promise<ApiResponse<void>> {
|
||||||
|
const config = useRuntimeConfig();
|
||||||
|
const token = useCookie('token').value;
|
||||||
|
|
||||||
|
const response = await $fetch<ApiResponse<void>>('/api/user/verify-email', {
|
||||||
|
method: 'POST',
|
||||||
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
},
|
||||||
|
body: { token: verificationToken }
|
||||||
|
});
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,23 @@ export interface InstructorsListResponse {
|
||||||
data: CourseInstructorResponse[];
|
data: CourseInstructorResponse[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SearchInstructorResult {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
|
profile: {
|
||||||
|
first_name: string;
|
||||||
|
last_name: string;
|
||||||
|
avatar_url: string | null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SearchInstructorsResponse {
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
data: SearchInstructorResult[];
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function to get auth token from cookie
|
// Helper function to get auth token from cookie
|
||||||
const getAuthToken = (): string => {
|
const getAuthToken = (): string => {
|
||||||
const tokenCookie = useCookie('token');
|
const tokenCookie = useCookie('token');
|
||||||
|
|
@ -108,113 +125,14 @@ const authRequest = async <T>(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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 = {
|
export const instructorService = {
|
||||||
async getCourses(): Promise<CourseResponse[]> {
|
async getCourses(): Promise<CourseResponse[]> {
|
||||||
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<CoursesListResponse>('/api/instructors/courses');
|
const response = await authRequest<CoursesListResponse>('/api/instructors/courses');
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
async createCourse(data: CreateCourseRequest): Promise<ApiResponse<CourseResponse>> {
|
async createCourse(data: CreateCourseRequest): Promise<ApiResponse<CourseResponse>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 201,
|
|
||||||
message: 'Course created successfully (Mock)',
|
|
||||||
data: {
|
|
||||||
...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
|
// Clean data - remove empty thumbnail_url
|
||||||
|
|
||||||
const cleanedData = { ...data };
|
const cleanedData = { ...data };
|
||||||
if (!cleanedData.thumbnail_url) {
|
if (!cleanedData.thumbnail_url) {
|
||||||
delete cleanedData.thumbnail_url;
|
delete cleanedData.thumbnail_url;
|
||||||
|
|
@ -230,36 +148,11 @@ export const instructorService = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async getCourseById(courseId: number): Promise<CourseDetailResponse> {
|
async getCourseById(courseId: number): Promise<CourseDetailResponse> {
|
||||||
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}`);
|
const response = await authRequest<{ code: number; data: CourseDetailResponse }>(`/api/instructors/courses/${courseId}`);
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateCourse(courseId: number, data: CreateCourseRequest): Promise<ApiResponse<CourseResponse>> {
|
async updateCourse(courseId: number, data: CreateCourseRequest): Promise<ApiResponse<CourseResponse>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Course updated successfully (Mock)',
|
|
||||||
data: {
|
|
||||||
...MOCK_COURSES[0],
|
|
||||||
id: courseId,
|
|
||||||
...data,
|
|
||||||
price: String(data.price)
|
|
||||||
} as CourseResponse
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return await authRequest<ApiResponse<CourseResponse>>(`/api/instructors/courses/${courseId}`, {
|
return await authRequest<ApiResponse<CourseResponse>>(`/api/instructors/courses/${courseId}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: { data }
|
body: { data }
|
||||||
|
|
@ -267,18 +160,6 @@ export const instructorService = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async uploadCourseThumbnail(courseId: number, file: File): Promise<ApiResponse<{ thumbnail_url: string }>> {
|
async uploadCourseThumbnail(courseId: number, file: File): Promise<ApiResponse<{ thumbnail_url: string }>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Thumbnail uploaded successfully (Mock)',
|
|
||||||
data: { thumbnail_url: URL.createObjectURL(file) }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
|
|
||||||
|
|
@ -289,70 +170,20 @@ export const instructorService = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async getCourseInstructors(courseId: number): Promise<CourseInstructorResponse[]> {
|
async getCourseInstructors(courseId: number): Promise<CourseInstructorResponse[]> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
course_id: courseId,
|
|
||||||
user_id: 1,
|
|
||||||
is_primary: true,
|
|
||||||
joined_at: new Date().toISOString(),
|
|
||||||
user: {
|
|
||||||
id: 1,
|
|
||||||
username: 'instructor',
|
|
||||||
email: 'instructor@elearning.local',
|
|
||||||
role_id: 2,
|
|
||||||
email_verified_at: new Date().toISOString(),
|
|
||||||
is_deactivated: false,
|
|
||||||
created_at: new Date().toISOString(),
|
|
||||||
updated_at: new Date().toISOString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await authRequest<InstructorsListResponse>(
|
const response = await authRequest<InstructorsListResponse>(
|
||||||
`/api/instructors/courses/listinstructor/${courseId}`
|
`/api/instructors/courses/listinstructor/${courseId}`
|
||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
async addInstructor(courseId: number, userId: number): Promise<ApiResponse<void>> {
|
async addInstructor(courseId: number, emailOrUsername: string): Promise<ApiResponse<void>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Instructor added successfully (Mock)',
|
|
||||||
data: undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return await authRequest<ApiResponse<void>>(
|
return await authRequest<ApiResponse<void>>(
|
||||||
`/api/instructors/courses/add-instructor/${courseId}/${userId}`,
|
`/api/instructors/courses/add-instructor/${courseId}/${encodeURIComponent(emailOrUsername)}`,
|
||||||
{ method: 'POST' }
|
{ method: 'POST' }
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
async removeInstructor(courseId: number, userId: number): Promise<ApiResponse<void>> {
|
async removeInstructor(courseId: number, userId: number): Promise<ApiResponse<void>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Instructor removed successfully (Mock)',
|
|
||||||
data: undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return await authRequest<ApiResponse<void>>(
|
return await authRequest<ApiResponse<void>>(
|
||||||
`/api/instructors/courses/remove-instructor/${courseId}/${userId}`,
|
`/api/instructors/courses/remove-instructor/${courseId}/${userId}`,
|
||||||
{ method: 'DELETE' }
|
{ method: 'DELETE' }
|
||||||
|
|
@ -360,65 +191,28 @@ export const instructorService = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async setPrimaryInstructor(courseId: number, userId: number): Promise<ApiResponse<void>> {
|
async setPrimaryInstructor(courseId: number, userId: number): Promise<ApiResponse<void>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Primary instructor updated successfully (Mock)',
|
|
||||||
data: undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return await authRequest<ApiResponse<void>>(
|
return await authRequest<ApiResponse<void>>(
|
||||||
`/api/instructors/courses/set-primary-instructor/${courseId}/${userId}`,
|
`/api/instructors/courses/set-primary-instructor/${courseId}/${userId}`,
|
||||||
{ method: 'PUT' }
|
{ method: 'PUT' }
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async searchInstructors(query: string): Promise<SearchInstructorResult[]> {
|
||||||
|
const response = await authRequest<SearchInstructorsResponse>(
|
||||||
|
`/api/instructors/courses/search-instructors?query=${encodeURIComponent(query)}`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
async deleteCourse(courseId: number): Promise<ApiResponse<void>> {
|
async deleteCourse(courseId: number): Promise<ApiResponse<void>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Course deleted successfully (Mock)',
|
|
||||||
data: undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return await authRequest<ApiResponse<void>>(`/api/instructors/courses/${courseId}`, { method: 'DELETE' });
|
return await authRequest<ApiResponse<void>>(`/api/instructors/courses/${courseId}`, { method: 'DELETE' });
|
||||||
},
|
},
|
||||||
|
|
||||||
async sendForReview(courseId: number): Promise<ApiResponse<void>> {
|
async sendForReview(courseId: number): Promise<ApiResponse<void>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Sent for review successfully (Mock)',
|
|
||||||
data: undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return await authRequest<ApiResponse<void>>(`/api/instructors/courses/send-review/${courseId}`, { method: 'POST' });
|
return await authRequest<ApiResponse<void>>(`/api/instructors/courses/send-review/${courseId}`, { method: 'POST' });
|
||||||
},
|
},
|
||||||
|
|
||||||
async getChapters(courseId: number): Promise<ChapterResponse[]> {
|
async getChapters(courseId: number): Promise<ChapterResponse[]> {
|
||||||
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
|
// Get chapters from course detail endpoint
|
||||||
const response = await authRequest<{ code: number; data: { chapters: ChapterResponse[] } }>(
|
const response = await authRequest<{ code: number; data: { chapters: ChapterResponse[] } }>(
|
||||||
`/api/instructors/courses/${courseId}`
|
`/api/instructors/courses/${courseId}`
|
||||||
|
|
@ -427,23 +221,6 @@ export const instructorService = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async createChapter(courseId: number, data: CreateChapterRequest): Promise<ApiResponse<ChapterResponse>> {
|
async createChapter(courseId: number, data: CreateChapterRequest): Promise<ApiResponse<ChapterResponse>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 201,
|
|
||||||
message: 'Chapter created successfully (Mock)',
|
|
||||||
data: {
|
|
||||||
...MOCK_COURSE_DETAIL.chapters[0],
|
|
||||||
id: Date.now(),
|
|
||||||
...data,
|
|
||||||
lessons: []
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return await authRequest<ApiResponse<ChapterResponse>>(
|
return await authRequest<ApiResponse<ChapterResponse>>(
|
||||||
`/api/instructors/courses/${courseId}/chapters`,
|
`/api/instructors/courses/${courseId}/chapters`,
|
||||||
{ method: 'POST', body: data }
|
{ method: 'POST', body: data }
|
||||||
|
|
@ -451,22 +228,6 @@ export const instructorService = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateChapter(courseId: number, chapterId: number, data: CreateChapterRequest): Promise<ApiResponse<ChapterResponse>> {
|
async updateChapter(courseId: number, chapterId: number, data: CreateChapterRequest): Promise<ApiResponse<ChapterResponse>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Chapter updated successfully (Mock)',
|
|
||||||
data: {
|
|
||||||
...MOCK_COURSE_DETAIL.chapters[0],
|
|
||||||
id: chapterId,
|
|
||||||
...data
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return await authRequest<ApiResponse<ChapterResponse>>(
|
return await authRequest<ApiResponse<ChapterResponse>>(
|
||||||
`/api/instructors/courses/${courseId}/chapters/${chapterId}`,
|
`/api/instructors/courses/${courseId}/chapters/${chapterId}`,
|
||||||
{ method: 'PUT', body: data }
|
{ method: 'PUT', body: data }
|
||||||
|
|
@ -474,18 +235,6 @@ export const instructorService = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async deleteChapter(courseId: number, chapterId: number): Promise<ApiResponse<void>> {
|
async deleteChapter(courseId: number, chapterId: number): Promise<ApiResponse<void>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Chapter deleted successfully (Mock)',
|
|
||||||
data: undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return await authRequest<ApiResponse<void>>(
|
return await authRequest<ApiResponse<void>>(
|
||||||
`/api/instructors/courses/${courseId}/chapters/${chapterId}`,
|
`/api/instructors/courses/${courseId}/chapters/${chapterId}`,
|
||||||
{ method: 'DELETE' }
|
{ method: 'DELETE' }
|
||||||
|
|
@ -493,18 +242,6 @@ export const instructorService = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async reorderChapter(courseId: number, chapterId: number, sortOrder: number): Promise<ApiResponse<void>> {
|
async reorderChapter(courseId: number, chapterId: number, sortOrder: number): Promise<ApiResponse<void>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 300));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Chapter reordered successfully (Mock)',
|
|
||||||
data: undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return await authRequest<ApiResponse<void>>(
|
return await authRequest<ApiResponse<void>>(
|
||||||
`/api/instructors/courses/${courseId}/chapters/${chapterId}/reorder`,
|
`/api/instructors/courses/${courseId}/chapters/${chapterId}/reorder`,
|
||||||
{ method: 'PUT', body: { sort_order: sortOrder } }
|
{ method: 'PUT', body: { sort_order: sortOrder } }
|
||||||
|
|
@ -513,14 +250,6 @@ export const instructorService = {
|
||||||
|
|
||||||
// Lesson CRUD
|
// Lesson CRUD
|
||||||
async getLesson(courseId: number, chapterId: number, lessonId: number): Promise<LessonDetailResponse> {
|
async getLesson(courseId: number, chapterId: number, lessonId: number): Promise<LessonDetailResponse> {
|
||||||
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 }>(
|
const response = await authRequest<{ code: number; data: LessonDetailResponse }>(
|
||||||
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}`
|
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}`
|
||||||
);
|
);
|
||||||
|
|
@ -528,22 +257,6 @@ export const instructorService = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async createLesson(courseId: number, chapterId: number, data: CreateLessonRequest): Promise<ApiResponse<LessonResponse>> {
|
async createLesson(courseId: number, chapterId: number, data: CreateLessonRequest): Promise<ApiResponse<LessonResponse>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 201,
|
|
||||||
message: 'Lesson created successfully (Mock)',
|
|
||||||
data: {
|
|
||||||
...MOCK_COURSE_DETAIL.chapters[0].lessons[0],
|
|
||||||
id: Date.now(),
|
|
||||||
...data
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return await authRequest<ApiResponse<LessonResponse>>(
|
return await authRequest<ApiResponse<LessonResponse>>(
|
||||||
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons`,
|
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons`,
|
||||||
{ method: 'POST', body: data }
|
{ method: 'POST', body: data }
|
||||||
|
|
@ -551,22 +264,6 @@ export const instructorService = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateLesson(courseId: number, chapterId: number, lessonId: number, data: UpdateLessonRequest): Promise<ApiResponse<LessonResponse>> {
|
async updateLesson(courseId: number, chapterId: number, lessonId: number, data: UpdateLessonRequest): Promise<ApiResponse<LessonResponse>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Lesson updated successfully (Mock)',
|
|
||||||
data: {
|
|
||||||
...MOCK_COURSE_DETAIL.chapters[0].lessons[0],
|
|
||||||
id: lessonId,
|
|
||||||
...data
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return await authRequest<ApiResponse<LessonResponse>>(
|
return await authRequest<ApiResponse<LessonResponse>>(
|
||||||
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}`,
|
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}`,
|
||||||
{ method: 'PUT', body: data }
|
{ method: 'PUT', body: data }
|
||||||
|
|
@ -574,18 +271,6 @@ export const instructorService = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async deleteLesson(courseId: number, chapterId: number, lessonId: number): Promise<ApiResponse<void>> {
|
async deleteLesson(courseId: number, chapterId: number, lessonId: number): Promise<ApiResponse<void>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Lesson deleted successfully (Mock)',
|
|
||||||
data: undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return await authRequest<ApiResponse<void>>(
|
return await authRequest<ApiResponse<void>>(
|
||||||
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}`,
|
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}`,
|
||||||
{ method: 'DELETE' }
|
{ method: 'DELETE' }
|
||||||
|
|
@ -593,18 +278,6 @@ export const instructorService = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async reorderLesson(courseId: number, chapterId: number, lessonId: number, sortOrder: number): Promise<ApiResponse<void>> {
|
async reorderLesson(courseId: number, chapterId: number, lessonId: number, sortOrder: number): Promise<ApiResponse<void>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 300));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Lesson reordered successfully (Mock)',
|
|
||||||
data: undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return await authRequest<ApiResponse<void>>(
|
return await authRequest<ApiResponse<void>>(
|
||||||
`/api/instructors/courses/${courseId}/chapters/${chapterId}/reorder-lessons`,
|
`/api/instructors/courses/${courseId}/chapters/${chapterId}/reorder-lessons`,
|
||||||
{ method: 'PUT', body: { lesson_id: lessonId, sort_order: sortOrder } }
|
{ method: 'PUT', body: { lesson_id: lessonId, sort_order: sortOrder } }
|
||||||
|
|
@ -613,35 +286,6 @@ export const instructorService = {
|
||||||
|
|
||||||
// Question CRUD
|
// Question CRUD
|
||||||
async createQuestion(courseId: number, chapterId: number, lessonId: number, data: CreateQuestionRequest): Promise<ApiResponse<QuizQuestionResponse>> {
|
async createQuestion(courseId: number, chapterId: number, lessonId: number, data: CreateQuestionRequest): Promise<ApiResponse<QuizQuestionResponse>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 201,
|
|
||||||
message: 'Question created successfully (Mock)',
|
|
||||||
data: {
|
|
||||||
id: Date.now(),
|
|
||||||
quiz_id: 1,
|
|
||||||
question: data.question,
|
|
||||||
explanation: data.explanation || null,
|
|
||||||
question_type: data.question_type,
|
|
||||||
score: data.score || 1,
|
|
||||||
sort_order: data.sort_order || 1,
|
|
||||||
created_at: new Date().toISOString(),
|
|
||||||
updated_at: new Date().toISOString(),
|
|
||||||
choices: data.choices.map((c, i) => ({
|
|
||||||
id: Date.now() + i,
|
|
||||||
question_id: Date.now(),
|
|
||||||
text: c.text,
|
|
||||||
is_correct: c.is_correct,
|
|
||||||
sort_order: c.sort_order || i + 1
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return await authRequest<ApiResponse<QuizQuestionResponse>>(
|
return await authRequest<ApiResponse<QuizQuestionResponse>>(
|
||||||
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/questions`,
|
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/questions`,
|
||||||
{ method: 'POST', body: data }
|
{ method: 'POST', body: data }
|
||||||
|
|
@ -649,35 +293,6 @@ export const instructorService = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateQuestion(courseId: number, chapterId: number, lessonId: number, questionId: number, data: CreateQuestionRequest): Promise<ApiResponse<QuizQuestionResponse>> {
|
async updateQuestion(courseId: number, chapterId: number, lessonId: number, questionId: number, data: CreateQuestionRequest): Promise<ApiResponse<QuizQuestionResponse>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Question updated successfully (Mock)',
|
|
||||||
data: {
|
|
||||||
id: questionId,
|
|
||||||
quiz_id: 1,
|
|
||||||
question: data.question,
|
|
||||||
explanation: data.explanation || null,
|
|
||||||
question_type: data.question_type,
|
|
||||||
score: data.score || 1,
|
|
||||||
sort_order: data.sort_order || 1,
|
|
||||||
created_at: new Date().toISOString(),
|
|
||||||
updated_at: new Date().toISOString(),
|
|
||||||
choices: data.choices.map((c, i) => ({
|
|
||||||
id: Date.now() + i,
|
|
||||||
question_id: questionId,
|
|
||||||
text: c.text,
|
|
||||||
is_correct: c.is_correct,
|
|
||||||
sort_order: c.sort_order || i + 1
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return await authRequest<ApiResponse<QuizQuestionResponse>>(
|
return await authRequest<ApiResponse<QuizQuestionResponse>>(
|
||||||
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/questions/${questionId}`,
|
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/questions/${questionId}`,
|
||||||
{ method: 'PUT', body: data }
|
{ method: 'PUT', body: data }
|
||||||
|
|
@ -685,18 +300,6 @@ export const instructorService = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async deleteQuestion(courseId: number, chapterId: number, lessonId: number, questionId: number): Promise<ApiResponse<void>> {
|
async deleteQuestion(courseId: number, chapterId: number, lessonId: number, questionId: number): Promise<ApiResponse<void>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Question deleted successfully (Mock)',
|
|
||||||
data: undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return await authRequest<ApiResponse<void>>(
|
return await authRequest<ApiResponse<void>>(
|
||||||
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/questions/${questionId}`,
|
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/questions/${questionId}`,
|
||||||
{ method: 'DELETE' }
|
{ method: 'DELETE' }
|
||||||
|
|
@ -704,18 +307,6 @@ export const instructorService = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async reorderQuestion(courseId: number, chapterId: number, lessonId: number, questionId: number, sortOrder: number): Promise<ApiResponse<void>> {
|
async reorderQuestion(courseId: number, chapterId: number, lessonId: number, questionId: number, sortOrder: number): Promise<ApiResponse<void>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 300));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Question reordered successfully (Mock)',
|
|
||||||
data: undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return await authRequest<ApiResponse<void>>(
|
return await authRequest<ApiResponse<void>>(
|
||||||
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/questions/${questionId}/reorder`,
|
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/questions/${questionId}/reorder`,
|
||||||
{ method: 'PUT', body: { sort_order: sortOrder } }
|
{ method: 'PUT', body: { sort_order: sortOrder } }
|
||||||
|
|
@ -724,28 +315,6 @@ export const instructorService = {
|
||||||
|
|
||||||
// Quiz Settings
|
// Quiz Settings
|
||||||
async updateQuizSettings(courseId: number, chapterId: number, lessonId: number, data: UpdateQuizSettingsRequest): Promise<ApiResponse<QuizResponse>> {
|
async updateQuizSettings(courseId: number, chapterId: number, lessonId: number, data: UpdateQuizSettingsRequest): Promise<ApiResponse<QuizResponse>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Quiz settings updated successfully (Mock)',
|
|
||||||
data: {
|
|
||||||
id: 1,
|
|
||||||
lesson_id: lessonId,
|
|
||||||
title: data.title,
|
|
||||||
description: data.description,
|
|
||||||
passing_score: data.passing_score,
|
|
||||||
time_limit: data.time_limit,
|
|
||||||
shuffle_questions: data.shuffle_questions,
|
|
||||||
shuffle_choices: data.shuffle_choices,
|
|
||||||
show_answers_after_completion: data.show_answers_after_completion
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return await authRequest<ApiResponse<QuizResponse>>(
|
return await authRequest<ApiResponse<QuizResponse>>(
|
||||||
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/quiz`,
|
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/quiz`,
|
||||||
{ method: 'PUT', body: data }
|
{ method: 'PUT', body: data }
|
||||||
|
|
@ -754,18 +323,6 @@ export const instructorService = {
|
||||||
|
|
||||||
// Video Upload
|
// Video Upload
|
||||||
async uploadLessonVideo(courseId: number, chapterId: number, lessonId: number, video: File, attachments?: File[]): Promise<ApiResponse<LessonResponse>> {
|
async uploadLessonVideo(courseId: number, chapterId: number, lessonId: number, video: File, attachments?: File[]): Promise<ApiResponse<LessonResponse>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Video uploaded successfully (Mock)',
|
|
||||||
data: MOCK_COURSE_DETAIL.chapters[0].lessons[0] as LessonResponse
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('video', video);
|
formData.append('video', video);
|
||||||
if (attachments) {
|
if (attachments) {
|
||||||
|
|
@ -781,18 +338,6 @@ export const instructorService = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateLessonVideo(courseId: number, chapterId: number, lessonId: number, video?: File, attachments?: File[]): Promise<ApiResponse<LessonResponse>> {
|
async updateLessonVideo(courseId: number, chapterId: number, lessonId: number, video?: File, attachments?: File[]): Promise<ApiResponse<LessonResponse>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Video updated successfully (Mock)',
|
|
||||||
data: MOCK_COURSE_DETAIL.chapters[0].lessons[0] as LessonResponse
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
if (video) {
|
if (video) {
|
||||||
formData.append('video', video);
|
formData.append('video', video);
|
||||||
|
|
@ -811,18 +356,6 @@ export const instructorService = {
|
||||||
|
|
||||||
// Attachments
|
// Attachments
|
||||||
async addAttachments(courseId: number, chapterId: number, lessonId: number, files: File[]): Promise<ApiResponse<LessonResponse>> {
|
async addAttachments(courseId: number, chapterId: number, lessonId: number, files: File[]): Promise<ApiResponse<LessonResponse>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Attachments added successfully (Mock)',
|
|
||||||
data: MOCK_COURSE_DETAIL.chapters[0].lessons[0] as LessonResponse
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
files.forEach(file => {
|
files.forEach(file => {
|
||||||
formData.append('attachment', file);
|
formData.append('attachment', file);
|
||||||
|
|
@ -835,18 +368,6 @@ export const instructorService = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async deleteAttachment(courseId: number, chapterId: number, lessonId: number, attachmentId: number): Promise<ApiResponse<void>> {
|
async deleteAttachment(courseId: number, chapterId: number, lessonId: number, attachmentId: number): Promise<ApiResponse<void>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Attachment deleted successfully (Mock)',
|
|
||||||
data: undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return await authRequest<ApiResponse<void>>(
|
return await authRequest<ApiResponse<void>>(
|
||||||
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/attachments/${attachmentId}`,
|
`/api/instructors/courses/${courseId}/chapters/${chapterId}/lessons/${lessonId}/attachments/${attachmentId}`,
|
||||||
{ method: 'DELETE' }
|
{ method: 'DELETE' }
|
||||||
|
|
@ -855,14 +376,6 @@ export const instructorService = {
|
||||||
|
|
||||||
// Announcements
|
// Announcements
|
||||||
async getAnnouncements(courseId: number): Promise<AnnouncementResponse[]> {
|
async getAnnouncements(courseId: number): Promise<AnnouncementResponse[]> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return MOCK_ANNOUNCEMENTS;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await authRequest<AnnouncementsListResponse>(
|
const response = await authRequest<AnnouncementsListResponse>(
|
||||||
`/api/instructors/courses/${courseId}/announcements`
|
`/api/instructors/courses/${courseId}/announcements`
|
||||||
);
|
);
|
||||||
|
|
@ -870,27 +383,6 @@ export const instructorService = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async createAnnouncement(courseId: number, data: CreateAnnouncementRequest, files?: File[]): Promise<ApiResponse<AnnouncementResponse>> {
|
async createAnnouncement(courseId: number, data: CreateAnnouncementRequest, files?: File[]): Promise<ApiResponse<AnnouncementResponse>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 201,
|
|
||||||
message: 'Announcement created successfully (Mock)',
|
|
||||||
data: {
|
|
||||||
id: Date.now(),
|
|
||||||
title: data.title,
|
|
||||||
content: data.content,
|
|
||||||
status: data.status || 'DRAFT',
|
|
||||||
is_pinned: data.is_pinned || false,
|
|
||||||
created_at: new Date().toISOString(),
|
|
||||||
updated_at: new Date().toISOString(),
|
|
||||||
attachments: []
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('data', JSON.stringify(data));
|
formData.append('data', JSON.stringify(data));
|
||||||
|
|
||||||
|
|
@ -907,27 +399,6 @@ export const instructorService = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateAnnouncement(courseId: number, announcementId: number, data: CreateAnnouncementRequest): Promise<ApiResponse<AnnouncementResponse>> {
|
async updateAnnouncement(courseId: number, announcementId: number, data: CreateAnnouncementRequest): Promise<ApiResponse<AnnouncementResponse>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Announcement updated successfully (Mock)',
|
|
||||||
data: {
|
|
||||||
id: announcementId,
|
|
||||||
title: data.title,
|
|
||||||
content: data.content,
|
|
||||||
status: data.status || 'DRAFT',
|
|
||||||
is_pinned: data.is_pinned || false,
|
|
||||||
created_at: new Date().toISOString(),
|
|
||||||
updated_at: new Date().toISOString(),
|
|
||||||
attachments: []
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return await authRequest<ApiResponse<AnnouncementResponse>>(
|
return await authRequest<ApiResponse<AnnouncementResponse>>(
|
||||||
`/api/instructors/courses/${courseId}/announcements/${announcementId}`,
|
`/api/instructors/courses/${courseId}/announcements/${announcementId}`,
|
||||||
{ method: 'PUT', body: data }
|
{ method: 'PUT', body: data }
|
||||||
|
|
@ -935,18 +406,6 @@ export const instructorService = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async deleteAnnouncement(courseId: number, announcementId: number): Promise<ApiResponse<void>> {
|
async deleteAnnouncement(courseId: number, announcementId: number): Promise<ApiResponse<void>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Announcement deleted successfully (Mock)',
|
|
||||||
data: undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return await authRequest<ApiResponse<void>>(
|
return await authRequest<ApiResponse<void>>(
|
||||||
`/api/instructors/courses/${courseId}/announcements/${announcementId}`,
|
`/api/instructors/courses/${courseId}/announcements/${announcementId}`,
|
||||||
{ method: 'DELETE' }
|
{ method: 'DELETE' }
|
||||||
|
|
@ -954,18 +413,6 @@ export const instructorService = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async uploadAnnouncementAttachment(courseId: number, announcementId: number, file: File): Promise<ApiResponse<AnnouncementResponse>> {
|
async uploadAnnouncementAttachment(courseId: number, announcementId: number, file: File): Promise<ApiResponse<AnnouncementResponse>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Upload attachment success (Mock)',
|
|
||||||
data: MOCK_ANNOUNCEMENTS[0]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
|
|
||||||
|
|
@ -976,18 +423,6 @@ export const instructorService = {
|
||||||
},
|
},
|
||||||
|
|
||||||
async deleteAnnouncementAttachment(courseId: number, announcementId: number, attachmentId: number): Promise<ApiResponse<void>> {
|
async deleteAnnouncementAttachment(courseId: number, announcementId: number, attachmentId: number): Promise<ApiResponse<void>> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Delete attachment success (Mock)',
|
|
||||||
data: undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return await authRequest<ApiResponse<void>>(
|
return await authRequest<ApiResponse<void>>(
|
||||||
`/api/instructors/courses/${courseId}/announcements/${announcementId}/attachments/${attachmentId}`,
|
`/api/instructors/courses/${courseId}/announcements/${announcementId}/attachments/${attachmentId}`,
|
||||||
{ method: 'DELETE' }
|
{ method: 'DELETE' }
|
||||||
|
|
@ -1192,146 +627,3 @@ export interface CreateAnnouncementRequest {
|
||||||
status?: 'DRAFT' | 'PUBLISHED';
|
status?: 'DRAFT' | 'PUBLISHED';
|
||||||
is_pinned?: boolean;
|
is_pinned?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock announcements
|
|
||||||
const MOCK_ANNOUNCEMENTS: AnnouncementResponse[] = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
title: { th: 'ยินดีต้อนรับสู่คอร์ส', en: 'Welcome to the Course' },
|
|
||||||
content: {
|
|
||||||
th: 'ยินดีต้อนรับทุกคนสู่คอร์ส JavaScript Fundamentals! เราจะเริ่มเรียนในสัปดาห์หน้า',
|
|
||||||
en: 'Welcome everyone to JavaScript Fundamentals! We will start next week'
|
|
||||||
},
|
|
||||||
status: 'PUBLISHED',
|
|
||||||
is_pinned: true,
|
|
||||||
created_at: '2024-01-15T10:00:00Z',
|
|
||||||
updated_at: '2024-01-15T10:00:00Z',
|
|
||||||
attachments: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
title: { th: 'อัพเดทตารางเรียน', en: 'Schedule Update' },
|
|
||||||
content: {
|
|
||||||
th: 'มีการเปลี่ยนแปลงตารางเรียนสำหรับบทที่ 3',
|
|
||||||
en: 'There is a schedule change for Chapter 3'
|
|
||||||
},
|
|
||||||
status: 'PUBLISHED',
|
|
||||||
is_pinned: false,
|
|
||||||
created_at: '2024-01-20T14:00:00Z',
|
|
||||||
updated_at: '2024-01-20T14:00:00Z',
|
|
||||||
attachments: []
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// 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',
|
|
||||||
video_url: null,
|
|
||||||
attachments: [],
|
|
||||||
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',
|
|
||||||
video_url: null,
|
|
||||||
attachments: [],
|
|
||||||
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',
|
|
||||||
video_url: null,
|
|
||||||
attachments: [],
|
|
||||||
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',
|
|
||||||
video_url: null,
|
|
||||||
attachments: [],
|
|
||||||
quiz: null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ export interface UserProfileResponse {
|
||||||
id: number;
|
id: number;
|
||||||
username: string;
|
username: string;
|
||||||
email: string;
|
email: string;
|
||||||
|
email_verified_at: string | null;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
role: {
|
role: {
|
||||||
|
|
@ -44,48 +45,6 @@ export interface UpdateProfileRequest {
|
||||||
birth_date?: string | null;
|
birth_date?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock profile data for development
|
|
||||||
const MOCK_PROFILES: Record<string, UserProfileResponse> = {
|
|
||||||
'admin@elearning.local': {
|
|
||||||
id: 1,
|
|
||||||
username: 'admin',
|
|
||||||
email: 'admin@elearning.local',
|
|
||||||
updated_at: '2024-01-01T00:00:00Z',
|
|
||||||
created_at: '2024-01-01T00:00:00Z',
|
|
||||||
role: {
|
|
||||||
code: 'ADMIN',
|
|
||||||
name: { en: 'Administrator', th: 'ผู้ดูแลระบบ' }
|
|
||||||
},
|
|
||||||
profile: {
|
|
||||||
prefix: { en: 'Mr.', th: 'นาย' },
|
|
||||||
first_name: 'ผู้ดูแล',
|
|
||||||
last_name: 'ระบบ',
|
|
||||||
avatar_url: null,
|
|
||||||
birth_date: null,
|
|
||||||
phone: '081-234-5678'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'instructor@elearning.local': {
|
|
||||||
id: 2,
|
|
||||||
username: 'instructor',
|
|
||||||
email: 'instructor@elearning.local',
|
|
||||||
updated_at: '2024-01-01T00:00:00Z',
|
|
||||||
created_at: '2024-01-01T00:00:00Z',
|
|
||||||
role: {
|
|
||||||
code: 'INSTRUCTOR',
|
|
||||||
name: { en: 'Instructor', th: 'ผู้สอน' }
|
|
||||||
},
|
|
||||||
profile: {
|
|
||||||
prefix: { en: 'Mr.', th: 'นาย' },
|
|
||||||
first_name: 'อาจารย์',
|
|
||||||
last_name: 'ทดสอบ',
|
|
||||||
avatar_url: null,
|
|
||||||
birth_date: null,
|
|
||||||
phone: '081-234-5678'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Helper function to get auth token from cookie
|
// Helper function to get auth token from cookie
|
||||||
const getAuthToken = (): string => {
|
const getAuthToken = (): string => {
|
||||||
const tokenCookie = useCookie('token');
|
const tokenCookie = useCookie('token');
|
||||||
|
|
@ -94,37 +53,6 @@ const getAuthToken = (): string => {
|
||||||
|
|
||||||
export const userService = {
|
export const userService = {
|
||||||
async getProfile(): Promise<UserProfileResponse> {
|
async getProfile(): Promise<UserProfileResponse> {
|
||||||
const config = useRuntimeConfig();
|
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
return this.mockGetProfile();
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.apiGetProfile();
|
|
||||||
},
|
|
||||||
|
|
||||||
// Mock get profile
|
|
||||||
async mockGetProfile(): Promise<UserProfileResponse> {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 300));
|
|
||||||
|
|
||||||
const userCookie = useCookie('user');
|
|
||||||
if (!userCookie.value) throw new Error('User not found');
|
|
||||||
|
|
||||||
const user = typeof userCookie.value === 'string'
|
|
||||||
? JSON.parse(userCookie.value)
|
|
||||||
: userCookie.value;
|
|
||||||
const mockProfile = MOCK_PROFILES[user.email];
|
|
||||||
|
|
||||||
if (!mockProfile) {
|
|
||||||
throw new Error('Profile not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
return mockProfile;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Real API get profile
|
|
||||||
async apiGetProfile(): Promise<UserProfileResponse> {
|
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const token = getAuthToken();
|
const token = getAuthToken();
|
||||||
|
|
||||||
|
|
@ -140,20 +68,8 @@ export const userService = {
|
||||||
|
|
||||||
async updateProfile(data: UpdateProfileRequest): Promise<ApiResponse<UserProfileResponse>> {
|
async updateProfile(data: UpdateProfileRequest): Promise<ApiResponse<UserProfileResponse>> {
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
// In mock mode, just return the current profile with updates
|
|
||||||
const profile = await this.mockGetProfile();
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Update profile successfully',
|
|
||||||
data: { ...profile, profile: { ...profile.profile, ...data } }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = getAuthToken();
|
const token = getAuthToken();
|
||||||
|
|
||||||
const response = await $fetch<ApiResponse<UserProfileResponse>>('/api/user/me', {
|
const response = await $fetch<ApiResponse<UserProfileResponse>>('/api/user/me', {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
|
|
@ -168,19 +84,8 @@ export const userService = {
|
||||||
|
|
||||||
async changePassword(oldPassword: string, newPassword: string): Promise<ApiResponse<null>> {
|
async changePassword(oldPassword: string, newPassword: string): Promise<ApiResponse<null>> {
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
// In mock mode, just simulate success
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Change password successfully',
|
|
||||||
data: null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = getAuthToken();
|
const token = getAuthToken();
|
||||||
|
|
||||||
const response = await $fetch<ApiResponse<null>>('/api/user/change-password', {
|
const response = await $fetch<ApiResponse<null>>('/api/user/change-password', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
baseURL: config.public.apiBaseUrl as string,
|
baseURL: config.public.apiBaseUrl as string,
|
||||||
|
|
@ -198,18 +103,6 @@ export const userService = {
|
||||||
|
|
||||||
async uploadAvatar(file: File): Promise<ApiResponse<{ avatar_url: string; id: number }>> {
|
async uploadAvatar(file: File): Promise<ApiResponse<{ avatar_url: string; id: number }>> {
|
||||||
const config = useRuntimeConfig();
|
const config = useRuntimeConfig();
|
||||||
const useMockData = config.public.useMockData as boolean;
|
|
||||||
|
|
||||||
if (useMockData) {
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
// Return mock URL
|
|
||||||
return {
|
|
||||||
code: 200,
|
|
||||||
message: 'Upload avatar successfully',
|
|
||||||
data: { avatar_url: URL.createObjectURL(file), id: 1 }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = getAuthToken();
|
const token = getAuthToken();
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue