sanket3280/code-execution
0
1const mongoose = require('mongoose');2const bcrypt = require('bcryptjs');3const jwt = require('jsonwebtoken');4const { config } = require('../config/env.config');5 6const UserSchema = new mongoose.Schema({7 username: {8 type: String,9 required: [true, 'Please provide a username'],10 unique: true,11 trim: true,12 minlength: [3, 'Username must be at least 3 characters long'],13 maxlength: [20, 'Username cannot exceed 20 characters']14 },15 email: {16 type: String,17 required: [true, 'Please provide an email'],18 unique: true,19 match: [20 /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/,21 'Please provide a valid email'22 ]23 },24 name: {25 type: String,26 default: ''27 },28 gender: {29 type: String,30 enum: ['', 'Male', 'Female', 'Other', 'Prefer not to say'],31 default: ''32 },33 birthday: {34 type: String,35 default: ''36 },37 website: {38 type: String,39 default: ''40 },41 password: {42 type: String,43 minlength: [6, 'Password must be at least 6 characters long'],44 select: false // Don't return password in queries45 },46 avatar: {47 type: String,48 default: 'https://gravatar.com/avatar/00000000000000000000000000000000?d=mp&f=y'49 },50 bio: {51 type: String,52 maxlength: [500, 'Bio cannot exceed 500 characters'],53 default: ''54 },55 country: {56 type: String,57 default: ''58 },59 socialLinks: {60 github: String,61 linkedin: String,62 twitter: String63 },64 skills: [{65 type: String66 }],67 workExperience: [{68 company: String,69 position: String,70 duration: String71 }],72 education: [{73 school: String,74 degree: String,75 duration: String76 }],77 privacy: {78 showStats: {79 type: Boolean,80 default: true81 },82 showActivity: {83 type: Boolean,84 default: true85 }86 },87 profileViews: {88 type: Number,89 default: 090 },91 profileViewsLastWeek: {92 type: Number,93 default: 094 },95 reputation: {96 type: Number,97 default: 098 },99 reputationLastWeek: {100 type: Number,101 default: 0102 },103 solutionsPosted: {104 type: Number,105 default: 0106 },107 solutionsPostedLastWeek: {108 type: Number,109 default: 0110 },111 discussionsStarted: {112 type: Number,113 default: 0114 },115 discussionsStartedLastWeek: {116 type: Number,117 default: 0118 },119 badges: [{120 badgeId: String,121 badgeName: String,122 badgeDescription: String,123 badgeIcon: String,124 unlockedAt: {125 type: Date,126 default: Date.now127 }128 }],129 role: {130 type: String,131 enum: ['user'],132 default: 'user'133 },134 googleId: String,135 githubId: String,136 linkedinId: String,137 problemsSolved: {138 type: Number,139 default: 0140 },141 solvedProblems: [{142 type: mongoose.Schema.Types.ObjectId,143 ref: 'Problem'144 }],145 challengesSolved: {146 type: Number,147 default: 0148 },149 solvedChallengeProblems: [{150 challengeId: {151 type: mongoose.Schema.Types.ObjectId,152 ref: 'Challenge'153 },154 problemId: {155 type: mongoose.Schema.Types.ObjectId,156 ref: 'Problem'157 },158 solvedAt: {159 type: Date,160 default: Date.now161 }162 }],163 totalSubmissions: {164 type: Number,165 default: 0166 },167 streak: {168 type: Number,169 default: 0170 },171 status: {172 type: String,173 enum: ['online', 'offline', 'busy', 'away'],174 default: 'offline'175 },176 friends: [{177 user: {178 type: mongoose.Schema.Types.ObjectId,179 ref: 'User'180 },181 status: {182 type: String,183 enum: ['pending', 'accepted', 'rejected'],184 default: 'pending'185 },186 createdAt: {187 type: Date,188 default: Date.now189 }190 }],191 friendRequests: [{192 from: {193 type: mongoose.Schema.Types.ObjectId,194 ref: 'User'195 },196 token: { type: String },197 expiresAt: { type: Date },198 status: {199 type: String,200 enum: ['pending', 'accepted', 'rejected', 'expired'],201 default: 'pending'202 },203 createdAt: {204 type: Date,205 default: Date.now206 }207 }],208 lastActive: {209 type: Date,210 default: Date.now211 },212 inviteToken: {213 type: String,214 default: null215 },216 inviteTokenExpiry: {217 type: Date,218 default: null219 },220 resetPasswordOTP: {221 type: String,222 default: null223 },224 resetPasswordOTPExpiry: {225 type: Date,226 default: null227 },228 otpAttempts: {229 type: Number,230 default: 0231 },232 createdAt: {233 type: Date,234 default: Date.now235 }236});237 238// Encrypt password using bcrypt239UserSchema.pre('save', async function(next) {240 // Only hash the password if it's modified (or new)241 if (!this.isModified('password')) {242 return next();243 }244 245 // Only hash if password exists (for social logins, password might be empty)246 if (this.password) {247 const salt = await bcrypt.genSalt(10);248 this.password = await bcrypt.hash(this.password, salt);249 }250 next();251});252 253// Sign JWT and return254UserSchema.methods.getSignedJwtToken = function() {255 return jwt.sign({ id: this._id }, config.jwt.secret, {256 expiresIn: config.jwt.expire257 });258};259 260// Match user entered password to hashed password in database261UserSchema.methods.matchPassword = async function(enteredPassword) {262 return await bcrypt.compare(enteredPassword, this.password);263};264 265// Indexes for better query performance266UserSchema.index({ email: 1 }, { unique: true });267UserSchema.index({ username: 1 }, { unique: true });268UserSchema.index({ googleId: 1 }, { sparse: true });269UserSchema.index({ githubId: 1 }, { sparse: true });270UserSchema.index({ linkedinId: 1 }, { sparse: true });271UserSchema.index({ 'friends.user': 1 });272UserSchema.index({ 'friendRequests.from': 1 });273UserSchema.index({ lastActive: -1 });274UserSchema.index({ status: 1 });275UserSchema.index({ createdAt: -1 });276 277// CRITICAL: Index for leaderboard queries (most important!)278UserSchema.index({ problemsSolved: -1, totalSubmissions: 1 });279 280// Index for profile views tracking281UserSchema.index({ profileViews: -1 });282 283// Index for badge queries284UserSchema.index({ 'badges.badgeId': 1 });285 286// Compound index for search queries287UserSchema.index({ username: 'text', email: 'text' });288 289module.exports = mongoose.model('User', UserSchema);