init
This commit is contained in:
216
serve/internal/models/learning.go
Normal file
216
serve/internal/models/learning.go
Normal file
@@ -0,0 +1,216 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ListeningMaterial 听力材料模型
|
||||
type ListeningMaterial struct {
|
||||
ID string `json:"id" gorm:"type:varchar(36);primaryKey;comment:材料ID"`
|
||||
Title string `json:"title" gorm:"type:varchar(200);not null;comment:标题"`
|
||||
Description *string `json:"description" gorm:"type:text;comment:描述"`
|
||||
AudioURL string `json:"audio_url" gorm:"type:varchar(500);not null;comment:音频URL"`
|
||||
Transcript *string `json:"transcript" gorm:"type:longtext;comment:音频文本"`
|
||||
Duration int `json:"duration" gorm:"type:int;comment:时长(秒)"`
|
||||
Level string `json:"level" gorm:"type:enum('beginner','intermediate','advanced');not null;comment:难度级别"`
|
||||
Category string `json:"category" gorm:"type:varchar(50);comment:分类"`
|
||||
Tags *string `json:"tags" gorm:"type:json;comment:标签(JSON数组)"`
|
||||
IsActive bool `json:"is_active" gorm:"type:boolean;default:true;comment:是否启用"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;comment:创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;comment:更新时间"`
|
||||
DeletedAt gorm.DeletedAt `json:"-" gorm:"index;comment:删除时间"`
|
||||
|
||||
// 关联关系
|
||||
ListeningRecords []ListeningRecord `json:"listening_records,omitempty" gorm:"foreignKey:MaterialID"`
|
||||
}
|
||||
|
||||
// ListeningRecord 听力练习记录模型
|
||||
type ListeningRecord struct {
|
||||
ID string `json:"id" gorm:"type:varchar(36);primaryKey;comment:记录ID"`
|
||||
UserID string `json:"user_id" gorm:"type:varchar(36);not null;index;comment:用户ID"`
|
||||
MaterialID string `json:"material_id" gorm:"type:varchar(36);not null;index;comment:材料ID"`
|
||||
Score *float64 `json:"score" gorm:"type:decimal(5,2);comment:得分"`
|
||||
Accuracy *float64 `json:"accuracy" gorm:"type:decimal(5,2);comment:准确率"`
|
||||
CompletionRate *float64 `json:"completion_rate" gorm:"type:decimal(5,2);comment:完成率"`
|
||||
TimeSpent int `json:"time_spent" gorm:"type:int;comment:用时(秒)"`
|
||||
Answers *string `json:"answers" gorm:"type:json;comment:答案(JSON对象)"`
|
||||
Feedback *string `json:"feedback" gorm:"type:text;comment:AI反馈"`
|
||||
StartedAt time.Time `json:"started_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;comment:开始时间"`
|
||||
CompletedAt *time.Time `json:"completed_at" gorm:"type:timestamp;comment:完成时间"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;comment:创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;comment:更新时间"`
|
||||
|
||||
// 关联关系
|
||||
User User `json:"-" gorm:"foreignKey:UserID"`
|
||||
Material ListeningMaterial `json:"-" gorm:"foreignKey:MaterialID"`
|
||||
}
|
||||
|
||||
// ReadingMaterial 阅读材料模型
|
||||
type ReadingMaterial struct {
|
||||
ID string `json:"id" gorm:"type:varchar(36);primaryKey;comment:材料ID"`
|
||||
Title string `json:"title" gorm:"type:varchar(200);not null;comment:标题"`
|
||||
Content string `json:"content" gorm:"type:longtext;not null;comment:内容"`
|
||||
Summary *string `json:"summary" gorm:"type:text;comment:摘要"`
|
||||
WordCount int `json:"word_count" gorm:"type:int;comment:字数"`
|
||||
Level string `json:"level" gorm:"type:enum('beginner','intermediate','advanced');not null;comment:难度级别"`
|
||||
Category string `json:"category" gorm:"type:varchar(50);comment:分类"`
|
||||
Tags *string `json:"tags" gorm:"type:json;comment:标签(JSON数组)"`
|
||||
Source *string `json:"source" gorm:"type:varchar(200);comment:来源"`
|
||||
Author *string `json:"author" gorm:"type:varchar(100);comment:作者"`
|
||||
IsActive bool `json:"is_active" gorm:"type:boolean;default:true;comment:是否启用"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;comment:创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;comment:更新时间"`
|
||||
DeletedAt gorm.DeletedAt `json:"-" gorm:"index;comment:删除时间"`
|
||||
|
||||
// 关联关系
|
||||
ReadingRecords []ReadingRecord `json:"reading_records,omitempty" gorm:"foreignKey:MaterialID"`
|
||||
}
|
||||
|
||||
// ReadingRecord 阅读练习记录模型
|
||||
type ReadingRecord struct {
|
||||
ID string `json:"id" gorm:"type:varchar(36);primaryKey;comment:记录ID"`
|
||||
UserID string `json:"user_id" gorm:"type:varchar(36);not null;index;comment:用户ID"`
|
||||
MaterialID string `json:"material_id" gorm:"type:varchar(36);not null;index;comment:材料ID"`
|
||||
ReadingTime int `json:"reading_time" gorm:"type:int;comment:阅读时间(秒)"`
|
||||
ComprehensionScore *float64 `json:"comprehension_score" gorm:"type:decimal(5,2);comment:理解得分"`
|
||||
ReadingSpeed *float64 `json:"reading_speed" gorm:"type:decimal(8,2);comment:阅读速度(词/分钟)"`
|
||||
Progress float64 `json:"progress" gorm:"type:decimal(5,2);default:0;comment:阅读进度"`
|
||||
Bookmarks *string `json:"bookmarks" gorm:"type:json;comment:书签(JSON数组)"`
|
||||
Notes *string `json:"notes" gorm:"type:text;comment:笔记"`
|
||||
QuizAnswers *string `json:"quiz_answers" gorm:"type:json;comment:测验答案(JSON对象)"`
|
||||
StartedAt time.Time `json:"started_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;comment:开始时间"`
|
||||
CompletedAt *time.Time `json:"completed_at" gorm:"type:timestamp;comment:完成时间"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;comment:创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;comment:更新时间"`
|
||||
|
||||
// 关联关系
|
||||
User User `json:"-" gorm:"foreignKey:UserID"`
|
||||
Material ReadingMaterial `json:"-" gorm:"foreignKey:MaterialID"`
|
||||
}
|
||||
|
||||
// WritingPrompt 写作题目模型
|
||||
type WritingPrompt struct {
|
||||
ID string `json:"id" gorm:"type:varchar(36);primaryKey;comment:题目ID"`
|
||||
Title string `json:"title" gorm:"type:varchar(200);not null;comment:标题"`
|
||||
Prompt string `json:"prompt" gorm:"type:text;not null;comment:题目内容"`
|
||||
Instructions *string `json:"instructions" gorm:"type:text;comment:写作要求"`
|
||||
MinWords *int `json:"min_words" gorm:"type:int;comment:最少字数"`
|
||||
MaxWords *int `json:"max_words" gorm:"type:int;comment:最多字数"`
|
||||
TimeLimit *int `json:"time_limit" gorm:"type:int;comment:时间限制(分钟)"`
|
||||
Level string `json:"level" gorm:"type:enum('beginner','intermediate','advanced');not null;comment:难度级别"`
|
||||
Category string `json:"category" gorm:"type:varchar(50);comment:分类"`
|
||||
Tags *string `json:"tags" gorm:"type:json;comment:标签(JSON数组)"`
|
||||
SampleAnswer *string `json:"sample_answer" gorm:"type:longtext;comment:参考答案"`
|
||||
Rubric *string `json:"rubric" gorm:"type:json;comment:评分标准(JSON对象)"`
|
||||
IsActive bool `json:"is_active" gorm:"type:boolean;default:true;comment:是否启用"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;comment:创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;comment:更新时间"`
|
||||
DeletedAt gorm.DeletedAt `json:"-" gorm:"index;comment:删除时间"`
|
||||
|
||||
// 关联关系
|
||||
WritingSubmissions []WritingSubmission `json:"writing_submissions,omitempty" gorm:"foreignKey:PromptID"`
|
||||
}
|
||||
|
||||
// WritingSubmission 写作提交模型
|
||||
type WritingSubmission struct {
|
||||
ID string `json:"id" gorm:"type:varchar(36);primaryKey;comment:提交ID"`
|
||||
UserID string `json:"user_id" gorm:"type:varchar(36);not null;index;comment:用户ID"`
|
||||
PromptID string `json:"prompt_id" gorm:"type:varchar(36);not null;index;comment:题目ID"`
|
||||
Content string `json:"content" gorm:"type:longtext;not null;comment:写作内容"`
|
||||
WordCount int `json:"word_count" gorm:"type:int;comment:字数"`
|
||||
TimeSpent int `json:"time_spent" gorm:"type:int;comment:用时(秒)"`
|
||||
Score *float64 `json:"score" gorm:"type:decimal(5,2);comment:总分"`
|
||||
GrammarScore *float64 `json:"grammar_score" gorm:"type:decimal(5,2);comment:语法得分"`
|
||||
VocabScore *float64 `json:"vocab_score" gorm:"type:decimal(5,2);comment:词汇得分"`
|
||||
CoherenceScore *float64 `json:"coherence_score" gorm:"type:decimal(5,2);comment:连贯性得分"`
|
||||
Feedback *string `json:"feedback" gorm:"type:longtext;comment:AI反馈"`
|
||||
Suggestions *string `json:"suggestions" gorm:"type:json;comment:改进建议(JSON数组)"`
|
||||
StartedAt time.Time `json:"started_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;comment:开始时间"`
|
||||
SubmittedAt *time.Time `json:"submitted_at" gorm:"type:timestamp;comment:提交时间"`
|
||||
GradedAt *time.Time `json:"graded_at" gorm:"type:timestamp;comment:批改时间"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;comment:创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;comment:更新时间"`
|
||||
|
||||
// 关联关系
|
||||
User User `json:"-" gorm:"foreignKey:UserID"`
|
||||
Prompt WritingPrompt `json:"-" gorm:"foreignKey:PromptID"`
|
||||
}
|
||||
|
||||
// SpeakingScenario 口语场景模型
|
||||
type SpeakingScenario struct {
|
||||
ID string `json:"id" gorm:"type:varchar(36);primaryKey;comment:场景ID"`
|
||||
Title string `json:"title" gorm:"type:varchar(200);not null;comment:标题"`
|
||||
Description string `json:"description" gorm:"type:text;not null;comment:场景描述"`
|
||||
Context *string `json:"context" gorm:"type:text;comment:背景信息"`
|
||||
Level string `json:"level" gorm:"type:enum('beginner','intermediate','advanced');not null;comment:难度级别"`
|
||||
Category string `json:"category" gorm:"type:varchar(50);comment:分类"`
|
||||
Tags *string `json:"tags" gorm:"type:json;comment:标签(JSON数组)"`
|
||||
Dialogue *string `json:"dialogue" gorm:"type:json;comment:对话模板(JSON数组)"`
|
||||
KeyPhrases *string `json:"key_phrases" gorm:"type:json;comment:关键短语(JSON数组)"`
|
||||
IsActive bool `json:"is_active" gorm:"type:boolean;default:true;comment:是否启用"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;comment:创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;comment:更新时间"`
|
||||
DeletedAt gorm.DeletedAt `json:"-" gorm:"index;comment:删除时间"`
|
||||
|
||||
// 关联关系
|
||||
SpeakingRecords []SpeakingRecord `json:"speaking_records,omitempty" gorm:"foreignKey:ScenarioID"`
|
||||
}
|
||||
|
||||
// SpeakingRecord 口语练习记录模型
|
||||
type SpeakingRecord struct {
|
||||
ID string `json:"id" gorm:"type:varchar(36);primaryKey;comment:记录ID"`
|
||||
UserID string `json:"user_id" gorm:"type:varchar(36);not null;index;comment:用户ID"`
|
||||
ScenarioID string `json:"scenario_id" gorm:"type:varchar(36);not null;index;comment:场景ID"`
|
||||
AudioURL *string `json:"audio_url" gorm:"type:varchar(500);comment:录音URL"`
|
||||
Transcript *string `json:"transcript" gorm:"type:longtext;comment:语音识别文本"`
|
||||
Duration int `json:"duration" gorm:"type:int;comment:录音时长(秒)"`
|
||||
PronunciationScore *float64 `json:"pronunciation_score" gorm:"type:decimal(5,2);comment:发音得分"`
|
||||
FluencyScore *float64 `json:"fluency_score" gorm:"type:decimal(5,2);comment:流利度得分"`
|
||||
AccuracyScore *float64 `json:"accuracy_score" gorm:"type:decimal(5,2);comment:准确度得分"`
|
||||
OverallScore *float64 `json:"overall_score" gorm:"type:decimal(5,2);comment:总分"`
|
||||
Feedback *string `json:"feedback" gorm:"type:longtext;comment:AI反馈"`
|
||||
Suggestions *string `json:"suggestions" gorm:"type:json;comment:改进建议(JSON数组)"`
|
||||
StartedAt time.Time `json:"started_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;comment:开始时间"`
|
||||
CompletedAt *time.Time `json:"completed_at" gorm:"type:timestamp;comment:完成时间"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;comment:创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;comment:更新时间"`
|
||||
|
||||
// 关联关系
|
||||
User User `json:"-" gorm:"foreignKey:UserID"`
|
||||
Scenario SpeakingScenario `json:"-" gorm:"foreignKey:ScenarioID"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (ListeningMaterial) TableName() string {
|
||||
return "ai_listening_materials"
|
||||
}
|
||||
|
||||
func (ListeningRecord) TableName() string {
|
||||
return "ai_listening_records"
|
||||
}
|
||||
|
||||
func (ReadingMaterial) TableName() string {
|
||||
return "ai_reading_materials"
|
||||
}
|
||||
|
||||
func (ReadingRecord) TableName() string {
|
||||
return "ai_reading_records"
|
||||
}
|
||||
|
||||
func (WritingPrompt) TableName() string {
|
||||
return "ai_writing_prompts"
|
||||
}
|
||||
|
||||
func (WritingSubmission) TableName() string {
|
||||
return "ai_writing_submissions"
|
||||
}
|
||||
|
||||
func (SpeakingScenario) TableName() string {
|
||||
return "ai_speaking_scenarios"
|
||||
}
|
||||
|
||||
func (SpeakingRecord) TableName() string {
|
||||
return "ai_speaking_records"
|
||||
}
|
||||
44
serve/internal/models/notification.go
Normal file
44
serve/internal/models/notification.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Notification 通知模型
|
||||
type Notification struct {
|
||||
ID int64 `json:"id" gorm:"type:bigint;primaryKey;autoIncrement;comment:通知ID"`
|
||||
UserID int64 `json:"user_id" gorm:"type:bigint;not null;index;comment:用户ID"`
|
||||
Type string `json:"type" gorm:"type:varchar(50);not null;index;comment:通知类型"`
|
||||
Title string `json:"title" gorm:"type:varchar(255);not null;comment:通知标题"`
|
||||
Content string `json:"content" gorm:"type:text;not null;comment:通知内容"`
|
||||
Link *string `json:"link" gorm:"type:varchar(500);comment:跳转链接"`
|
||||
IsRead bool `json:"is_read" gorm:"type:boolean;default:false;index;comment:是否已读"`
|
||||
Priority int `json:"priority" gorm:"type:tinyint;default:0;comment:优先级:0-普通,1-重要,2-紧急"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;index;comment:创建时间"`
|
||||
ReadAt *time.Time `json:"read_at" gorm:"type:timestamp;comment:阅读时间"`
|
||||
DeletedAt gorm.DeletedAt `json:"-" gorm:"index;comment:删除时间"`
|
||||
|
||||
// 关联关系
|
||||
User User `json:"-"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (Notification) TableName() string {
|
||||
return "ai_notifications"
|
||||
}
|
||||
|
||||
// NotificationType 通知类型常量
|
||||
const (
|
||||
NotificationTypeSystem = "system" // 系统通知
|
||||
NotificationTypeLearning = "learning" // 学习提醒
|
||||
NotificationTypeAchievement = "achievement" // 成就通知
|
||||
)
|
||||
|
||||
// NotificationPriority 通知优先级常量
|
||||
const (
|
||||
NotificationPriorityNormal = 0 // 普通
|
||||
NotificationPriorityImportant = 1 // 重要
|
||||
NotificationPriorityUrgent = 2 // 紧急
|
||||
)
|
||||
59
serve/internal/models/study_plan.go
Normal file
59
serve/internal/models/study_plan.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// StudyPlan 学习计划模型
|
||||
type StudyPlan struct {
|
||||
ID int64 `json:"id" gorm:"type:bigint;primaryKey;autoIncrement;comment:计划ID"`
|
||||
UserID int64 `json:"user_id" gorm:"type:bigint;not null;index;comment:用户ID"`
|
||||
BookID *string `json:"book_id" gorm:"type:varchar(36);index;comment:词汇书ID(可选)"`
|
||||
PlanName string `json:"plan_name" gorm:"type:varchar(200);not null;comment:计划名称"`
|
||||
Description *string `json:"description" gorm:"type:text;comment:计划描述"`
|
||||
DailyGoal int `json:"daily_goal" gorm:"type:int;not null;default:20;comment:每日目标单词数"`
|
||||
TotalWords int `json:"total_words" gorm:"type:int;default:0;comment:计划总单词数"`
|
||||
LearnedWords int `json:"learned_words" gorm:"type:int;default:0;comment:已学单词数"`
|
||||
StartDate time.Time `json:"start_date" gorm:"type:date;not null;comment:开始日期"`
|
||||
EndDate *time.Time `json:"end_date" gorm:"type:date;comment:结束日期"`
|
||||
Status string `json:"status" gorm:"type:enum('active','paused','completed','cancelled');default:'active';comment:计划状态"`
|
||||
RemindTime *string `json:"remind_time" gorm:"type:varchar(10);comment:提醒时间(HH:mm格式)"`
|
||||
RemindDays *string `json:"remind_days" gorm:"type:varchar(20);comment:提醒日期(1,2,3..7表示周一到周日)"`
|
||||
IsRemindEnabled bool `json:"is_remind_enabled" gorm:"type:boolean;default:false;comment:是否启用提醒"`
|
||||
LastStudyDate *time.Time `json:"last_study_date" gorm:"type:date;comment:最后学习日期"`
|
||||
StreakDays int `json:"streak_days" gorm:"type:int;default:0;comment:连续学习天数"`
|
||||
CompletedAt *time.Time `json:"completed_at" gorm:"type:timestamp;comment:完成时间"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;comment:创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;comment:更新时间"`
|
||||
DeletedAt gorm.DeletedAt `json:"-" gorm:"index;comment:删除时间"`
|
||||
|
||||
// 关联关系
|
||||
User User `json:"-" gorm:"foreignKey:UserID"`
|
||||
Book *VocabularyBook `json:"book,omitempty" gorm:"-"`
|
||||
}
|
||||
|
||||
func (StudyPlan) TableName() string {
|
||||
return "ai_study_plans"
|
||||
}
|
||||
|
||||
// StudyPlanRecord 学习计划完成记录
|
||||
type StudyPlanRecord struct {
|
||||
ID int64 `json:"id" gorm:"type:bigint;primaryKey;autoIncrement;comment:记录ID"`
|
||||
PlanID int64 `json:"plan_id" gorm:"type:bigint;not null;index;comment:计划ID"`
|
||||
UserID int64 `json:"user_id" gorm:"type:bigint;not null;index;comment:用户ID"`
|
||||
StudyDate time.Time `json:"study_date" gorm:"type:date;not null;index;comment:学习日期"`
|
||||
WordsStudied int `json:"words_studied" gorm:"type:int;default:0;comment:学习单词数"`
|
||||
GoalCompleted bool `json:"goal_completed" gorm:"type:boolean;default:false;comment:是否完成目标"`
|
||||
StudyDuration int `json:"study_duration" gorm:"type:int;default:0;comment:学习时长(分钟)"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;comment:创建时间"`
|
||||
|
||||
// 关联关系
|
||||
Plan StudyPlan `json:"-" gorm:"foreignKey:PlanID"`
|
||||
User User `json:"-" gorm:"foreignKey:UserID"`
|
||||
}
|
||||
|
||||
func (StudyPlanRecord) TableName() string {
|
||||
return "ai_study_plan_records"
|
||||
}
|
||||
190
serve/internal/models/test.go
Normal file
190
serve/internal/models/test.go
Normal file
@@ -0,0 +1,190 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestType 测试类型
|
||||
type TestType string
|
||||
|
||||
const (
|
||||
TestTypeQuick TestType = "quick" // 快速测试
|
||||
TestTypeComprehensive TestType = "comprehensive" // 综合测试
|
||||
TestTypeDaily TestType = "daily" // 每日测试
|
||||
TestTypeCustom TestType = "custom" // 自定义测试
|
||||
)
|
||||
|
||||
// TestDifficulty 测试难度
|
||||
type TestDifficulty string
|
||||
|
||||
const (
|
||||
TestDifficultyBeginner TestDifficulty = "beginner" // 初级
|
||||
TestDifficultyIntermediate TestDifficulty = "intermediate" // 中级
|
||||
TestDifficultyAdvanced TestDifficulty = "advanced" // 高级
|
||||
)
|
||||
|
||||
// TestStatus 测试状态
|
||||
type TestStatus string
|
||||
|
||||
const (
|
||||
TestStatusPending TestStatus = "pending" // 待开始
|
||||
TestStatusInProgress TestStatus = "in_progress" // 进行中
|
||||
TestStatusPaused TestStatus = "paused" // 已暂停
|
||||
TestStatusCompleted TestStatus = "completed" // 已完成
|
||||
TestStatusAbandoned TestStatus = "abandoned" // 已放弃
|
||||
)
|
||||
|
||||
// SkillType 技能类型
|
||||
type SkillType string
|
||||
|
||||
const (
|
||||
SkillTypeVocabulary SkillType = "vocabulary" // 词汇
|
||||
SkillTypeGrammar SkillType = "grammar" // 语法
|
||||
SkillTypeReading SkillType = "reading" // 阅读
|
||||
SkillTypeListening SkillType = "listening" // 听力
|
||||
SkillTypeSpeaking SkillType = "speaking" // 口语
|
||||
SkillTypeWriting SkillType = "writing" // 写作
|
||||
)
|
||||
|
||||
// QuestionType 题目类型
|
||||
type QuestionType string
|
||||
|
||||
const (
|
||||
QuestionTypeSingleChoice QuestionType = "single_choice" // 单选题
|
||||
QuestionTypeMultipleChoice QuestionType = "multiple_choice" // 多选题
|
||||
QuestionTypeTrueFalse QuestionType = "true_false" // 判断题
|
||||
QuestionTypeFillBlank QuestionType = "fill_blank" // 填空题
|
||||
QuestionTypeShortAnswer QuestionType = "short_answer" // 简答题
|
||||
)
|
||||
|
||||
// TestTemplate 测试模板
|
||||
type TestTemplate struct {
|
||||
ID string `json:"id" gorm:"primaryKey;type:varchar(36)"`
|
||||
Title string `json:"title" gorm:"type:varchar(255);not null"`
|
||||
Description string `json:"description" gorm:"type:text"`
|
||||
Type TestType `json:"type" gorm:"type:varchar(50);not null"`
|
||||
Difficulty TestDifficulty `json:"difficulty" gorm:"type:varchar(50)"`
|
||||
Duration int `json:"duration" gorm:"comment:测试时长(秒)"`
|
||||
TotalQuestions int `json:"total_questions" gorm:"comment:总题目数"`
|
||||
PassingScore int `json:"passing_score" gorm:"comment:及格分数"`
|
||||
MaxScore int `json:"max_score" gorm:"comment:最高分数"`
|
||||
QuestionConfig string `json:"question_config" gorm:"type:json;comment:题目配置"`
|
||||
SkillDistribution string `json:"skill_distribution" gorm:"type:json;comment:技能分布"`
|
||||
IsActive bool `json:"is_active" gorm:"default:true"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (TestTemplate) TableName() string {
|
||||
return "test_templates"
|
||||
}
|
||||
|
||||
// TestQuestion 测试题目
|
||||
type TestQuestion struct {
|
||||
ID string `json:"id" gorm:"primaryKey;type:varchar(36)"`
|
||||
TemplateID string `json:"template_id" gorm:"type:varchar(36);index"`
|
||||
QuestionType QuestionType `json:"question_type" gorm:"type:varchar(50);not null"`
|
||||
SkillType SkillType `json:"skill_type" gorm:"type:varchar(50);not null"`
|
||||
Difficulty TestDifficulty `json:"difficulty" gorm:"type:varchar(50)"`
|
||||
Content string `json:"content" gorm:"type:text;not null;comment:题目内容"`
|
||||
Options string `json:"options" gorm:"type:json;comment:选项(JSON数组)"`
|
||||
CorrectAnswer string `json:"correct_answer" gorm:"type:text;comment:正确答案"`
|
||||
Explanation string `json:"explanation" gorm:"type:text;comment:答案解析"`
|
||||
Points int `json:"points" gorm:"default:1;comment:分值"`
|
||||
OrderIndex int `json:"order_index" gorm:"comment:题目顺序"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (TestQuestion) TableName() string {
|
||||
return "test_questions"
|
||||
}
|
||||
|
||||
// TestSession 测试会话
|
||||
type TestSession struct {
|
||||
ID string `json:"id" gorm:"primaryKey;type:varchar(36)"`
|
||||
TemplateID string `json:"template_id" gorm:"type:varchar(36);not null;index"`
|
||||
UserID string `json:"user_id" gorm:"type:varchar(36);not null;index"`
|
||||
Status TestStatus `json:"status" gorm:"type:varchar(50);not null;default:'pending'"`
|
||||
StartTime *time.Time `json:"start_time"`
|
||||
EndTime *time.Time `json:"end_time"`
|
||||
PausedAt *time.Time `json:"paused_at"`
|
||||
TimeRemaining int `json:"time_remaining" gorm:"comment:剩余时间(秒)"`
|
||||
CurrentQuestionIndex int `json:"current_question_index" gorm:"default:0"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
// 关联
|
||||
Template *TestTemplate `json:"template,omitempty" gorm:"foreignKey:TemplateID"`
|
||||
Questions []TestQuestion `json:"questions,omitempty" gorm:"many2many:test_session_questions"`
|
||||
Answers []TestAnswer `json:"answers,omitempty" gorm:"foreignKey:SessionID"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (TestSession) TableName() string {
|
||||
return "test_sessions"
|
||||
}
|
||||
|
||||
// TestAnswer 测试答案
|
||||
type TestAnswer struct {
|
||||
ID string `json:"id" gorm:"primaryKey;type:varchar(36)"`
|
||||
SessionID string `json:"session_id" gorm:"type:varchar(36);not null;index"`
|
||||
QuestionID string `json:"question_id" gorm:"type:varchar(36);not null;index"`
|
||||
Answer string `json:"answer" gorm:"type:text;comment:用户答案"`
|
||||
IsCorrect *bool `json:"is_correct" gorm:"comment:是否正确"`
|
||||
Score int `json:"score" gorm:"default:0;comment:得分"`
|
||||
TimeSpent int `json:"time_spent" gorm:"comment:答题用时(秒)"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
// 关联
|
||||
Question *TestQuestion `json:"question,omitempty" gorm:"foreignKey:QuestionID"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (TestAnswer) TableName() string {
|
||||
return "test_answers"
|
||||
}
|
||||
|
||||
// TestResult 测试结果
|
||||
type TestResult struct {
|
||||
ID string `json:"id" gorm:"primaryKey;type:varchar(36)"`
|
||||
SessionID string `json:"session_id" gorm:"type:varchar(36);not null;unique;index"`
|
||||
UserID string `json:"user_id" gorm:"type:varchar(36);not null;index"`
|
||||
TemplateID string `json:"template_id" gorm:"type:varchar(36);not null;index"`
|
||||
TotalScore int `json:"total_score" gorm:"comment:总得分"`
|
||||
MaxScore int `json:"max_score" gorm:"comment:最高分"`
|
||||
Percentage float64 `json:"percentage" gorm:"type:decimal(5,2);comment:得分百分比"`
|
||||
CorrectCount int `json:"correct_count" gorm:"comment:正确题数"`
|
||||
WrongCount int `json:"wrong_count" gorm:"comment:错误题数"`
|
||||
SkippedCount int `json:"skipped_count" gorm:"comment:跳过题数"`
|
||||
TimeSpent int `json:"time_spent" gorm:"comment:总用时(秒)"`
|
||||
SkillScores string `json:"skill_scores" gorm:"type:json;comment:各技能得分"`
|
||||
Passed bool `json:"passed" gorm:"comment:是否通过"`
|
||||
CompletedAt time.Time `json:"completed_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
|
||||
// 关联
|
||||
Session *TestSession `json:"session,omitempty" gorm:"foreignKey:SessionID"`
|
||||
Template *TestTemplate `json:"template,omitempty" gorm:"foreignKey:TemplateID"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (TestResult) TableName() string {
|
||||
return "test_results"
|
||||
}
|
||||
|
||||
// TestSessionQuestion 测试会话题目关联表
|
||||
type TestSessionQuestion struct {
|
||||
SessionID string `gorm:"primaryKey;type:varchar(36)"`
|
||||
QuestionID string `gorm:"primaryKey;type:varchar(36)"`
|
||||
OrderIndex int `gorm:"comment:题目顺序"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (TestSessionQuestion) TableName() string {
|
||||
return "test_session_questions"
|
||||
}
|
||||
84
serve/internal/models/user.go
Normal file
84
serve/internal/models/user.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// User 用户模型
|
||||
type User struct {
|
||||
ID int64 `json:"id" gorm:"type:bigint;primaryKey;autoIncrement;comment:用户ID"`
|
||||
Username string `json:"username" gorm:"type:varchar(50);uniqueIndex;not null;comment:用户名"`
|
||||
Email string `json:"email" gorm:"type:varchar(100);uniqueIndex;not null;comment:邮箱"`
|
||||
Phone *string `json:"phone" gorm:"type:varchar(20);uniqueIndex;comment:手机号"`
|
||||
PasswordHash string `json:"-" gorm:"type:varchar(255);not null;comment:密码哈希"`
|
||||
Nickname *string `json:"nickname" gorm:"type:varchar(100);comment:昵称"`
|
||||
Avatar *string `json:"avatar" gorm:"type:varchar(500);comment:头像URL"`
|
||||
Gender *string `json:"gender" gorm:"type:enum('male','female','other');comment:性别"`
|
||||
BirthDate *time.Time `json:"birth_date" gorm:"type:date;comment:出生日期"`
|
||||
Bio *string `json:"bio" gorm:"type:text;comment:个人简介"`
|
||||
Location *string `json:"location" gorm:"type:varchar(100);comment:所在地"`
|
||||
Timezone string `json:"timezone" gorm:"type:varchar(50);default:'Asia/Shanghai';comment:时区"`
|
||||
Language string `json:"language" gorm:"type:varchar(10);default:'zh-CN';comment:界面语言"`
|
||||
EmailVerified bool `json:"email_verified" gorm:"type:boolean;default:false;comment:邮箱是否验证"`
|
||||
PhoneVerified bool `json:"phone_verified" gorm:"type:boolean;default:false;comment:手机是否验证"`
|
||||
Status string `json:"status" gorm:"type:enum('active','inactive','suspended','deleted');default:'active';comment:账户状态"`
|
||||
LastLoginAt *time.Time `json:"last_login_at" gorm:"type:timestamp;comment:最后登录时间"`
|
||||
LastLoginIP *string `json:"last_login_ip" gorm:"type:varchar(45);comment:最后登录IP"`
|
||||
LoginCount int `json:"login_count" gorm:"type:int;default:0;comment:登录次数"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;comment:创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;comment:更新时间"`
|
||||
DeletedAt gorm.DeletedAt `json:"-" gorm:"index;comment:删除时间"`
|
||||
|
||||
// 关联关系
|
||||
SocialLinks []UserSocialLink `json:"social_links,omitempty"`
|
||||
Preferences *UserPreference `json:"preferences,omitempty"`
|
||||
VocabularyProgress []UserVocabularyProgress `json:"vocabulary_progress,omitempty"`
|
||||
}
|
||||
|
||||
// UserSocialLink 用户社交链接模型
|
||||
type UserSocialLink struct {
|
||||
ID int64 `json:"id" gorm:"type:bigint;primaryKey;autoIncrement;comment:ID"`
|
||||
UserID int64 `json:"user_id" gorm:"type:bigint;not null;index;comment:用户ID"`
|
||||
Platform string `json:"platform" gorm:"type:varchar(50);not null;comment:平台名称"`
|
||||
URL string `json:"url" gorm:"type:varchar(500);not null;comment:链接地址"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;comment:创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;comment:更新时间"`
|
||||
|
||||
// 关联关系
|
||||
User User `json:"-"`
|
||||
}
|
||||
|
||||
// UserPreference 用户偏好设置模型
|
||||
type UserPreference struct {
|
||||
ID int64 `json:"id" gorm:"type:bigint;primaryKey;autoIncrement;comment:ID"`
|
||||
UserID int64 `json:"user_id" gorm:"type:bigint;uniqueIndex;not null;comment:用户ID"`
|
||||
DailyGoal int `json:"daily_goal" gorm:"type:int;default:50;comment:每日学习目标(分钟)"`
|
||||
WeeklyGoal int `json:"weekly_goal" gorm:"type:int;default:350;comment:每周学习目标(分钟)"`
|
||||
ReminderEnabled bool `json:"reminder_enabled" gorm:"type:boolean;default:true;comment:是否启用提醒"`
|
||||
ReminderTime *string `json:"reminder_time" gorm:"type:time;comment:提醒时间"`
|
||||
DifficultyLevel string `json:"difficulty_level" gorm:"type:enum('beginner','intermediate','advanced');default:'beginner';comment:难度级别"`
|
||||
LearningMode string `json:"learning_mode" gorm:"type:enum('casual','intensive','exam_prep');default:'casual';comment:学习模式"`
|
||||
PreferredTopics *string `json:"preferred_topics" gorm:"type:json;comment:偏好话题(JSON数组)"`
|
||||
NotificationSettings *string `json:"notification_settings" gorm:"type:json;comment:通知设置(JSON对象)"`
|
||||
PrivacySettings *string `json:"privacy_settings" gorm:"type:json;comment:隐私设置(JSON对象)"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;comment:创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;comment:更新时间"`
|
||||
|
||||
// 关联关系
|
||||
User User `json:"-"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (User) TableName() string {
|
||||
return "ai_users"
|
||||
}
|
||||
|
||||
func (UserSocialLink) TableName() string {
|
||||
return "ai_user_social_links"
|
||||
}
|
||||
|
||||
func (UserPreference) TableName() string {
|
||||
return "ai_user_preferences"
|
||||
}
|
||||
314
serve/internal/models/user_test.go
Normal file
314
serve/internal/models/user_test.go
Normal file
@@ -0,0 +1,314 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestUser tests the User model
|
||||
func TestUser(t *testing.T) {
|
||||
t.Run("Create User", func(t *testing.T) {
|
||||
user := &User{
|
||||
Username: "testuser",
|
||||
Email: "test@example.com",
|
||||
PasswordHash: "hashedpassword",
|
||||
Status: "active",
|
||||
Timezone: "Asia/Shanghai",
|
||||
Language: "zh-CN",
|
||||
}
|
||||
|
||||
if user.Username != "testuser" {
|
||||
t.Errorf("Expected username 'testuser', got '%s'", user.Username)
|
||||
}
|
||||
|
||||
if user.Email != "test@example.com" {
|
||||
t.Errorf("Expected email 'test@example.com', got '%s'", user.Email)
|
||||
}
|
||||
|
||||
if user.Status != "active" {
|
||||
t.Errorf("Expected status 'active', got '%s'", user.Status)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("User Validation", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
user User
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "Valid User",
|
||||
user: User{
|
||||
Username: "validuser",
|
||||
Email: "valid@example.com",
|
||||
PasswordHash: "validpassword",
|
||||
Status: "active",
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Empty Username",
|
||||
user: User{
|
||||
Username: "",
|
||||
Email: "valid@example.com",
|
||||
PasswordHash: "validpassword",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Empty Email",
|
||||
user: User{
|
||||
Username: "validuser",
|
||||
Email: "",
|
||||
PasswordHash: "validpassword",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Empty Password",
|
||||
user: User{
|
||||
Username: "validuser",
|
||||
Email: "valid@example.com",
|
||||
PasswordHash: "",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
isValid := validateUser(tt.user)
|
||||
if isValid != tt.expected {
|
||||
t.Errorf("Expected %v, got %v", tt.expected, isValid)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("User Timestamps", func(t *testing.T) {
|
||||
user := &User{
|
||||
Username: "testuser",
|
||||
Email: "test@example.com",
|
||||
PasswordHash: "hashedpassword",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
if user.CreatedAt.IsZero() {
|
||||
t.Error("CreatedAt should not be zero")
|
||||
}
|
||||
|
||||
if user.UpdatedAt.IsZero() {
|
||||
t.Error("UpdatedAt should not be zero")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// validateUser is a helper function for testing user validation
|
||||
func validateUser(user User) bool {
|
||||
if user.Username == "" {
|
||||
return false
|
||||
}
|
||||
if user.Email == "" {
|
||||
return false
|
||||
}
|
||||
if user.PasswordHash == "" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// TestUserPreference tests the UserPreference model
|
||||
func TestUserPreference(t *testing.T) {
|
||||
t.Run("Create UserPreference", func(t *testing.T) {
|
||||
preference := &UserPreference{
|
||||
UserID: 1,
|
||||
DailyGoal: 50,
|
||||
WeeklyGoal: 350,
|
||||
ReminderEnabled: true,
|
||||
DifficultyLevel: "beginner",
|
||||
LearningMode: "casual",
|
||||
}
|
||||
|
||||
if preference.UserID != 1 {
|
||||
t.Errorf("Expected UserID 1, got %d", preference.UserID)
|
||||
}
|
||||
|
||||
if preference.DailyGoal != 50 {
|
||||
t.Errorf("Expected DailyGoal 50, got %d", preference.DailyGoal)
|
||||
}
|
||||
|
||||
if preference.DifficultyLevel != "beginner" {
|
||||
t.Errorf("Expected DifficultyLevel 'beginner', got '%s'", preference.DifficultyLevel)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Preference Validation", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
preference UserPreference
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "Valid Preference",
|
||||
preference: UserPreference{
|
||||
UserID: 1,
|
||||
DailyGoal: 50,
|
||||
WeeklyGoal: 350,
|
||||
DifficultyLevel: "beginner",
|
||||
LearningMode: "casual",
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid UserID",
|
||||
preference: UserPreference{
|
||||
UserID: 0,
|
||||
DailyGoal: 50,
|
||||
DifficultyLevel: "beginner",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Negative Daily Goal",
|
||||
preference: UserPreference{
|
||||
UserID: 1,
|
||||
DailyGoal: -10,
|
||||
DifficultyLevel: "beginner",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid Difficulty Level",
|
||||
preference: UserPreference{
|
||||
UserID: 1,
|
||||
DailyGoal: 50,
|
||||
DifficultyLevel: "invalid",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
isValid := validateUserPreference(tt.preference)
|
||||
if isValid != tt.expected {
|
||||
t.Errorf("Expected %v, got %v", tt.expected, isValid)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// validateUserPreference is a helper function for testing user preference validation
|
||||
func validateUserPreference(preference UserPreference) bool {
|
||||
if preference.UserID <= 0 {
|
||||
return false
|
||||
}
|
||||
if preference.DailyGoal < 0 || preference.WeeklyGoal < 0 {
|
||||
return false
|
||||
}
|
||||
validDifficultyLevels := []string{"beginner", "intermediate", "advanced"}
|
||||
validDifficulty := false
|
||||
for _, level := range validDifficultyLevels {
|
||||
if preference.DifficultyLevel == level {
|
||||
validDifficulty = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !validDifficulty {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// TestUserSocialLink tests the UserSocialLink model
|
||||
func TestUserSocialLink(t *testing.T) {
|
||||
t.Run("Create UserSocialLink", func(t *testing.T) {
|
||||
socialLink := &UserSocialLink{
|
||||
UserID: 1,
|
||||
Platform: "github",
|
||||
URL: "https://github.com/testuser",
|
||||
}
|
||||
|
||||
if socialLink.UserID != 1 {
|
||||
t.Errorf("Expected UserID 1, got %d", socialLink.UserID)
|
||||
}
|
||||
|
||||
if socialLink.Platform != "github" {
|
||||
t.Errorf("Expected Platform 'github', got '%s'", socialLink.Platform)
|
||||
}
|
||||
|
||||
if socialLink.URL != "https://github.com/testuser" {
|
||||
t.Errorf("Expected URL 'https://github.com/testuser', got '%s'", socialLink.URL)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Social Link Validation", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
socialLink UserSocialLink
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "Valid Social Link",
|
||||
socialLink: UserSocialLink{
|
||||
UserID: 1,
|
||||
Platform: "github",
|
||||
URL: "https://github.com/testuser",
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid UserID",
|
||||
socialLink: UserSocialLink{
|
||||
UserID: 0,
|
||||
Platform: "github",
|
||||
URL: "https://github.com/testuser",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Empty Platform",
|
||||
socialLink: UserSocialLink{
|
||||
UserID: 1,
|
||||
Platform: "",
|
||||
URL: "https://github.com/testuser",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Empty URL",
|
||||
socialLink: UserSocialLink{
|
||||
UserID: 1,
|
||||
Platform: "github",
|
||||
URL: "",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
isValid := validateUserSocialLink(tt.socialLink)
|
||||
if isValid != tt.expected {
|
||||
t.Errorf("Expected %v, got %v", tt.expected, isValid)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// validateUserSocialLink is a helper function for testing user social link validation
|
||||
func validateUserSocialLink(socialLink UserSocialLink) bool {
|
||||
if socialLink.UserID <= 0 {
|
||||
return false
|
||||
}
|
||||
if socialLink.Platform == "" {
|
||||
return false
|
||||
}
|
||||
if socialLink.URL == "" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
282
serve/internal/models/vocabulary.go
Normal file
282
serve/internal/models/vocabulary.go
Normal file
@@ -0,0 +1,282 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// VocabularyCategory 词汇分类模型
|
||||
type VocabularyCategory struct {
|
||||
ID string `json:"id" gorm:"type:varchar(36);primaryKey;comment:分类ID"`
|
||||
Name string `json:"name" gorm:"type:varchar(100);not null;comment:分类名称"`
|
||||
Description *string `json:"description" gorm:"type:text;comment:分类描述"`
|
||||
Level string `json:"level" gorm:"type:enum('beginner','intermediate','advanced');not null;comment:难度级别"`
|
||||
Icon *string `json:"icon" gorm:"type:varchar(255);comment:图标URL"`
|
||||
Color *string `json:"color" gorm:"type:varchar(7);comment:主题色"`
|
||||
SortOrder int `json:"sort_order" gorm:"type:int;default:0;comment:排序"`
|
||||
IsActive bool `json:"is_active" gorm:"type:boolean;default:true;comment:是否启用"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;comment:创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;comment:更新时间"`
|
||||
|
||||
// 关联关系
|
||||
Vocabularies []Vocabulary `json:"vocabularies,omitempty" gorm:"many2many:ai_vocabulary_category_relations;foreignKey:ID;joinForeignKey:CategoryID;References:ID;joinReferences:VocabularyID;"`
|
||||
}
|
||||
|
||||
// Vocabulary 词汇模型
|
||||
type Vocabulary struct {
|
||||
ID int64 `json:"id" gorm:"column:id;primaryKey;autoIncrement;comment:词汇ID"`
|
||||
Word string `json:"word" gorm:"column:word;type:varchar(100);uniqueIndex;not null;comment:单词"`
|
||||
Phonetic *string `json:"phonetic" gorm:"column:phonetic;type:varchar(200);comment:音标"`
|
||||
AudioURL *string `json:"audio_url" gorm:"column:audio_url;type:varchar(500);comment:音频URL"`
|
||||
Level string `json:"level" gorm:"column:level;type:enum('beginner','intermediate','advanced');not null;comment:难度级别"`
|
||||
Frequency int `json:"frequency" gorm:"column:frequency;type:int;default:0;comment:使用频率"`
|
||||
IsActive bool `json:"is_active" gorm:"column:is_active;type:boolean;default:true;comment:是否启用"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"column:created_at;type:timestamp;default:CURRENT_TIMESTAMP;comment:创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at;type:timestamp;default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;comment:更新时间"`
|
||||
DeletedAt gorm.DeletedAt `json:"-" gorm:"column:deleted_at;index;comment:删除时间"`
|
||||
|
||||
// 关联关系
|
||||
Definitions []VocabularyDefinition `json:"definitions,omitempty" gorm:"foreignKey:VocabularyID"`
|
||||
Examples []VocabularyExample `json:"examples,omitempty" gorm:"foreignKey:VocabularyID"`
|
||||
Images []VocabularyImage `json:"images,omitempty" gorm:"foreignKey:VocabularyID"`
|
||||
Categories []VocabularyCategory `json:"categories,omitempty" gorm:"many2many:ai_vocabulary_category_relations;foreignKey:ID;joinForeignKey:VocabularyID;References:ID;joinReferences:CategoryID;"`
|
||||
UserProgress []UserVocabularyProgress `json:"user_progress,omitempty" gorm:"foreignKey:VocabularyID"`
|
||||
}
|
||||
|
||||
// VocabularyDefinition 词汇定义模型
|
||||
type VocabularyDefinition struct {
|
||||
ID int64 `json:"id" gorm:"column:id;primaryKey;autoIncrement;comment:定义ID"`
|
||||
VocabularyID int64 `json:"vocabulary_id" gorm:"column:vocabulary_id;not null;index;comment:词汇ID"`
|
||||
PartOfSpeech string `json:"part_of_speech" gorm:"column:part_of_speech;type:varchar(20);not null;comment:词性"`
|
||||
Definition string `json:"definition" gorm:"column:definition_en;type:text;not null;comment:英文定义"`
|
||||
Translation string `json:"translation" gorm:"column:definition_cn;type:text;not null;comment:中文翻译"`
|
||||
SortOrder int `json:"sort_order" gorm:"column:sort_order;type:int;default:0;comment:排序"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"column:created_at;type:timestamp;default:CURRENT_TIMESTAMP;comment:创建时间"`
|
||||
|
||||
// 关联关系
|
||||
Vocabulary Vocabulary `json:"-" gorm:"foreignKey:VocabularyID"`
|
||||
}
|
||||
|
||||
// VocabularyExample 词汇例句模型
|
||||
type VocabularyExample struct {
|
||||
ID int64 `json:"id" gorm:"column:id;primaryKey;autoIncrement;comment:例句ID"`
|
||||
VocabularyID int64 `json:"vocabulary_id" gorm:"column:vocabulary_id;not null;index;comment:词汇ID"`
|
||||
Example string `json:"example" gorm:"column:sentence_en;type:text;not null;comment:英文例句"`
|
||||
Translation string `json:"translation" gorm:"column:sentence_cn;type:text;not null;comment:中文翻译"`
|
||||
AudioURL *string `json:"audio_url" gorm:"column:audio_url;type:varchar(500);comment:音频URL"`
|
||||
SortOrder int `json:"sort_order" gorm:"column:sort_order;type:int;default:0;comment:排序"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"column:created_at;type:timestamp;default:CURRENT_TIMESTAMP;comment:创建时间"`
|
||||
|
||||
// 关联关系
|
||||
Vocabulary Vocabulary `json:"-" gorm:"foreignKey:VocabularyID"`
|
||||
}
|
||||
|
||||
// VocabularyImage 词汇图片模型
|
||||
type VocabularyImage struct {
|
||||
ID string `json:"id" gorm:"type:varchar(36);primaryKey;comment:图片ID"`
|
||||
VocabularyID string `json:"vocabulary_id" gorm:"type:varchar(36);not null;index;comment:词汇ID"`
|
||||
ImageURL string `json:"image_url" gorm:"type:varchar(500);not null;comment:图片URL"`
|
||||
AltText *string `json:"alt_text" gorm:"type:varchar(255);comment:替代文本"`
|
||||
Caption *string `json:"caption" gorm:"type:text;comment:图片说明"`
|
||||
SortOrder int `json:"sort_order" gorm:"type:int;default:0;comment:排序"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;comment:创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;comment:更新时间"`
|
||||
|
||||
// 关联关系
|
||||
Vocabulary Vocabulary `json:"-" gorm:"foreignKey:VocabularyID"`
|
||||
}
|
||||
|
||||
// VocabularyCategoryRelation 词汇分类关系模型
|
||||
type VocabularyCategoryRelation struct {
|
||||
VocabularyID string `json:"vocabulary_id" gorm:"type:varchar(36);primaryKey;comment:词汇ID"`
|
||||
CategoryID string `json:"category_id" gorm:"type:varchar(36);primaryKey;comment:分类ID"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;comment:创建时间"`
|
||||
}
|
||||
|
||||
// UserVocabularyProgress 用户词汇学习进度模型
|
||||
type UserVocabularyProgress struct {
|
||||
ID int64 `json:"id" gorm:"type:bigint;primaryKey;autoIncrement;comment:进度ID"`
|
||||
UserID int64 `json:"user_id" gorm:"type:bigint;not null;index;comment:用户ID"`
|
||||
VocabularyID string `json:"vocabulary_id" gorm:"type:varchar(36);not null;index;comment:词汇ID"`
|
||||
MasteryLevel int `json:"mastery_level" gorm:"type:int;default:0;comment:掌握程度(0-100)"`
|
||||
StudyCount int `json:"study_count" gorm:"type:int;default:0;comment:学习次数"`
|
||||
CorrectCount int `json:"correct_count" gorm:"type:int;default:0;comment:正确次数"`
|
||||
IncorrectCount int `json:"incorrect_count" gorm:"type:int;default:0;comment:错误次数"`
|
||||
LastStudiedAt *time.Time `json:"last_studied_at" gorm:"type:timestamp;comment:最后学习时间"`
|
||||
NextReviewAt *time.Time `json:"next_review_at" gorm:"type:timestamp;comment:下次复习时间"`
|
||||
IsMarkedDifficult bool `json:"is_marked_difficult" gorm:"type:boolean;default:false;comment:是否标记为困难"`
|
||||
IsFavorite bool `json:"is_favorite" gorm:"type:boolean;default:false;comment:是否收藏"`
|
||||
Notes *string `json:"notes" gorm:"type:text;comment:学习笔记"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;comment:创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;comment:更新时间"`
|
||||
|
||||
// 关联关系
|
||||
User User `json:"-" gorm:"foreignKey:UserID"`
|
||||
Vocabulary Vocabulary `json:"-" gorm:"foreignKey:VocabularyID"`
|
||||
}
|
||||
|
||||
// UserWordProgress 用户单词学习进度模型
|
||||
type UserWordProgress struct {
|
||||
ID int64 `json:"id" gorm:"type:bigint;primaryKey;autoIncrement;comment:进度ID"`
|
||||
UserID int64 `json:"user_id" gorm:"type:bigint;not null;index:idx_user_vocab;comment:用户ID"`
|
||||
VocabularyID int64 `json:"vocabulary_id" gorm:"type:bigint;not null;index:idx_user_vocab;comment:单词ID"`
|
||||
Status string `json:"status" gorm:"type:varchar(20);default:'not_started';comment:学习状态"`
|
||||
StudyCount int `json:"study_count" gorm:"type:int;default:0;comment:学习次数"`
|
||||
CorrectCount int `json:"correct_count" gorm:"type:int;default:0;comment:正确次数"`
|
||||
WrongCount int `json:"wrong_count" gorm:"type:int;default:0;comment:错误次数"`
|
||||
Proficiency int `json:"proficiency" gorm:"type:int;default:0;comment:熟练度(0-100)"`
|
||||
IsFavorite bool `json:"is_favorite" gorm:"type:boolean;default:false;comment:是否收藏"`
|
||||
NextReviewAt *time.Time `json:"next_review_at" gorm:"type:timestamp;comment:下次复习时间"`
|
||||
ReviewInterval int `json:"review_interval" gorm:"type:int;default:1;comment:复习间隔(天)"`
|
||||
FirstStudiedAt time.Time `json:"first_studied_at" gorm:"type:timestamp;comment:首次学习时间"`
|
||||
LastStudiedAt time.Time `json:"last_studied_at" gorm:"type:timestamp;comment:最后学习时间"`
|
||||
MasteredAt *time.Time `json:"mastered_at" gorm:"type:timestamp;comment:掌握时间"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;comment:创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;comment:更新时间"`
|
||||
|
||||
// 关联关系
|
||||
User User `json:"-" gorm:"foreignKey:UserID"`
|
||||
Vocabulary Vocabulary `json:"-" gorm:"foreignKey:VocabularyID"`
|
||||
}
|
||||
|
||||
// VocabularyTest 词汇测试模型
|
||||
type VocabularyTest struct {
|
||||
ID int64 `json:"id" gorm:"type:bigint;primaryKey;autoIncrement;comment:测试ID"`
|
||||
UserID int64 `json:"user_id" gorm:"type:bigint;not null;index;comment:用户ID"`
|
||||
TestType string `json:"test_type" gorm:"type:enum('placement','progress','review');not null;comment:测试类型"`
|
||||
Level string `json:"level" gorm:"type:enum('beginner','intermediate','advanced');comment:测试级别"`
|
||||
TotalWords int `json:"total_words" gorm:"type:int;not null;comment:总词汇数"`
|
||||
CorrectWords int `json:"correct_words" gorm:"type:int;default:0;comment:正确词汇数"`
|
||||
Score float64 `json:"score" gorm:"type:decimal(5,2);comment:得分"`
|
||||
Duration int `json:"duration" gorm:"type:int;comment:测试时长(秒)"`
|
||||
StartedAt time.Time `json:"started_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;comment:开始时间"`
|
||||
CompletedAt *time.Time `json:"completed_at" gorm:"type:timestamp;comment:完成时间"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;comment:创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;comment:更新时间"`
|
||||
|
||||
// 关联关系
|
||||
User User `json:"-" gorm:"foreignKey:UserID"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (VocabularyCategory) TableName() string {
|
||||
return "ai_vocabulary_categories"
|
||||
}
|
||||
|
||||
func (Vocabulary) TableName() string {
|
||||
return "ai_vocabulary"
|
||||
}
|
||||
|
||||
func (VocabularyDefinition) TableName() string {
|
||||
return "ai_vocabulary_definitions"
|
||||
}
|
||||
|
||||
func (VocabularyExample) TableName() string {
|
||||
return "ai_vocabulary_examples"
|
||||
}
|
||||
|
||||
func (VocabularyImage) TableName() string {
|
||||
return "ai_vocabulary_images"
|
||||
}
|
||||
|
||||
func (VocabularyCategoryRelation) TableName() string {
|
||||
return "ai_vocabulary_category_relations"
|
||||
}
|
||||
|
||||
func (UserVocabularyProgress) TableName() string {
|
||||
return "ai_user_vocabulary_progress"
|
||||
}
|
||||
|
||||
func (VocabularyTest) TableName() string {
|
||||
return "ai_vocabulary_tests"
|
||||
}
|
||||
|
||||
// VocabularyBook 词汇书模型
|
||||
type VocabularyBook struct {
|
||||
ID string `json:"id" gorm:"type:varchar(36);primaryKey;comment:词汇书ID"`
|
||||
Name string `json:"name" gorm:"type:varchar(200);not null;comment:词汇书名称"`
|
||||
Description *string `json:"description" gorm:"type:text;comment:词汇书描述"`
|
||||
Category string `json:"category" gorm:"type:varchar(100);not null;comment:分类"`
|
||||
Level string `json:"level" gorm:"type:enum('beginner','elementary','intermediate','advanced','expert');not null;comment:难度级别"`
|
||||
TotalWords int `json:"total_words" gorm:"type:int;default:0;comment:总单词数"`
|
||||
CoverImage *string `json:"cover_image" gorm:"type:varchar(500);comment:封面图片URL"`
|
||||
Icon *string `json:"icon" gorm:"type:varchar(255);comment:图标"`
|
||||
Color *string `json:"color" gorm:"type:varchar(7);comment:主题色"`
|
||||
IsSystem bool `json:"is_system" gorm:"type:boolean;default:true;comment:是否系统词汇书"`
|
||||
IsActive bool `json:"is_active" gorm:"type:boolean;default:true;comment:是否启用"`
|
||||
SortOrder int `json:"sort_order" gorm:"type:int;default:0;comment:排序"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;comment:创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;comment:更新时间"`
|
||||
|
||||
// 关联关系(不使用外键约束)
|
||||
Words []VocabularyBookWord `json:"words,omitempty" gorm:"-"`
|
||||
}
|
||||
|
||||
// VocabularyBookWord 词汇书单词关联模型
|
||||
type VocabularyBookWord struct {
|
||||
ID int64 `json:"id" gorm:"type:bigint;primaryKey;autoIncrement;comment:关联ID"`
|
||||
BookID string `json:"book_id" gorm:"type:varchar(36);not null;index;comment:词汇书ID"`
|
||||
VocabularyID string `json:"vocabulary_id" gorm:"type:varchar(36);not null;index;comment:词汇ID"`
|
||||
SortOrder int `json:"sort_order" gorm:"type:int;default:0;comment:排序"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;comment:创建时间"`
|
||||
|
||||
// 关联关系(不使用外键约束)
|
||||
Book VocabularyBook `json:"-" gorm:"-"`
|
||||
Vocabulary *Vocabulary `json:"word,omitempty" gorm:"-"`
|
||||
}
|
||||
|
||||
func (VocabularyBook) TableName() string {
|
||||
return "ai_vocabulary_books"
|
||||
}
|
||||
|
||||
func (VocabularyBookWord) TableName() string {
|
||||
return "ai_vocabulary_book_words"
|
||||
}
|
||||
|
||||
func (UserWordProgress) TableName() string {
|
||||
return "ai_user_word_progress"
|
||||
}
|
||||
|
||||
// LearningSession 学习会话模型
|
||||
type LearningSession struct {
|
||||
ID int64 `json:"id" gorm:"type:bigint;primaryKey;autoIncrement;comment:会话ID"`
|
||||
UserID int64 `json:"user_id" gorm:"type:bigint;not null;index;comment:用户ID"`
|
||||
BookID string `json:"book_id" gorm:"type:varchar(36);not null;index;comment:词汇书ID"`
|
||||
DailyGoal int `json:"daily_goal" gorm:"type:int;default:20;comment:每日学习目标"`
|
||||
NewWordsCount int `json:"new_words_count" gorm:"type:int;default:0;comment:新学单词数"`
|
||||
ReviewCount int `json:"review_count" gorm:"type:int;default:0;comment:复习单词数"`
|
||||
MasteredCount int `json:"mastered_count" gorm:"type:int;default:0;comment:掌握单词数"`
|
||||
StartedAt time.Time `json:"started_at" gorm:"type:timestamp;comment:开始时间"`
|
||||
CompletedAt *time.Time `json:"completed_at" gorm:"type:timestamp;comment:完成时间"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP;comment:创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamp;default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;comment:更新时间"`
|
||||
}
|
||||
|
||||
func (LearningSession) TableName() string {
|
||||
return "ai_learning_sessions"
|
||||
}
|
||||
|
||||
// UserVocabularyBookProgress 用户词汇书学习进度
|
||||
type UserVocabularyBookProgress struct {
|
||||
ID int64 `gorm:"primaryKey;autoIncrement" json:"id"`
|
||||
UserID int64 `gorm:"not null;index:idx_user_book" json:"user_id"`
|
||||
BookID string `gorm:"type:varchar(36);not null;index:idx_user_book" json:"book_id"`
|
||||
LearnedWords int `gorm:"default:0" json:"learned_words"`
|
||||
MasteredWords int `gorm:"default:0" json:"mastered_words"`
|
||||
ProgressPercentage float64 `gorm:"type:decimal(5,2);default:0.00" json:"progress_percentage"`
|
||||
StreakDays int `gorm:"default:0" json:"streak_days"`
|
||||
TotalStudyDays int `gorm:"default:0" json:"total_study_days"`
|
||||
AverageDailyWords float64 `gorm:"type:decimal(5,2);default:0.00" json:"average_daily_words"`
|
||||
EstimatedCompletionDate *time.Time `json:"estimated_completion_date"`
|
||||
IsCompleted bool `gorm:"default:false" json:"is_completed"`
|
||||
CompletedAt *time.Time `json:"completed_at"`
|
||||
StartedAt time.Time `gorm:"not null" json:"started_at"`
|
||||
LastStudiedAt time.Time `json:"last_studied_at"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||
}
|
||||
|
||||
func (UserVocabularyBookProgress) TableName() string {
|
||||
return "user_vocabulary_book_progress"
|
||||
}
|
||||
482
serve/internal/models/vocabulary_test.go
Normal file
482
serve/internal/models/vocabulary_test.go
Normal file
@@ -0,0 +1,482 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestVocabulary tests the Vocabulary model
|
||||
func TestVocabulary(t *testing.T) {
|
||||
t.Run("Create Vocabulary", func(t *testing.T) {
|
||||
vocab := &Vocabulary{
|
||||
ID: "vocab-123",
|
||||
Word: "hello",
|
||||
Level: "beginner",
|
||||
Frequency: 100,
|
||||
IsActive: true,
|
||||
}
|
||||
|
||||
if vocab.Word != "hello" {
|
||||
t.Errorf("Expected word 'hello', got '%s'", vocab.Word)
|
||||
}
|
||||
|
||||
if vocab.Level != "beginner" {
|
||||
t.Errorf("Expected level 'beginner', got '%s'", vocab.Level)
|
||||
}
|
||||
|
||||
if vocab.Frequency != 100 {
|
||||
t.Errorf("Expected frequency 100, got %d", vocab.Frequency)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Vocabulary Validation", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
vocab Vocabulary
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "Valid Vocabulary",
|
||||
vocab: Vocabulary{
|
||||
ID: "vocab-123",
|
||||
Word: "test",
|
||||
Level: "beginner",
|
||||
Frequency: 50,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Empty Word",
|
||||
vocab: Vocabulary{
|
||||
ID: "vocab-123",
|
||||
Word: "",
|
||||
Level: "beginner",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid Level",
|
||||
vocab: Vocabulary{
|
||||
ID: "vocab-123",
|
||||
Word: "test",
|
||||
Level: "invalid",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Negative Frequency",
|
||||
vocab: Vocabulary{
|
||||
ID: "vocab-123",
|
||||
Word: "test",
|
||||
Level: "beginner",
|
||||
Frequency: -1,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
isValid := validateVocabulary(tt.vocab)
|
||||
if isValid != tt.expected {
|
||||
t.Errorf("Expected %v, got %v", tt.expected, isValid)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// validateVocabulary is a helper function for testing vocabulary validation
|
||||
func validateVocabulary(vocab Vocabulary) bool {
|
||||
if vocab.Word == "" {
|
||||
return false
|
||||
}
|
||||
validLevels := []string{"beginner", "intermediate", "advanced"}
|
||||
validLevel := false
|
||||
for _, level := range validLevels {
|
||||
if vocab.Level == level {
|
||||
validLevel = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !validLevel {
|
||||
return false
|
||||
}
|
||||
if vocab.Frequency < 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// TestVocabularyCategory tests the VocabularyCategory model
|
||||
func TestVocabularyCategory(t *testing.T) {
|
||||
t.Run("Create VocabularyCategory", func(t *testing.T) {
|
||||
category := &VocabularyCategory{
|
||||
ID: "cat-123",
|
||||
Name: "Animals",
|
||||
Level: "beginner",
|
||||
SortOrder: 1,
|
||||
IsActive: true,
|
||||
}
|
||||
|
||||
if category.Name != "Animals" {
|
||||
t.Errorf("Expected name 'Animals', got '%s'", category.Name)
|
||||
}
|
||||
|
||||
if category.Level != "beginner" {
|
||||
t.Errorf("Expected level 'beginner', got '%s'", category.Level)
|
||||
}
|
||||
|
||||
if category.SortOrder != 1 {
|
||||
t.Errorf("Expected sort order 1, got %d", category.SortOrder)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Category Validation", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
category VocabularyCategory
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "Valid Category",
|
||||
category: VocabularyCategory{
|
||||
ID: "cat-123",
|
||||
Name: "Animals",
|
||||
Level: "beginner",
|
||||
SortOrder: 1,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Empty Name",
|
||||
category: VocabularyCategory{
|
||||
ID: "cat-123",
|
||||
Name: "",
|
||||
Level: "beginner",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid Level",
|
||||
category: VocabularyCategory{
|
||||
ID: "cat-123",
|
||||
Name: "Animals",
|
||||
Level: "invalid",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
isValid := validateVocabularyCategory(tt.category)
|
||||
if isValid != tt.expected {
|
||||
t.Errorf("Expected %v, got %v", tt.expected, isValid)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// validateVocabularyCategory is a helper function for testing vocabulary category validation
|
||||
func validateVocabularyCategory(category VocabularyCategory) bool {
|
||||
if category.Name == "" {
|
||||
return false
|
||||
}
|
||||
validLevels := []string{"beginner", "intermediate", "advanced"}
|
||||
validLevel := false
|
||||
for _, level := range validLevels {
|
||||
if category.Level == level {
|
||||
validLevel = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return validLevel
|
||||
}
|
||||
|
||||
// TestVocabularyDefinition tests the VocabularyDefinition model
|
||||
func TestVocabularyDefinition(t *testing.T) {
|
||||
t.Run("Create VocabularyDefinition", func(t *testing.T) {
|
||||
definition := &VocabularyDefinition{
|
||||
ID: "def-123",
|
||||
VocabularyID: "vocab-123",
|
||||
PartOfSpeech: "noun",
|
||||
Definition: "A greeting or expression of goodwill",
|
||||
SortOrder: 1,
|
||||
}
|
||||
|
||||
if definition.PartOfSpeech != "noun" {
|
||||
t.Errorf("Expected part of speech 'noun', got '%s'", definition.PartOfSpeech)
|
||||
}
|
||||
|
||||
if definition.Definition != "A greeting or expression of goodwill" {
|
||||
t.Errorf("Expected definition 'A greeting or expression of goodwill', got '%s'", definition.Definition)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Definition Validation", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
definition VocabularyDefinition
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "Valid Definition",
|
||||
definition: VocabularyDefinition{
|
||||
ID: "def-123",
|
||||
VocabularyID: "vocab-123",
|
||||
PartOfSpeech: "noun",
|
||||
Definition: "A greeting",
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Empty VocabularyID",
|
||||
definition: VocabularyDefinition{
|
||||
ID: "def-123",
|
||||
VocabularyID: "",
|
||||
PartOfSpeech: "noun",
|
||||
Definition: "A greeting",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Empty Definition",
|
||||
definition: VocabularyDefinition{
|
||||
ID: "def-123",
|
||||
VocabularyID: "vocab-123",
|
||||
PartOfSpeech: "noun",
|
||||
Definition: "",
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
isValid := validateVocabularyDefinition(tt.definition)
|
||||
if isValid != tt.expected {
|
||||
t.Errorf("Expected %v, got %v", tt.expected, isValid)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// validateVocabularyDefinition is a helper function for testing vocabulary definition validation
|
||||
func validateVocabularyDefinition(definition VocabularyDefinition) bool {
|
||||
if definition.VocabularyID == "" {
|
||||
return false
|
||||
}
|
||||
if definition.Definition == "" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// TestUserVocabularyProgress tests the UserVocabularyProgress model
|
||||
func TestUserVocabularyProgress(t *testing.T) {
|
||||
t.Run("Create UserVocabularyProgress", func(t *testing.T) {
|
||||
now := time.Now()
|
||||
progress := &UserVocabularyProgress{
|
||||
UserID: 1,
|
||||
VocabularyID: "vocab-123",
|
||||
MasteryLevel: 75,
|
||||
StudyCount: 10,
|
||||
CorrectCount: 8,
|
||||
IncorrectCount: 2,
|
||||
LastStudiedAt: &now,
|
||||
IsMarkedDifficult: false,
|
||||
IsFavorite: true,
|
||||
}
|
||||
|
||||
if progress.UserID != 1 {
|
||||
t.Errorf("Expected UserID 1, got %d", progress.UserID)
|
||||
}
|
||||
|
||||
if progress.MasteryLevel != 75 {
|
||||
t.Errorf("Expected MasteryLevel 75, got %d", progress.MasteryLevel)
|
||||
}
|
||||
|
||||
if progress.StudyCount != 10 {
|
||||
t.Errorf("Expected StudyCount 10, got %d", progress.StudyCount)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Progress Validation", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
progress UserVocabularyProgress
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "Valid Progress",
|
||||
progress: UserVocabularyProgress{
|
||||
UserID: 1,
|
||||
VocabularyID: "vocab-123",
|
||||
MasteryLevel: 75,
|
||||
StudyCount: 10,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid UserID",
|
||||
progress: UserVocabularyProgress{
|
||||
UserID: 0,
|
||||
VocabularyID: "vocab-123",
|
||||
MasteryLevel: 75,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid MasteryLevel",
|
||||
progress: UserVocabularyProgress{
|
||||
UserID: 1,
|
||||
VocabularyID: "vocab-123",
|
||||
MasteryLevel: 150, // Over 100
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Empty VocabularyID",
|
||||
progress: UserVocabularyProgress{
|
||||
UserID: 1,
|
||||
VocabularyID: "",
|
||||
MasteryLevel: 75,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
isValid := validateUserVocabularyProgress(tt.progress)
|
||||
if isValid != tt.expected {
|
||||
t.Errorf("Expected %v, got %v", tt.expected, isValid)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// validateUserVocabularyProgress is a helper function for testing user vocabulary progress validation
|
||||
func validateUserVocabularyProgress(progress UserVocabularyProgress) bool {
|
||||
if progress.UserID <= 0 {
|
||||
return false
|
||||
}
|
||||
if progress.VocabularyID == "" {
|
||||
return false
|
||||
}
|
||||
if progress.MasteryLevel < 0 || progress.MasteryLevel > 100 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// TestVocabularyTest tests the VocabularyTest model
|
||||
func TestVocabularyTest(t *testing.T) {
|
||||
t.Run("Create VocabularyTest", func(t *testing.T) {
|
||||
test := &VocabularyTest{
|
||||
UserID: 1,
|
||||
TestType: "placement",
|
||||
Level: "beginner",
|
||||
TotalWords: 20,
|
||||
CorrectWords: 15,
|
||||
Score: 75.0,
|
||||
Duration: 300, // 5 minutes
|
||||
StartedAt: time.Now(),
|
||||
}
|
||||
|
||||
if test.UserID != 1 {
|
||||
t.Errorf("Expected UserID 1, got %d", test.UserID)
|
||||
}
|
||||
|
||||
if test.TestType != "placement" {
|
||||
t.Errorf("Expected TestType 'placement', got '%s'", test.TestType)
|
||||
}
|
||||
|
||||
if test.Score != 75.0 {
|
||||
t.Errorf("Expected Score 75.0, got %f", test.Score)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Test Validation", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
test VocabularyTest
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "Valid Test",
|
||||
test: VocabularyTest{
|
||||
UserID: 1,
|
||||
TestType: "placement",
|
||||
Level: "beginner",
|
||||
TotalWords: 20,
|
||||
CorrectWords: 15,
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid UserID",
|
||||
test: VocabularyTest{
|
||||
UserID: 0,
|
||||
TestType: "placement",
|
||||
TotalWords: 20,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid TestType",
|
||||
test: VocabularyTest{
|
||||
UserID: 1,
|
||||
TestType: "invalid",
|
||||
TotalWords: 20,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Correct Words Greater Than Total",
|
||||
test: VocabularyTest{
|
||||
UserID: 1,
|
||||
TestType: "placement",
|
||||
TotalWords: 20,
|
||||
CorrectWords: 25,
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
isValid := validateVocabularyTest(tt.test)
|
||||
if isValid != tt.expected {
|
||||
t.Errorf("Expected %v, got %v", tt.expected, isValid)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// validateVocabularyTest is a helper function for testing vocabulary test validation
|
||||
func validateVocabularyTest(test VocabularyTest) bool {
|
||||
if test.UserID <= 0 {
|
||||
return false
|
||||
}
|
||||
validTestTypes := []string{"placement", "progress", "review"}
|
||||
validTestType := false
|
||||
for _, testType := range validTestTypes {
|
||||
if test.TestType == testType {
|
||||
validTestType = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !validTestType {
|
||||
return false
|
||||
}
|
||||
if test.CorrectWords > test.TotalWords {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user