All checks were successful
Build and Deploy Frontend Management to Dev Server / Build Frontend Management Docker Image (push) Successful in 46s
Build and Deploy Frontend Management to Dev Server / Deploy E-learning Frontend Management to Dev Server (push) Successful in 6s
Build and Deploy Frontend Management to Dev Server / Notify Deployment Status (push) Successful in 1s
624 lines
17 KiB
TypeScript
624 lines
17 KiB
TypeScript
// API Response structure for user list
|
|
export interface AdminUserResponse {
|
|
id: number;
|
|
username: string;
|
|
email: string;
|
|
created_at: string;
|
|
updated_at: string;
|
|
role: {
|
|
code: string;
|
|
name: {
|
|
en: string;
|
|
th: string;
|
|
};
|
|
};
|
|
avatar_url?: string;
|
|
profile: {
|
|
prefix: {
|
|
en: string;
|
|
th: string;
|
|
};
|
|
first_name: string;
|
|
last_name: string;
|
|
avatar_url: string | null;
|
|
birth_date: string | null;
|
|
phone: string | null;
|
|
};
|
|
}
|
|
|
|
export interface ApiResponse<T> {
|
|
code: number;
|
|
message: string;
|
|
data: T;
|
|
}
|
|
|
|
export interface UsersListResponse {
|
|
code: number;
|
|
message: string;
|
|
data: AdminUserResponse[];
|
|
}
|
|
|
|
// Pending Course interfaces
|
|
export interface PendingCourseUser {
|
|
id: number;
|
|
email: string;
|
|
username: string;
|
|
}
|
|
|
|
export interface PendingCourseInstructor {
|
|
user_id: number;
|
|
is_primary: boolean;
|
|
user: PendingCourseUser;
|
|
}
|
|
|
|
export interface PendingCourseSubmission {
|
|
id: number;
|
|
submitted_by: number;
|
|
created_at: string;
|
|
submitter: PendingCourseUser;
|
|
}
|
|
|
|
export interface PendingCourse {
|
|
id: number;
|
|
title: {
|
|
th: string;
|
|
en: string;
|
|
};
|
|
slug: string;
|
|
description: {
|
|
th: string;
|
|
en: string;
|
|
};
|
|
thumbnail_url: string | null;
|
|
status: string;
|
|
created_at: string;
|
|
updated_at: string;
|
|
created_by: number;
|
|
creator: PendingCourseUser;
|
|
instructors: PendingCourseInstructor[];
|
|
chapters_count: number;
|
|
lessons_count: number;
|
|
latest_submission: PendingCourseSubmission | null;
|
|
}
|
|
|
|
export interface PendingCoursesListResponse {
|
|
code: number;
|
|
message: string;
|
|
data: PendingCourse[];
|
|
total: number;
|
|
}
|
|
|
|
// Course Detail interfaces for admin review
|
|
export interface CourseCategory {
|
|
id: number;
|
|
name: {
|
|
th: string;
|
|
en: string;
|
|
};
|
|
}
|
|
|
|
export interface CourseLesson {
|
|
id: number;
|
|
title: {
|
|
th: string;
|
|
en: string;
|
|
};
|
|
type: string;
|
|
sort_order: number;
|
|
is_published: boolean;
|
|
}
|
|
|
|
export interface CourseChapter {
|
|
id: number;
|
|
title: {
|
|
th: string;
|
|
en: string;
|
|
};
|
|
sort_order: number;
|
|
is_published: boolean;
|
|
lessons: CourseLesson[];
|
|
}
|
|
|
|
export interface ApprovalHistory {
|
|
id: number;
|
|
action: string;
|
|
comment: string | null;
|
|
created_at: string;
|
|
submitter: PendingCourseUser;
|
|
reviewer: PendingCourseUser | null;
|
|
}
|
|
|
|
export interface CourseDetailForReview {
|
|
id: number;
|
|
title: {
|
|
th: string;
|
|
en: string;
|
|
};
|
|
slug: string;
|
|
description: {
|
|
th: string;
|
|
en: string;
|
|
};
|
|
thumbnail_url: string | null;
|
|
price: number;
|
|
is_free: boolean;
|
|
have_certificate: boolean;
|
|
status: string;
|
|
created_at: string;
|
|
updated_at: string;
|
|
category: CourseCategory;
|
|
creator: PendingCourseUser;
|
|
instructors: PendingCourseInstructor[];
|
|
chapters: CourseChapter[];
|
|
approval_history: ApprovalHistory[];
|
|
}
|
|
|
|
export interface CourseDetailForReviewResponse {
|
|
code: number;
|
|
message: string;
|
|
data: CourseDetailForReview;
|
|
}
|
|
|
|
// Category interfaces
|
|
export interface CategoryResponse {
|
|
id: number;
|
|
name: {
|
|
en: string;
|
|
th: string;
|
|
};
|
|
slug: string;
|
|
description: {
|
|
en: string;
|
|
th: string;
|
|
};
|
|
icon: string;
|
|
sort_order: number;
|
|
is_active: boolean;
|
|
created_at: string;
|
|
created_by: number;
|
|
updated_at: string;
|
|
updated_by: number | null;
|
|
}
|
|
|
|
export interface CategoriesListResponse {
|
|
code: number;
|
|
message: string;
|
|
data: {
|
|
categories: CategoryResponse[];
|
|
};
|
|
}
|
|
|
|
export interface CreateCategoryRequest {
|
|
name: {
|
|
en: string;
|
|
th: string;
|
|
};
|
|
slug: string;
|
|
description: {
|
|
en: string;
|
|
th: string;
|
|
};
|
|
created_by?: number;
|
|
}
|
|
|
|
export interface UpdateCategoryRequest {
|
|
id: number;
|
|
name: {
|
|
en: string;
|
|
th: string;
|
|
};
|
|
slug: string;
|
|
description: {
|
|
en: string;
|
|
th: string;
|
|
};
|
|
}
|
|
|
|
// Audit Logs Interfaces
|
|
export interface AuditLog {
|
|
id: number;
|
|
user_id: number;
|
|
action: string;
|
|
entity_type: string;
|
|
entity_id: number;
|
|
old_value: string | null;
|
|
new_value: string | null;
|
|
ip_address: string | null;
|
|
user_agent: string | null;
|
|
metadata: string | null;
|
|
created_at: string;
|
|
user: {
|
|
email: string;
|
|
username: string;
|
|
id: number;
|
|
} | null;
|
|
}
|
|
|
|
export interface AuditLogsListResponse {
|
|
data: AuditLog[];
|
|
pagination: {
|
|
totalPages: number;
|
|
total: number;
|
|
limit: number;
|
|
page: number;
|
|
};
|
|
}
|
|
|
|
export interface AuditLogStats {
|
|
totalLogs: number;
|
|
todayLogs: number;
|
|
actionSummary: {
|
|
action: string;
|
|
count: number;
|
|
}[];
|
|
recentActivity: AuditLog[];
|
|
}
|
|
|
|
export interface RecommendedCourse {
|
|
id: number;
|
|
title: {
|
|
th: string;
|
|
en: string;
|
|
};
|
|
slug: string;
|
|
description: {
|
|
th: string;
|
|
en: string;
|
|
};
|
|
thumbnail_url: string | null;
|
|
price: number;
|
|
is_free: boolean;
|
|
have_certificate: boolean;
|
|
is_recommended: boolean;
|
|
status: string;
|
|
created_at: string;
|
|
updated_at: string;
|
|
category: {
|
|
id: number;
|
|
name: {
|
|
th: string;
|
|
en: string;
|
|
};
|
|
};
|
|
instructors: {
|
|
user_id: number;
|
|
is_primary: boolean;
|
|
user: {
|
|
id: number;
|
|
username: string;
|
|
email: string;
|
|
};
|
|
}[];
|
|
creator: {
|
|
id: number;
|
|
username: string;
|
|
email: string;
|
|
};
|
|
chapters_count: number;
|
|
lessons_count: number;
|
|
chapters?: {
|
|
id: number;
|
|
title: { th: string; en: string };
|
|
sort_order: number;
|
|
lessons: {
|
|
id: number;
|
|
title: { th: string; en: string };
|
|
}[];
|
|
}[];
|
|
}
|
|
|
|
export interface RecommendedCoursesListResponse {
|
|
code: number;
|
|
message: string;
|
|
data: RecommendedCourse[];
|
|
total: number;
|
|
}
|
|
|
|
// Helper function to get auth token from cookie
|
|
const getAuthToken = (): string => {
|
|
const tokenCookie = useCookie('token');
|
|
return tokenCookie.value || '';
|
|
};
|
|
|
|
export const adminService = {
|
|
async getUsers(): Promise<AdminUserResponse[]> {
|
|
const config = useRuntimeConfig();
|
|
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 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 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 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;
|
|
},
|
|
|
|
// ============ Audit Logs ============
|
|
async getAuditLogs(
|
|
page: number = 1,
|
|
limit: number = 20,
|
|
filters: {
|
|
userId?: number;
|
|
action?: string;
|
|
entityType?: string;
|
|
entityId?: number;
|
|
startDate?: string;
|
|
endDate?: string;
|
|
} = {}
|
|
): Promise<AuditLogsListResponse> {
|
|
const config = useRuntimeConfig();
|
|
const token = getAuthToken();
|
|
|
|
let query: any = { page, limit };
|
|
if (filters.userId) query.userId = filters.userId;
|
|
if (filters.action) query.action = filters.action;
|
|
if (filters.entityType) query.entityType = filters.entityType;
|
|
if (filters.entityId) query.entityId = filters.entityId;
|
|
if (filters.startDate) query.startDate = filters.startDate;
|
|
if (filters.endDate) query.endDate = filters.endDate;
|
|
|
|
const response = await $fetch<AuditLogsListResponse>('/api/admin/audit-logs', {
|
|
baseURL: config.public.apiBaseUrl as string,
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
query
|
|
});
|
|
|
|
return response;
|
|
},
|
|
|
|
async getAuditLogById(id: number): Promise<AuditLog> {
|
|
const config = useRuntimeConfig();
|
|
const token = getAuthToken();
|
|
const response = await $fetch<AuditLog>(`/api/admin/audit-logs/${id}`, {
|
|
baseURL: config.public.apiBaseUrl as string,
|
|
headers: { Authorization: `Bearer ${token}` }
|
|
});
|
|
return response;
|
|
},
|
|
|
|
async getAuditLogStats(): Promise<AuditLogStats> {
|
|
const config = useRuntimeConfig();
|
|
const token = getAuthToken();
|
|
const response = await $fetch<AuditLogStats>('/api/admin/audit-logs/stats/summary', {
|
|
baseURL: config.public.apiBaseUrl as string,
|
|
headers: { Authorization: `Bearer ${token}` }
|
|
});
|
|
return response;
|
|
},
|
|
|
|
async getAuditLogsByEntity(entityType: string, entityId: number): Promise<AuditLog[]> {
|
|
const config = useRuntimeConfig();
|
|
const token = getAuthToken();
|
|
const response = await $fetch<AuditLog[]>(`/api/admin/audit-logs/entity/${entityType}/${entityId}`, {
|
|
baseURL: config.public.apiBaseUrl as string,
|
|
headers: { Authorization: `Bearer ${token}` }
|
|
});
|
|
return response;
|
|
},
|
|
|
|
async getAuditLogsByUser(userId: number, limit: number = 20): Promise<AuditLog[]> {
|
|
const config = useRuntimeConfig();
|
|
const token = getAuthToken();
|
|
const response = await $fetch<AuditLog[]>(`/api/admin/audit-logs/user/${userId}/activity`, {
|
|
baseURL: config.public.apiBaseUrl as string,
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
query: { limit }
|
|
});
|
|
return response;
|
|
},
|
|
|
|
async cleanupAuditLogs(days: number = 90): Promise<ApiResponse<void>> {
|
|
const config = useRuntimeConfig();
|
|
const token = getAuthToken();
|
|
const response = await $fetch<ApiResponse<void>>('/api/admin/audit-logs/cleanup', {
|
|
method: 'DELETE',
|
|
baseURL: config.public.apiBaseUrl as string,
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
query: { days }
|
|
});
|
|
return response;
|
|
},
|
|
|
|
// ============ Recommended Courses ============
|
|
async getRecommendedCourses(): Promise<RecommendedCourse[]> {
|
|
const config = useRuntimeConfig();
|
|
const token = getAuthToken();
|
|
const response = await $fetch<RecommendedCoursesListResponse>('/api/admin/recommended-courses', {
|
|
baseURL: config.public.apiBaseUrl as string,
|
|
headers: {
|
|
Authorization: `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
return response.data;
|
|
},
|
|
|
|
async getRecommendedCourseById(id: number): Promise<RecommendedCourse> {
|
|
const config = useRuntimeConfig();
|
|
const token = getAuthToken();
|
|
const response = await $fetch<ApiResponse<RecommendedCourse>>(`/api/admin/recommended-courses/${id}`, {
|
|
baseURL: config.public.apiBaseUrl as string,
|
|
headers: {
|
|
Authorization: `Bearer ${token}`
|
|
}
|
|
});
|
|
|
|
return response.data;
|
|
},
|
|
|
|
async toggleCourseRecommendation(courseId: number, isRecommended: boolean): Promise<ApiResponse<void>> {
|
|
const config = useRuntimeConfig();
|
|
const token = getAuthToken();
|
|
const response = await $fetch<ApiResponse<void>>(`/api/admin/recommended-courses/${courseId}/toggle`, {
|
|
method: 'PUT',
|
|
baseURL: config.public.apiBaseUrl as string,
|
|
headers: {
|
|
Authorization: `Bearer ${token}`
|
|
},
|
|
query: { is_recommended: isRecommended }
|
|
});
|
|
|
|
return response;
|
|
}
|
|
};
|