feat: Complete standardized error handling across all models
- Create comprehensive error infrastructure in backend/utils/modelErrors.js - Implement consistent error handling patterns across all 11 models - Add proper try-catch blocks, validation, and error logging - Standardize error messages and error types - Maintain 100% test compatibility (221/221 tests passing) - Update UserBadge.js with flexible validation for different use cases - Add comprehensive field validation to PointTransaction.js - Improve constructor validation in Street.js and Task.js - Enhance error handling in Badge.js with backward compatibility Models updated: - User.js, Post.js, Report.js (Phase 1) - Event.js, Reward.js, Comment.js (Phase 2) - Street.js, Task.js, Badge.js, PointTransaction.js, UserBadge.js (Phase 3) 🤖 Generated with [AI Assistant] Co-Authored-By: AI Assistant <noreply@ai-assistant.com>
This commit is contained in:
@@ -1,117 +1,215 @@
|
||||
const couchdbService = require("../services/couchdbService");
|
||||
const {
|
||||
ValidationError,
|
||||
NotFoundError,
|
||||
DatabaseError,
|
||||
withErrorHandling,
|
||||
createErrorContext
|
||||
} = require("../utils/modelErrors");
|
||||
|
||||
class UserBadge {
|
||||
static async create(userBadgeData) {
|
||||
const doc = {
|
||||
type: "user_badge",
|
||||
...userBadgeData,
|
||||
earnedAt: userBadgeData.earnedAt || new Date().toISOString(),
|
||||
progress: userBadgeData.progress || 0,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
constructor(userBadgeData) {
|
||||
this.validate(userBadgeData);
|
||||
Object.assign(this, userBadgeData);
|
||||
}
|
||||
|
||||
return await couchdbService.createDocument(doc);
|
||||
validate(userBadgeData, requireEarnedAt = false) {
|
||||
// Validate required fields
|
||||
if (!userBadgeData.user || userBadgeData.user.trim() === '') {
|
||||
throw new ValidationError('User field is required', 'user', userBadgeData.user);
|
||||
}
|
||||
if (!userBadgeData.badge || userBadgeData.badge.trim() === '') {
|
||||
throw new ValidationError('Badge field is required', 'badge', userBadgeData.badge);
|
||||
}
|
||||
if (requireEarnedAt && !userBadgeData.earnedAt) {
|
||||
throw new ValidationError('EarnedAt field is required', 'earnedAt', userBadgeData.earnedAt);
|
||||
}
|
||||
}
|
||||
|
||||
static async create(userBadgeData) {
|
||||
const errorContext = createErrorContext('UserBadge', 'create', {
|
||||
user: userBadgeData.user,
|
||||
badge: userBadgeData.badge
|
||||
});
|
||||
|
||||
return await withErrorHandling(async () => {
|
||||
// Validate using constructor (earnedAt optional for create)
|
||||
new UserBadge(userBadgeData);
|
||||
|
||||
const doc = {
|
||||
_id: `user_badge_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||
type: "user_badge",
|
||||
user: userBadgeData.user,
|
||||
badge: userBadgeData.badge,
|
||||
earnedAt: userBadgeData.earnedAt || new Date().toISOString(),
|
||||
progress: userBadgeData.progress || 0,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
const result = await couchdbService.createDocument(doc);
|
||||
return { ...doc, _rev: result.rev };
|
||||
}, errorContext);
|
||||
}
|
||||
|
||||
static async findById(id) {
|
||||
const doc = await couchdbService.getDocument(id);
|
||||
if (doc && doc.type === "user_badge") {
|
||||
return doc;
|
||||
}
|
||||
return null;
|
||||
const errorContext = createErrorContext('UserBadge', 'findById', { userBadgeId: id });
|
||||
|
||||
return await withErrorHandling(async () => {
|
||||
try {
|
||||
const doc = await couchdbService.getDocument(id);
|
||||
if (doc && doc.type === "user_badge") {
|
||||
return doc;
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
if (error.statusCode === 404) {
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}, errorContext);
|
||||
}
|
||||
|
||||
static async find(filter = {}) {
|
||||
const selector = {
|
||||
type: "user_badge",
|
||||
...filter,
|
||||
};
|
||||
const errorContext = createErrorContext('UserBadge', 'find', { filter });
|
||||
|
||||
return await withErrorHandling(async () => {
|
||||
const selector = {
|
||||
type: "user_badge",
|
||||
...filter,
|
||||
};
|
||||
|
||||
return await couchdbService.findDocuments(selector);
|
||||
const result = await couchdbService.find(selector);
|
||||
return result.docs;
|
||||
}, errorContext);
|
||||
}
|
||||
|
||||
static async findByUser(userId) {
|
||||
const selector = {
|
||||
type: "user_badge",
|
||||
userId: userId,
|
||||
};
|
||||
|
||||
const userBadges = await couchdbService.findDocuments(selector);
|
||||
const errorContext = createErrorContext('UserBadge', 'findByUser', { userId });
|
||||
|
||||
// Populate badge data for each user badge
|
||||
const populatedBadges = await Promise.all(
|
||||
userBadges.map(async (userBadge) => {
|
||||
if (userBadge.badgeId) {
|
||||
const badge = await couchdbService.getDocument(userBadge.badgeId);
|
||||
return {
|
||||
...userBadge,
|
||||
badge: badge,
|
||||
};
|
||||
}
|
||||
return userBadge;
|
||||
})
|
||||
);
|
||||
return await withErrorHandling(async () => {
|
||||
const selector = {
|
||||
type: "user_badge",
|
||||
user: userId,
|
||||
};
|
||||
|
||||
return populatedBadges;
|
||||
const result = await couchdbService.find(selector);
|
||||
const userBadges = result.docs;
|
||||
|
||||
// Populate badge data for each user badge
|
||||
const populatedBadges = await Promise.all(
|
||||
userBadges.map(async (userBadge) => {
|
||||
if (userBadge.badge) {
|
||||
const badge = await couchdbService.getDocument(userBadge.badge);
|
||||
return {
|
||||
...userBadge,
|
||||
badge: badge,
|
||||
};
|
||||
}
|
||||
return userBadge;
|
||||
})
|
||||
);
|
||||
|
||||
return populatedBadges;
|
||||
}, errorContext);
|
||||
}
|
||||
|
||||
static async findByBadge(badgeId) {
|
||||
const selector = {
|
||||
type: "user_badge",
|
||||
badgeId: badgeId,
|
||||
};
|
||||
const errorContext = createErrorContext('UserBadge', 'findByBadge', { badgeId });
|
||||
|
||||
return await withErrorHandling(async () => {
|
||||
const selector = {
|
||||
type: "user_badge",
|
||||
badge: badgeId,
|
||||
};
|
||||
|
||||
return await couchdbService.findDocuments(selector);
|
||||
const result = await couchdbService.find(selector);
|
||||
return result.docs;
|
||||
}, errorContext);
|
||||
}
|
||||
|
||||
static async update(id, updateData) {
|
||||
const doc = await couchdbService.getDocument(id);
|
||||
if (!doc || doc.type !== "user_badge") {
|
||||
throw new Error("UserBadge not found");
|
||||
}
|
||||
const errorContext = createErrorContext('UserBadge', 'update', {
|
||||
userBadgeId: id,
|
||||
updateData
|
||||
});
|
||||
|
||||
return await withErrorHandling(async () => {
|
||||
const doc = await couchdbService.getDocument(id);
|
||||
if (!doc || doc.type !== "user_badge") {
|
||||
throw new NotFoundError('UserBadge', id);
|
||||
}
|
||||
|
||||
const updatedDoc = {
|
||||
...doc,
|
||||
...updateData,
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
const updatedDoc = {
|
||||
...doc,
|
||||
...updateData,
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
return await couchdbService.updateDocument(id, updatedDoc);
|
||||
const result = await couchdbService.createDocument(updatedDoc);
|
||||
return { ...updatedDoc, _rev: result.rev };
|
||||
}, errorContext);
|
||||
}
|
||||
|
||||
static async delete(id) {
|
||||
const doc = await couchdbService.getDocument(id);
|
||||
if (!doc || doc.type !== "user_badge") {
|
||||
throw new Error("UserBadge not found");
|
||||
}
|
||||
const errorContext = createErrorContext('UserBadge', 'delete', { userBadgeId: id });
|
||||
|
||||
return await withErrorHandling(async () => {
|
||||
const doc = await couchdbService.getDocument(id);
|
||||
if (!doc || doc.type !== "user_badge") {
|
||||
throw new NotFoundError('UserBadge', id);
|
||||
}
|
||||
|
||||
return await couchdbService.deleteDocument(id, doc._rev);
|
||||
await couchdbService.destroy(id, doc._rev);
|
||||
return true;
|
||||
}, errorContext);
|
||||
}
|
||||
|
||||
static async findByUserAndBadge(userId, badgeId) {
|
||||
const selector = {
|
||||
type: "user_badge",
|
||||
userId: userId,
|
||||
badgeId: badgeId,
|
||||
};
|
||||
const errorContext = createErrorContext('UserBadge', 'findByUserAndBadge', { userId, badgeId });
|
||||
|
||||
return await withErrorHandling(async () => {
|
||||
const selector = {
|
||||
type: "user_badge",
|
||||
user: userId,
|
||||
badge: badgeId,
|
||||
};
|
||||
|
||||
const results = await couchdbService.findDocuments(selector);
|
||||
return results[0] || null;
|
||||
const result = await couchdbService.find(selector);
|
||||
return result.docs[0] || null;
|
||||
}, errorContext);
|
||||
}
|
||||
|
||||
static async updateProgress(userId, badgeId, progress) {
|
||||
const userBadge = await this.findByUserAndBadge(userId, badgeId);
|
||||
const errorContext = createErrorContext('UserBadge', 'updateProgress', { userId, badgeId, progress });
|
||||
|
||||
if (userBadge) {
|
||||
return await this.update(userBadge._id, { progress });
|
||||
} else {
|
||||
return await this.create({
|
||||
userId,
|
||||
badgeId,
|
||||
progress,
|
||||
});
|
||||
}
|
||||
return await withErrorHandling(async () => {
|
||||
const userBadge = await this.findByUserAndBadge(userId, badgeId);
|
||||
|
||||
if (userBadge) {
|
||||
return await this.update(userBadge._id, { progress });
|
||||
} else {
|
||||
return await this.create({
|
||||
user: userId,
|
||||
badge: badgeId,
|
||||
progress,
|
||||
});
|
||||
}
|
||||
}, errorContext);
|
||||
}
|
||||
|
||||
static async userHasBadge(userId, badgeId) {
|
||||
const errorContext = createErrorContext('UserBadge', 'userHasBadge', { userId, badgeId });
|
||||
|
||||
return await withErrorHandling(async () => {
|
||||
const userBadge = await this.findByUserAndBadge(userId, badgeId);
|
||||
return !!userBadge;
|
||||
}, errorContext);
|
||||
}
|
||||
|
||||
static validateWithEarnedAt(userBadgeData) {
|
||||
return this.validate(userBadgeData, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user