import mongoose, { Document, Schema } from 'mongoose'; // 单词接口 export interface IWord extends Document { word: string; translation: string; pronunciation?: string; example?: string; wordSet: mongoose.Types.ObjectId; createdAt: Date; updatedAt: Date; } // 单词集接口 export interface IWordSet extends Document { name: string; description?: string; owner: mongoose.Types.ObjectId; totalWords: number; createdAt: Date; updatedAt: Date; } // 单词学习记录接口 export interface IWordRecord extends Document { user: mongoose.Types.ObjectId; word: mongoose.Types.ObjectId; mode: 'chinese-to-english' | 'audio-to-english' | 'multiple-choice'; streak: number; totalCorrect: number; totalWrong: number; mastered: boolean; inWrongBook: boolean; lastTestedAt: Date; lastMasteredAt: Date; createdAt: Date; } // 单词测试记录接口 export interface IVocabularyTestRecord extends Document { user: mongoose.Types.ObjectId; wordSet: mongoose.Types.ObjectId; testType: 'chinese-to-english' | 'audio-to-english' | 'multiple-choice'; attempt?: mongoose.Types.ObjectId; stats: { totalWords: number; correctWords: number; accuracy: number; startTime: Date; endTime: Date; duration: number; }; results?: Array<{ word: mongoose.Types.ObjectId; userAnswer: string; correctAnswer: string; isCorrect: boolean; }>; invalidated?: boolean; riskFlags?: string[]; reviewDecision?: 'approved'; reviewedBy?: mongoose.Types.ObjectId; reviewedAt?: Date; reviewNote?: string; createdAt: Date; } export interface IVocabularyTestAttempt extends Document { user: mongoose.Types.ObjectId; wordSet: mongoose.Types.ObjectId; testType: 'chinese-to-english' | 'audio-to-english' | 'multiple-choice'; questionWordIds: mongoose.Types.ObjectId[]; questionTokens: string[]; optionTokens?: string[][]; optionTexts?: string[][]; answeredQuestionTokens?: string[]; answerDurations?: number[]; answers?: Array<{ questionToken: string; word: mongoose.Types.ObjectId; userAnswer: string; correctAnswer: string; isCorrect: boolean; submittedAt: Date; duration: number; riskFlags: string[]; interactionSummary?: { keyCount: number; inputCount: number; pasteCount: number; focusCount: number; blurCount: number; pointerCount: number; firstEventOffset: number; lastEventOffset: number; }; }>; riskFlags?: string[]; reviewDecision?: 'approved'; reviewedBy?: mongoose.Types.ObjectId; reviewedAt?: Date; reviewNote?: string; issuedAt: Date; expiresAt: Date; submittedAt?: Date; status: 'active' | 'submitted' | 'expired'; createdAt: Date; updatedAt: Date; } // 单词模式 const WordSchema = new Schema({ word: { type: String, required: true, trim: true }, translation: { type: String, required: true, trim: true }, pronunciation: { type: String, trim: true }, example: { type: String, trim: true }, wordSet: { type: Schema.Types.ObjectId, ref: 'WordSet', required: true }, }, { timestamps: true }); // 为单词模型添加索引 WordSchema.index({ word: 1, wordSet: 1 }, { unique: true }); WordSchema.index({ translation: 'text', word: 'text' }); // 单词集模式 const WordSetSchema = new Schema({ name: { type: String, required: true, trim: true }, description: { type: String, trim: true }, owner: { type: Schema.Types.ObjectId, ref: 'User', required: true }, totalWords: { type: Number, default: 0 }, }, { timestamps: true }); // 为单词集模型添加索引 WordSetSchema.index({ name: 1, owner: 1 }, { unique: true }); // 简化的嵌套模式统计 const ModeStatsSchema = new Schema({ streak: { type: Number, default: 0 }, totalCorrect: { type: Number, default: 0 }, totalWrong: { type: Number, default: 0 }, mastered: { type: Boolean, default: false }, inWrongBook: { type: Boolean, default: false }, lastTestedAt: Date, lastMasteredAt: Date }, { _id: false }); // 简化后的主记录模式 const WordRecordSchema = new Schema({ user: { type: Schema.Types.ObjectId, ref: 'User', required: true }, word: { type: Schema.Types.ObjectId, ref: 'Word', required: true }, // 各模式的基础统计数据 multipleChoice: { type: ModeStatsSchema, default: () => ({}) }, audioToEnglish: { type: ModeStatsSchema, default: () => ({}) }, chineseToEnglish: { type: ModeStatsSchema, default: () => ({}) }, // 只在顶层保留整体掌握状态 isFullyMastered: { type: Boolean, default: false }, lastFullyMasteredAt: Date, createdAt: { type: Date, default: Date.now } }); // 单词测试记录模式 const VocabularyTestRecordSchema = new Schema({ user: { type: Schema.Types.ObjectId, ref: 'User', required: true }, wordSet: { type: Schema.Types.ObjectId, ref: 'WordSet', required: true }, attempt: { type: Schema.Types.ObjectId, ref: 'VocabularyTestAttempt' }, testType: { type: String, enum: ['chinese-to-english', 'audio-to-english', 'multiple-choice'], required: true }, stats: { totalWords: { type: Number, required: true }, correctWords: { type: Number, required: true }, accuracy: { type: Number, required: true }, startTime: { type: Date, required: true }, endTime: { type: Date, required: true }, duration: { type: Number, required: true } }, results: [{ word: { type: Schema.Types.ObjectId, ref: 'Word', required: true }, userAnswer: { type: String, default: '' }, correctAnswer: { type: String, required: true }, isCorrect: { type: Boolean, required: true } }], invalidated: { type: Boolean, default: false }, riskFlags: [{ type: String }], reviewDecision: { type: String, enum: ['approved'] }, reviewedBy: { type: Schema.Types.ObjectId, ref: 'User' }, reviewedAt: Date, reviewNote: { type: String, trim: true, maxlength: 500 }, createdAt: { type: Date, default: Date.now } }); // 单词测试会话模式,用于防止直接伪造测试提交 const VocabularyTestAttemptSchema = new Schema({ user: { type: Schema.Types.ObjectId, ref: 'User', required: true }, wordSet: { type: Schema.Types.ObjectId, ref: 'WordSet', required: true }, testType: { type: String, enum: ['chinese-to-english', 'audio-to-english', 'multiple-choice'], required: true }, questionWordIds: [{ type: Schema.Types.ObjectId, ref: 'Word', required: true }], questionTokens: [{ type: String, required: true }], optionTokens: [[{ type: String }]], optionTexts: [[{ type: String }]], answeredQuestionTokens: [{ type: String }], answerDurations: [{ type: Number }], answers: [{ questionToken: { type: String, required: true }, word: { type: Schema.Types.ObjectId, ref: 'Word', required: true }, userAnswer: { type: String, default: '' }, correctAnswer: { type: String, required: true }, isCorrect: { type: Boolean, required: true }, submittedAt: { type: Date, required: true }, duration: { type: Number, required: true }, riskFlags: [{ type: String }], interactionSummary: { keyCount: { type: Number, default: 0 }, inputCount: { type: Number, default: 0 }, pasteCount: { type: Number, default: 0 }, focusCount: { type: Number, default: 0 }, blurCount: { type: Number, default: 0 }, pointerCount: { type: Number, default: 0 }, firstEventOffset: { type: Number, default: 0 }, lastEventOffset: { type: Number, default: 0 } } }], riskFlags: [{ type: String }], reviewDecision: { type: String, enum: ['approved'] }, reviewedBy: { type: Schema.Types.ObjectId, ref: 'User' }, reviewedAt: Date, reviewNote: { type: String, trim: true, maxlength: 500 }, issuedAt: { type: Date, required: true }, expiresAt: { type: Date, required: true }, submittedAt: Date, status: { type: String, enum: ['active', 'submitted', 'expired'], default: 'active', required: true } }, { timestamps: true }); VocabularyTestAttemptSchema.index({ user: 1, status: 1, expiresAt: 1 }); VocabularyTestAttemptSchema.index({ expiresAt: 1 }); // 创建和导出模型 export const Word = mongoose.model('Word', WordSchema); export const WordSet = mongoose.model('WordSet', WordSetSchema); export const WordRecord = mongoose.model('WordRecord', WordRecordSchema); export const VocabularyTestRecord = mongoose.model('VocabularyTestRecord', VocabularyTestRecordSchema); export const VocabularyTestAttempt = mongoose.model('VocabularyTestAttempt', VocabularyTestAttemptSchema); export default { Word, WordSet, WordRecord, VocabularyTestRecord, VocabularyTestAttempt };