Files
adopt-a-street/backend/models/UserBadge.js
William Valentin 0cc3d508e1 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>
2025-11-03 10:30:58 -08:00

217 lines
6.2 KiB
JavaScript

const couchdbService = require("../services/couchdbService");
const {
ValidationError,
NotFoundError,
DatabaseError,
withErrorHandling,
createErrorContext
} = require("../utils/modelErrors");
class UserBadge {
constructor(userBadgeData) {
this.validate(userBadgeData);
Object.assign(this, userBadgeData);
}
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 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 errorContext = createErrorContext('UserBadge', 'find', { filter });
return await withErrorHandling(async () => {
const selector = {
type: "user_badge",
...filter,
};
const result = await couchdbService.find(selector);
return result.docs;
}, errorContext);
}
static async findByUser(userId) {
const errorContext = createErrorContext('UserBadge', 'findByUser', { userId });
return await withErrorHandling(async () => {
const selector = {
type: "user_badge",
user: userId,
};
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 errorContext = createErrorContext('UserBadge', 'findByBadge', { badgeId });
return await withErrorHandling(async () => {
const selector = {
type: "user_badge",
badge: badgeId,
};
const result = await couchdbService.find(selector);
return result.docs;
}, errorContext);
}
static async update(id, updateData) {
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 result = await couchdbService.createDocument(updatedDoc);
return { ...updatedDoc, _rev: result.rev };
}, errorContext);
}
static async delete(id) {
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);
}
await couchdbService.destroy(id, doc._rev);
return true;
}, errorContext);
}
static async findByUserAndBadge(userId, badgeId) {
const errorContext = createErrorContext('UserBadge', 'findByUserAndBadge', { userId, badgeId });
return await withErrorHandling(async () => {
const selector = {
type: "user_badge",
user: userId,
badge: badgeId,
};
const result = await couchdbService.find(selector);
return result.docs[0] || null;
}, errorContext);
}
static async updateProgress(userId, badgeId, progress) {
const errorContext = createErrorContext('UserBadge', 'updateProgress', { 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);
}
}
module.exports = UserBadge;