diff --git a/backend/middleware/validators/profileValidator.js b/backend/middleware/validators/profileValidator.js index 790f228..0702059 100644 --- a/backend/middleware/validators/profileValidator.js +++ b/backend/middleware/validators/profileValidator.js @@ -1,6 +1,6 @@ const { body, validationResult } = require("express-validator"); -const URL_REGEX = /^(https?|ftp):\\/\\/[^\\s\\/$.?#].[^\\s]*$/i; +// URL validation handled via express-validator isURL const validateProfile = [ body("bio") @@ -11,22 +11,22 @@ const validateProfile = [ body("website") .optional() .if(body("website").notEmpty()) - .matches(URL_REGEX) + .isURL({ protocols: ["http", "https", "ftp"], require_protocol: true }) .withMessage("Invalid website URL."), body("social.twitter") .optional() .if(body("social.twitter").notEmpty()) - .matches(URL_REGEX) + .isURL({ protocols: ["http", "https", "ftp"], require_protocol: true }) .withMessage("Invalid Twitter URL."), body("social.github") .optional() .if(body("social.github").notEmpty()) - .matches(URL_REGEX) + .isURL({ protocols: ["http", "https", "ftp"], require_protocol: true }) .withMessage("Invalid Github URL."), body("social.linkedin") .optional() .if(body("social.linkedin").notEmpty()) - .matches(URL_REGEX) + .isURL({ protocols: ["http", "https", "ftp"], require_protocol: true }) .withMessage("Invalid LinkedIn URL."), body("privacySettings.profileVisibility") .optional() diff --git a/backend/models/User.js b/backend/models/User.js index 76a39e0..5e9db69 100644 --- a/backend/models/User.js +++ b/backend/models/User.js @@ -27,7 +27,7 @@ class User { this.avatar = data.avatar || null; this.cloudinaryPublicId = data.cloudinaryPublicId || null; this.bio = data.bio || ""; - if (this.bio.length > 510) { throw new ValidationError("Bio cannot exceed 500 characters.", "bio", this.bio); } + if (this.bio.length > 500) { throw new ValidationError("Bio cannot exceed 500 characters.", "bio", this.bio); } this.location = data.location || ""; this.website = data.website || ""; if (this.website && !URL_REGEX.test(this.website)) { throw new ValidationError("Invalid website URL.", "website", this.website); } @@ -40,9 +40,9 @@ class User { // --- Settings & Preferences --- this.privacySettings = data.privacySettings || { profileVisibility: "public" }; - if (!["public", "private"].includes(this.privacySettings.profileVisibility)) { throw new ValidationError("Profile visibility must be 'public' or 'private.", "privacySettings.profileVisibility", this.privacySettings.profileVisibility); } + if (!["public", "private"].includes(this.privacySettings.profileVisibility)) { throw new ValidationError("Profile visibility must be 'public' or 'private'.", "privacySettings.profileVisibility", this.privacySettings.profileVisibility); } this.preferences = data.preferences || { emailNotifications: true, pushNotifications: true, theme: "light" }; - if (!["light", "dark"].includes(this.preferences.theme)) { throw new ValidationError("Theme must be light' or 'dark'.", "preferences.theme", this.preferences.theme); } + if (!["light", "dark"].includes(this.preferences.theme)) { throw new ValidationError("Theme must be 'light' or 'dark'.", "preferences.theme", this.preferences.theme); } // --- Gamification & App Data --- @@ -73,9 +73,9 @@ class User { return await withErrorHandling(async () => { let user; if (query.email) { user = await couchdbService.findUserByEmail(query.email); } - else if (query._id) { user = await couchdeService.findUserById(query._id); } + else if (query._id) { user = await couchdbService.findUserById(query._id); } else { // Generic query fallback - const docs = await couchdeService.find({ + const docs = await couchdbService.find({ selector: { type: "user", ...query }, limit: 1 }); @@ -89,7 +89,7 @@ class User { const errorContext = createErrorContext('User', 'findById', { id }); return await withErrorHandling(async () => { - const user = await couchdeService.findUserById(id); + const user = await couchdbService.findUserById(id); return user ? new User(user) : null; }, errorContext); } @@ -98,11 +98,11 @@ class User { const errorContext = createErrorContext('User', 'findByIdAndUpdate', { id, update, options }); return await withErrorHandling(async () => { - const user = await couchdeService.findUserById(id); + const user = await couchdbService.findUserById(id); if (!user) return null; const updatedUser = { ...user, ...update, updatedAt: new Date().toISOString() }; - const saved = await couchdeService.update(id, updatedUser); + const saved = await couchdbService.update(id, updatedUser); if (options.new) { return new User(saved); } return new User(user); @@ -113,7 +113,7 @@ class User { const errorContext = createErrorContext('User', 'findByIdAndDelete', { id }); return await withErrorHandling(async () => { - const user = await couchdeService.findUserById(id); + const user = await couchdbService.findUserById(id); if (!user) return null; await couchdbService.delete(id); @@ -157,7 +157,7 @@ class User { this.updatedAt = new Date().toISOString(); if (!this._id) { this._id = `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; - if (this.password && !this.password.startsWith('$2)')) { + if (this.password && !this.password.startsWith('$2')) { const salt = await bcrypt.genSalt(10); this.password = await bcrypt.hash(this.password, salt); } @@ -165,7 +165,7 @@ class User { this._rev = created._rev; return this; } else { - const updated = await couchdeService.updateDocument(this.toJSON()); + const updated = await couchdbService.updateDocument(this.toJSON()); this._rev = updated._rev; return this; } diff --git a/backend/models/UserBadge.js b/backend/models/UserBadge.js index e94421f..1b08bd3 100644 --- a/backend/models/UserBadge.js +++ b/backend/models/UserBadge.js @@ -48,7 +48,7 @@ class UserBadge { }; const result = await couchdbService.createDocument(doc); - return { ...doc, _rev: result.rev }; + return result; }, errorContext); } @@ -80,8 +80,8 @@ class UserBadge { ...filter, }; - const result = await couchdbService.find(selector); - return result.docs; + const docs = await couchdbService.find({ selector }); + return docs; }, errorContext); } @@ -94,8 +94,7 @@ class UserBadge { user: userId, }; - const result = await couchdbService.find(selector); - const userBadges = result.docs; + const userBadges = await couchdbService.find({ selector }); // Populate badge data for each user badge const populatedBadges = await Promise.all( @@ -124,8 +123,8 @@ class UserBadge { badge: badgeId, }; - const result = await couchdbService.find(selector); - return result.docs; + const docs = await couchdbService.find({ selector }); + return docs; }, errorContext); } @@ -147,8 +146,8 @@ class UserBadge { updatedAt: new Date().toISOString(), }; - const result = await couchdbService.createDocument(updatedDoc); - return { ...updatedDoc, _rev: result.rev }; + const result = await couchdbService.updateDocument(updatedDoc); + return result; }, errorContext); } @@ -161,7 +160,7 @@ class UserBadge { throw new NotFoundError('UserBadge', id); } - await couchdbService.destroy(id, doc._rev); + await couchdbService.deleteDocument(id, doc._rev); return true; }, errorContext); } @@ -176,8 +175,8 @@ class UserBadge { badge: badgeId, }; - const result = await couchdbService.find(selector); - return result.docs[0] || null; + const docs = await couchdbService.find({ selector }); + return docs[0] || null; }, errorContext); }