const mongoose = require('mongoose'); const bcrypt = require('bcryptjs'); // Schema für Kinder-Profile const childSchema = new mongoose.Schema({ name: { type: String, required: true, trim: true, maxlength: 50 }, age: { type: Number, required: true, min: 3, max: 18 }, avatar: { type: String, default: 'default-child.png', enum: [ 'default-child.png', 'superhero-boy.png', 'superhero-girl.png', 'pirate-boy.png', 'pirate-girl.png', 'princess.png', 'knight.png', 'astronaut.png', 'detective.png', 'artist.png' ] }, parent: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, // Authentifizierung für Kinder username: { type: String, required: true, unique: true, trim: true, minlength: 3, maxlength: 20, match: /^[a-zA-Z0-9_]+$/ }, pin: { type: String, required: true, minlength: 4, maxlength: 6, match: /^[0-9]+$/ }, // Für sichere PIN-Speicherung (gehashed) hashedPin: { type: String, required: true }, points: { total: { type: Number, default: 0, min: 0 }, available: { type: Number, default: 0, min: 0 }, spent: { type: Number, default: 0, min: 0 } }, level: { current: { type: Number, default: 1, min: 1 }, title: { type: String, default: 'Nachwuchs-Held' } }, achievements: [{ name: String, description: String, icon: String, earnedAt: { type: Date, default: Date.now } }], preferences: { theme: { type: String, enum: ['colorful', 'nature', 'space', 'ocean', 'forest'], default: 'colorful' }, favoriteColor: { type: String, default: '#4CAF50' }, soundEffects: { type: Boolean, default: true }, animations: { type: Boolean, default: true } }, stats: { tasksCompleted: { type: Number, default: 0 }, tasksCompletedToday: { type: Number, default: 0 }, streak: { current: { type: Number, default: 0 }, longest: { type: Number, default: 0 }, lastCompletionDate: Date } }, isActive: { type: Boolean, default: true }, createdAt: { type: Date, default: Date.now }, lastActivity: { type: Date, default: Date.now } }); // Indizes für bessere Performance childSchema.index({ parent: 1, isActive: 1 }); childSchema.index({ 'points.total': -1 }); childSchema.index({ username: 1 }, { unique: true }); // Methode zum Punkte hinzufügen childSchema.methods.addPoints = function(points) { this.points.total += points; this.points.available += points; this.lastActivity = new Date(); // Level-System aktualisieren this.updateLevel(); return this.save(); }; // PIN-Hashing vor dem Speichern childSchema.pre('save', async function(next) { // Nur hashen wenn PIN geändert wurde if (!this.isModified('pin')) return next(); try { // PIN hashen const salt = await bcrypt.genSalt(10); this.hashedPin = await bcrypt.hash(this.pin, salt); // Klartext-PIN entfernen (nicht in DB speichern) this.pin = undefined; next(); } catch (error) { next(error); } }); // PIN-Verifikation childSchema.methods.verifyPin = async function(pin) { return await bcrypt.compare(pin, this.hashedPin); }; // Sichere Daten für Frontend (ohne hashedPin) childSchema.methods.toSafeObject = function() { const obj = this.toObject(); delete obj.hashedPin; delete obj.pin; return obj; }; // Methode zum Punkte ausgeben childSchema.methods.spendPoints = function(points) { if (this.points.available >= points) { this.points.available -= points; this.points.spent += points; this.lastActivity = new Date(); return this.save(); } throw new Error('Nicht genügend Punkte verfügbar'); }; // Level-System aktualisieren childSchema.methods.updateLevel = function() { const totalPoints = this.points.total; const newLevel = Math.floor(totalPoints / 100) + 1; // Alle 100 Punkte ein Level if (newLevel > this.level.current) { this.level.current = newLevel; this.level.title = this.getLevelTitle(newLevel); } }; // Level-Titel bestimmen childSchema.methods.getLevelTitle = function(level) { const titles = { 1: 'Nachwuchs-Held', 2: 'Aufgaben-Entdecker', 3: 'Fleißiger Helfer', 4: 'Verantwortungs-Champion', 5: 'Familien-Superheld', 6: 'Aufgaben-Meister', 7: 'Pflichten-Profi', 8: 'Haushalts-Held', 9: 'Verantwortungs-Legende', 10: 'Familien-Champion' }; return titles[level] || `Super-Held Level ${level}`; }; // Streak aktualisieren childSchema.methods.updateStreak = function() { const today = new Date(); today.setHours(0, 0, 0, 0); const lastCompletion = this.stats.streak.lastCompletionDate; if (!lastCompletion) { // Erste Aufgabe this.stats.streak.current = 1; this.stats.streak.lastCompletionDate = today; } else { const lastDate = new Date(lastCompletion); lastDate.setHours(0, 0, 0, 0); const daysDiff = (today - lastDate) / (1000 * 60 * 60 * 24); if (daysDiff === 1) { // Aufeinanderfolgende Tage this.stats.streak.current += 1; this.stats.streak.lastCompletionDate = today; } else if (daysDiff > 1) { // Streak unterbrochen this.stats.streak.current = 1; this.stats.streak.lastCompletionDate = today; } // daysDiff === 0 bedeutet heute schon eine Aufgabe erledigt } // Längste Streak aktualisieren if (this.stats.streak.current > this.stats.streak.longest) { this.stats.streak.longest = this.stats.streak.current; } }; module.exports = mongoose.model('Child', childSchema);