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;