feat: Implement student course management functionalities and standardize multi-language text types across course and category definitions.

This commit is contained in:
JakkrapartXD 2026-01-19 14:14:59 +07:00
parent d97569acbc
commit 4c9ad1cea7
6 changed files with 673 additions and 57 deletions

View file

@ -1,17 +1,12 @@
import { Course, Prisma, User } from '@prisma/client';
import { MultiLanguageText } from './index';
// Custom type for TSOA - avoiding complex Prisma types
export interface CreateCourseInput {
category_id?: number;
title: {
th: string;
en: string;
};
title: MultiLanguageText;
slug: string;
description: {
th: string;
en: string;
};
description: MultiLanguageText;
thumbnail_url?: string;
price?: number;
is_free?: boolean;
@ -48,15 +43,9 @@ export interface getmyCourse {
export interface UpdateCourseInput {
category_id?: number;
title?: {
th: string;
en: string;
};
title?: MultiLanguageText;
slug?: string;
description?: {
th: string;
en: string;
};
description?: MultiLanguageText;
thumbnail_url?: string;
price?: number;
is_free?: boolean;
@ -151,4 +140,3 @@ export interface sendCourseForReview {
token: string;
course_id: number;
}

View file

@ -0,0 +1,289 @@
import { Course, Lesson, Chapter, LessonAttachment, Quiz, LessonProgress, EnrollmentStatus } from '@prisma/client';
import { MultiLanguageText } from './index';
// Use MultiLanguageText from index.ts for consistency
export type MultiLangText = MultiLanguageText;
// ============================================
// Enrollment Types
// ============================================
export interface EnrollCourseInput {
token: string;
course_id: number;
}
export interface EnrollCourseResponse {
code: number;
message: string;
data?: {
enrollment_id: number;
course_id: number;
user_id: number;
status: EnrollmentStatus;
enrolled_at: Date;
};
}
export interface ListEnrolledCoursesInput {
token: string;
page?: number;
limit?: number;
status?: EnrollmentStatus;
}
export interface EnrolledCourseItem {
id: number;
course_id: number;
course: {
id: number;
title: MultiLangText;
slug: string;
thumbnail_url: string | null;
description: MultiLangText;
};
status: EnrollmentStatus;
progress_percentage: number;
enrolled_at: Date;
started_at: Date | null;
completed_at: Date | null;
last_accessed_at: Date | null;
}
export interface ListEnrolledCoursesResponse {
code: number;
message: string;
data: EnrolledCourseItem[];
total: number;
page: number;
limit: number;
}
// ============================================
// Course Learning Page Types
// ============================================
export interface GetCourseLearningInput {
token: string;
course_id: number;
}
export interface LessonWithLockStatus {
id: number;
chapter_id: number;
title: MultiLangText;
type: 'VIDEO' | 'QUIZ';
duration_minutes: number | null;
sort_order: number;
is_published: boolean;
is_sequential: boolean;
prerequisite_lesson_ids: number[] | null;
require_pass_quiz: boolean;
// Lock status
is_locked: boolean;
lock_reason?: string;
// Progress
is_completed: boolean;
video_progress_percentage?: number;
}
export interface ChapterWithLessons {
id: number;
title: MultiLangText;
description: MultiLangText | null;
sort_order: number;
is_published: boolean;
lessons: LessonWithLockStatus[];
}
export interface GetCourseLearningResponse {
code: number;
message: string;
data: {
course: {
id: number;
title: MultiLangText;
slug: string;
description: MultiLangText;
thumbnail_url: string | null;
have_certificate: boolean;
};
enrollment: {
status: EnrollmentStatus;
progress_percentage: number;
enrolled_at: Date;
started_at: Date | null;
completed_at: Date | null;
};
chapters: ChapterWithLessons[];
total_lessons: number;
completed_lessons: number;
};
}
// ============================================
// Lesson Content Types
// ============================================
export interface GetLessonContentInput {
token: string;
course_id: number;
lesson_id: number;
}
export interface LessonContentData {
id: number;
chapter_id: number;
title: MultiLangText;
content: MultiLangText | null;
type: 'VIDEO' | 'QUIZ';
duration_minutes: number | null;
is_sequential: boolean;
prerequisite_lesson_ids: number[] | null;
require_pass_quiz: boolean;
attachments: {
id: number;
file_name: string;
file_path: string;
file_size: number;
mime_type: string;
description: MultiLangText | null;
}[];
quiz?: {
id: number;
title: MultiLangText;
description: MultiLangText | null;
passing_score: number;
time_limit: number | null;
shuffle_questions: boolean;
shuffle_choices: boolean;
} | null;
// Navigation
prev_lesson_id: number | null;
next_lesson_id: number | null;
}
export interface GetLessonContentResponse {
code: number;
message: string;
data: LessonContentData;
progress?: {
is_completed: boolean;
video_progress_seconds: number;
video_duration_seconds: number | null;
video_progress_percentage: number | null;
last_watched_at: Date | null;
};
}
// ============================================
// Lesson Access Check Types
// ============================================
export interface CheckLessonAccessInput {
token: string;
course_id: number;
lesson_id: number;
}
export interface CheckLessonAccessResponse {
code: number;
message: string;
data: {
is_accessible: boolean;
is_enrolled: boolean;
is_locked: boolean;
lock_reason?: string;
required_lessons?: {
id: number;
title: MultiLangText;
is_completed: boolean;
}[];
required_quiz_pass?: {
lesson_id: number;
quiz_id: number;
title: MultiLangText;
is_passed: boolean;
};
};
}
// ============================================
// Video Progress Types
// ============================================
export interface SaveVideoProgressInput {
token: string;
lesson_id: number;
video_progress_seconds: number;
video_duration_seconds?: number;
}
export interface SaveVideoProgressResponse {
code: number;
message: string;
data?: {
lesson_id: number;
video_progress_seconds: number;
video_duration_seconds: number | null;
video_progress_percentage: number | null;
is_completed: boolean;
last_watched_at: Date;
};
}
export interface GetVideoProgressInput {
token: string;
lesson_id: number;
}
export interface GetVideoProgressResponse {
code: number;
message: string;
data: {
lesson_id: number;
video_progress_seconds: number;
video_duration_seconds: number | null;
video_progress_percentage: number | null;
is_completed: boolean;
completed_at: Date | null;
last_watched_at: Date | null;
} | null;
}
// ============================================
// Lesson Completion Types
// ============================================
export interface MarkLessonCompleteInput {
token: string;
course_id: number;
lesson_id: number;
}
export interface MarkLessonCompleteResponse {
code: number;
message: string;
data?: {
lesson_id: number;
is_completed: boolean;
completed_at: Date;
course_progress_percentage: number;
is_course_completed: boolean;
next_lesson_id: number | null;
certificate_issued?: boolean;
};
}
// ============================================
// Request Body Types (for TSOA)
// ============================================
export interface SaveVideoProgressBody {
video_progress_seconds: number;
video_duration_seconds?: number;
}
export interface EnrollCourseBody {
course_id: number;
}

View file

@ -1,14 +1,10 @@
import { MultiLanguageText } from './index';
export interface Category {
id: number;
name: {
th: string;
en: string;
};
name: MultiLanguageText;
slug: string;
description: {
th: string;
en: string;
};
description: MultiLanguageText;
icon: string | null;
sort_order: number;
is_active: boolean;
@ -26,15 +22,9 @@ export interface listCategoriesResponse {
}
export interface createCategory {
name: {
th: string;
en: string;
};
name: MultiLanguageText;
slug: string;
description: {
th: string;
en: string;
};
description: MultiLanguageText;
created_by: number;
}
@ -43,43 +33,25 @@ export interface createCategoryResponse {
message: string;
data: {
id: number;
name: {
en: string;
th: string;
};
name: MultiLanguageText;
slug: string;
description: {
en: string;
th: string;
};
description: MultiLanguageText;
created_by: number;
};
}
export interface updateCategory {
id: number;
name: {
th: string;
en: string;
};
name: MultiLanguageText;
slug: string;
description: {
th: string;
en: string;
};
description: MultiLanguageText;
}
export interface updateCategoryResponse {
id: number;
name: {
th: string;
en: string;
};
name: MultiLanguageText;
slug: string;
description: {
th: string;
en: string;
};
description: MultiLanguageText;
updated_by: number;
}

View file

@ -1,4 +1,5 @@
export interface MultiLanguageText {
[key: string]: string;
th: string;
en: string;
}