This commit is contained in:
silverpro89
2026-01-20 20:29:07 +07:00
parent 97e2e8402e
commit 53d97ba5db
12 changed files with 3461 additions and 20 deletions

View File

@@ -0,0 +1,100 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
/**
* LessonComponentProgress Model
* Theo dõi tiến độ học viên cho từng component trong composite lesson
*/
const LessonComponentProgress = sequelize.define('lesson_component_progress', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
user_id: {
type: DataTypes.UUID,
allowNull: false,
comment: 'ID học viên'
},
lesson_id: {
type: DataTypes.UUID,
allowNull: false,
comment: 'ID bài học'
},
component_id: {
type: DataTypes.STRING(50),
allowNull: false,
comment: 'ID component từ content_json.components[].id'
},
component_type: {
type: DataTypes.STRING(50),
comment: 'Loại component: story_game, game, results_board, leaderboard'
},
// Progress tracking
status: {
type: DataTypes.ENUM('not_started', 'in_progress', 'completed'),
defaultValue: 'not_started',
comment: 'Trạng thái tiến độ'
},
started_at: {
type: DataTypes.DATE,
comment: 'Thời gian bắt đầu'
},
completed_at: {
type: DataTypes.DATE,
comment: 'Thời gian hoàn thành'
},
// Results (cho game/quiz components)
score: {
type: DataTypes.INTEGER,
comment: 'Điểm số đạt được'
},
max_score: {
type: DataTypes.INTEGER,
comment: 'Điểm tối đa'
},
accuracy: {
type: DataTypes.DECIMAL(5, 2),
comment: 'Độ chính xác (%)'
},
time_spent: {
type: DataTypes.INTEGER,
comment: 'Thời gian làm bài (giây)'
},
attempts: {
type: DataTypes.INTEGER,
defaultValue: 0,
comment: 'Số lần thử'
},
// Detailed results
results_data: {
type: DataTypes.JSON,
comment: 'Chi tiết kết quả: câu trả lời, achievements, etc.'
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
}
}, {
tableName: 'lesson_component_progress',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['user_id'] },
{ fields: ['lesson_id'] },
{ fields: ['status'] },
{ fields: ['user_id', 'lesson_id', 'component_id'], unique: true }
]
});
module.exports = LessonComponentProgress;

View File

@@ -0,0 +1,88 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
/**
* LessonLeaderboard Model
* Cache bảng xếp hạng cho lessons
*/
const LessonLeaderboard = sequelize.define('lesson_leaderboard', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
lesson_id: {
type: DataTypes.UUID,
allowNull: false,
comment: 'ID bài học'
},
user_id: {
type: DataTypes.UUID,
allowNull: false,
comment: 'ID học viên'
},
// Ranking data
total_score: {
type: DataTypes.INTEGER,
comment: 'Tổng điểm'
},
accuracy: {
type: DataTypes.DECIMAL(5, 2),
comment: 'Độ chính xác (%)'
},
completion_time: {
type: DataTypes.INTEGER,
comment: 'Thời gian hoàn thành (giây)'
},
rank: {
type: DataTypes.INTEGER,
comment: 'Thứ hạng'
},
// Scope filters
scope: {
type: DataTypes.ENUM('class', 'school', 'global'),
allowNull: false,
comment: 'Phạm vi xếp hạng'
},
scope_id: {
type: DataTypes.UUID,
comment: 'ID của class hoặc school (tùy scope)'
},
time_range: {
type: DataTypes.ENUM('day', 'week', 'month', 'all_time'),
allowNull: false,
comment: 'Khoảng thời gian'
},
// Metadata
computed_at: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
comment: 'Thời gian tính toán'
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updated_at: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
}
}, {
tableName: 'lesson_leaderboard',
timestamps: true,
underscored: true,
indexes: [
{ fields: ['lesson_id', 'scope', 'time_range'] },
{ fields: ['rank'] },
{ fields: ['user_id'] },
{ fields: ['lesson_id', 'user_id', 'scope', 'time_range'], unique: true }
]
});
module.exports = LessonLeaderboard;

View File

@@ -29,6 +29,8 @@ const Room = require('./Room');
const Chapter = require('./Chapter');
const Lesson = require('./Lesson');
const Game = require('./Game');
const LessonComponentProgress = require('./LessonComponentProgress');
const LessonLeaderboard = require('./LessonLeaderboard');
// Group 4: Attendance
const AttendanceLog = require('./AttendanceLog');
@@ -136,6 +138,16 @@ const setupRelationships = () => {
Chapter.hasMany(Lesson, { foreignKey: 'chapter_id', as: 'lessons' });
Lesson.belongsTo(Chapter, { foreignKey: 'chapter_id', as: 'chapter' });
// Lesson Progress relationships
LessonComponentProgress.belongsTo(UsersAuth, { foreignKey: 'user_id', as: 'user' });
LessonComponentProgress.belongsTo(Lesson, { foreignKey: 'lesson_id', as: 'lesson' });
Lesson.hasMany(LessonComponentProgress, { foreignKey: 'lesson_id', as: 'progress' });
// Leaderboard relationships
LessonLeaderboard.belongsTo(Lesson, { foreignKey: 'lesson_id', as: 'lesson' });
LessonLeaderboard.belongsTo(UsersAuth, { foreignKey: 'user_id', as: 'user' });
Lesson.hasMany(LessonLeaderboard, { foreignKey: 'lesson_id', as: 'leaderboard' });
// Attendance relationships
AttendanceLog.belongsTo(UsersAuth, { foreignKey: 'user_id', as: 'user' });
AttendanceLog.belongsTo(School, { foreignKey: 'school_id', as: 'school' });
@@ -243,6 +255,8 @@ module.exports = {
Chapter,
Lesson,
Game,
LessonComponentProgress,
LessonLeaderboard,
// Group 4: Attendance
AttendanceLog,