fixed fake post attack

This commit is contained in:
2026-05-21 16:44:53 +08:00
parent dec29186e0
commit 7f44194416
4 changed files with 805 additions and 333 deletions

View File

@@ -41,6 +41,7 @@ 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;
@@ -49,9 +50,29 @@ export interface IVocabularyTestRecord extends Document {
endTime: Date;
duration: number;
};
results?: Array<{
word: mongoose.Types.ObjectId;
userAnswer: string;
correctAnswer: string;
isCorrect: boolean;
}>;
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[];
issuedAt: Date;
expiresAt: Date;
submittedAt?: Date;
status: 'active' | 'submitted' | 'expired';
createdAt: Date;
updatedAt: Date;
}
// 单词模式
const WordSchema = new Schema<IWord>({
word: { type: String, required: true, trim: true },
@@ -83,7 +104,8 @@ const ModeStatsSchema = new Schema({
totalWrong: { type: Number, default: 0 },
mastered: { type: Boolean, default: false },
inWrongBook: { type: Boolean, default: false },
lastTestedAt: Date
lastTestedAt: Date,
lastMasteredAt: Date
}, { _id: false });
// 简化后的主记录模式
@@ -107,6 +129,7 @@ const WordRecordSchema = new Schema({
const VocabularyTestRecordSchema = new Schema<IVocabularyTestRecord>({
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'],
@@ -120,18 +143,51 @@ const VocabularyTestRecordSchema = new Schema<IVocabularyTestRecord>({
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 }
}],
createdAt: { type: Date, default: Date.now }
});
// 单词测试会话模式,用于防止直接伪造测试提交
const VocabularyTestAttemptSchema = new Schema<IVocabularyTestAttempt>({
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 }],
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<IWord>('Word', WordSchema);
export const WordSet = mongoose.model<IWordSet>('WordSet', WordSetSchema);
export const WordRecord = mongoose.model('WordRecord', WordRecordSchema);
export const VocabularyTestRecord = mongoose.model<IVocabularyTestRecord>('VocabularyTestRecord', VocabularyTestRecordSchema);
export const VocabularyTestAttempt = mongoose.model<IVocabularyTestAttempt>('VocabularyTestAttempt', VocabularyTestAttemptSchema);
export default {
Word,
WordSet,
WordRecord,
VocabularyTestRecord
};
VocabularyTestRecord,
VocabularyTestAttempt
};