const couchdbService = require("../services/couchdbService"); const { ValidationError, NotFoundError, DatabaseError, withErrorHandling, createErrorContext } = require("../utils/modelErrors"); class UserBadge { constructor(userBadgeData) { UserBadge.validate(userBadgeData); Object.assign(this, userBadgeData); } static validate(userBadgeData, requireEarnedAt = false) { 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 () => { 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); const _rev = result && (result._rev || result.rev); return _rev ? { ...doc, _rev } : doc; }, 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 res = await couchdbService.find({ selector }); const docs = Array.isArray(res) ? res : (res && Array.isArray(res.docs) ? res.docs : []); return docs; }, errorContext); } static async findByUser(userId) { const errorContext = createErrorContext('UserBadge', 'findByUser', { userId }); return await withErrorHandling(async () => { const selector = { type: "user_badge", user: userId }; const res = await couchdbService.find({ selector }); const userBadges = Array.isArray(res) ? res : (res && Array.isArray(res.docs) ? res.docs : []); const populatedBadges = await Promise.all( userBadges.map(async (userBadge) => { if (userBadge.badge) { const badge = await couchdbService.getDocument(userBadge.badge); return { ...userBadge, 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 res = await couchdbService.find({ selector }); const docs = Array.isArray(res) ? res : (res && Array.isArray(res.docs) ? res.docs : []); return 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() }; let result; if (typeof couchdbService.updateDocument === 'function') { result = await couchdbService.updateDocument(updatedDoc); } else if (typeof couchdbService.update === 'function') { result = await couchdbService.update(updatedDoc._id, updatedDoc); } else { throw new DatabaseError('Update method not available on couchdbService'); } const _rev = result && (result._rev || result.rev); return _rev ? { ...updatedDoc, _rev } : updatedDoc; }, 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 res = await couchdbService.find({ selector }); const docs = Array.isArray(res) ? res : (res && Array.isArray(res.docs) ? res.docs : []); return docs[0] || null; }, 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.deleteDocument(id, doc._rev); return true; }, 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;