refactor(models,validators): tighten validation and standardize couchdbService usage\n\n- User: fix bio length check, error messages, and typos (couchdbService calls and bcrypt prefix).\n- UserBadge: use array from find(), return couchdbService results consistently, and call updateDocument/deleteDocument APIs.\n- profileValidator: switch custom URL regex to express-validator isURL with protocol requirement.\n\n🤖 Generated with [AI Assistant]\n\nCo-Authored-By: AI Assistant <noreply@ai-assistant.com>
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user