diff --git a/backend/__tests__/models/Reward.test.js b/backend/__tests__/models/Reward.test.js index e95a0c8..6e62970 100644 --- a/backend/__tests__/models/Reward.test.js +++ b/backend/__tests__/models/Reward.test.js @@ -1,36 +1,20 @@ -// Mock CouchDB service for testing -const mockCouchdbService = { - create: jest.fn(), - getById: jest.fn(), - update: jest.fn(), - delete: jest.fn(), - find: jest.fn(), - findUserById: jest.fn(), - updateUserPoints: jest.fn(), - bulkDocs: jest.fn(), - initialize: jest.fn().mockResolvedValue(true), - isReady: jest.fn().mockReturnValue(true), - isConnected: true, - isConnecting: false, - shutdown: jest.fn().mockResolvedValue(true), -}; - -// Mock the service module -jest.mock('../../services/couchdbService', () => mockCouchdbService); - -const couchdbService = require('../../services/couchdbService'); const Reward = require('../../models/Reward'); describe('Reward Model', () => { beforeEach(() => { - mockCouchdbService.create.mockReset(); - mockCouchdbService.getById.mockReset(); - mockCouchdbService.update.mockReset(); - mockCouchdbService.delete.mockReset(); - mockCouchdbService.find.mockReset(); - mockCouchdbService.findUserById.mockReset(); - mockCouchdbService.updateUserPoints.mockReset(); - mockCouchdbService.bulkDocs.mockReset(); + jest.clearAllMocks(); + // Reset all mocks to ensure clean state + global.mockCouchdbService.createDocument.mockReset(); + global.mockCouchdbService.updateDocument.mockReset(); + global.mockCouchdbService.deleteDocument.mockReset(); + global.mockCouchdbService.getById.mockReset(); + global.mockCouchdbService.find.mockReset(); + global.mockCouchdbService.findUserById.mockReset(); + global.mockCouchdbService.updateUserPoints.mockReset(); + global.mockCouchdbService.bulkDocs.mockReset(); + global.mockCouchdbService.create.mockReset(); + global.mockCouchdbService.update.mockReset(); + global.mockCouchdbService.delete.mockReset(); }); describe('Schema Validation', () => { @@ -53,7 +37,7 @@ describe('Reward Model', () => { updatedAt: '2023-01-01T00:00:00.000Z' }; - mockCouchdbService.create.mockResolvedValue(mockCreated); + global.mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const reward = await Reward.create(rewardData); @@ -81,7 +65,7 @@ describe('Reward Model', () => { updatedAt: '2023-01-01T00:00:00.000Z' }; - mockCouchdbService.create.mockResolvedValue(mockCreated); + global.mockCouchdbService.createDocument.mockResolvedValue(mockCreated); // The Reward model doesn't validate, so we test the behavior const reward = await Reward.create(rewardData); @@ -104,7 +88,7 @@ describe('Reward Model', () => { updatedAt: '2023-01-01T00:00:00.000Z' }; - mockCouchdbService.create.mockResolvedValue(mockCreated); + global.mockCouchdbService.createDocument.mockResolvedValue(mockCreated); // The Reward model doesn't validate, so we test the behavior const reward = await Reward.create(rewardData); @@ -127,7 +111,7 @@ describe('Reward Model', () => { updatedAt: '2023-01-01T00:00:00.000Z' }; - mockCouchdbService.create.mockResolvedValue(mockCreated); + global.mockCouchdbService.createDocument.mockResolvedValue(mockCreated); // The Reward model doesn't validate, so we test the behavior const reward = await Reward.create(rewardData); @@ -151,7 +135,7 @@ describe('Reward Model', () => { updatedAt: '2023-01-01T00:00:00.000Z' }; - mockCouchdbService.create.mockResolvedValue(mockCreated); + global.mockCouchdbService.createDocument.mockResolvedValue(mockCreated); // The Reward model doesn't validate, so we test the behavior const reward = await Reward.create(rewardData); @@ -178,7 +162,7 @@ describe('Reward Model', () => { updatedAt: '2023-01-01T00:00:00.000Z' }; - mockCouchdbService.create.mockResolvedValue(mockCreated); + global.mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const reward = await Reward.create(rewardData); @@ -203,7 +187,7 @@ describe('Reward Model', () => { updatedAt: '2023-01-01T00:00:00.000Z' }; - mockCouchdbService.create.mockResolvedValue(mockCreated); + global.mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const reward = await Reward.create(rewardData); @@ -235,7 +219,7 @@ describe('Reward Model', () => { updatedAt: '2023-01-01T00:00:00.000Z' }; - mockCouchdbService.create.mockResolvedValue(mockCreated); + global.mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const reward = await Reward.create(rewardData); @@ -266,7 +250,7 @@ describe('Reward Model', () => { updatedAt: '2023-01-01T00:00:00.000Z' }; - mockCouchdbService.create.mockResolvedValue(mockCreated); + global.mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const reward = await Reward.create(rewardData); @@ -291,7 +275,7 @@ describe('Reward Model', () => { updatedAt: '2023-01-01T00:00:00.000Z' }; - mockCouchdbService.create.mockResolvedValue(mockCreated); + global.mockCouchdbService.createDocument.mockResolvedValue(mockCreated); // The Reward model doesn't validate, so we test behavior const reward = await Reward.create(rewardData); @@ -318,7 +302,7 @@ describe('Reward Model', () => { updatedAt: '2023-01-01T00:00:00.000Z' }; - mockCouchdbService.create.mockResolvedValue(mockCreated); + global.mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const reward = await Reward.create(rewardData); @@ -343,8 +327,8 @@ describe('Reward Model', () => { updatedAt: '2023-01-01T00:00:00.000Z' }; - mockCouchdbService.getById.mockResolvedValue(mockReward); - mockCouchdbService.update.mockResolvedValue({ + global.mockCouchdbService.getById.mockResolvedValue(mockReward); + global.mockCouchdbService.updateDocument.mockResolvedValue({ ...mockReward, isActive: false, _rev: '2-def' @@ -386,7 +370,7 @@ describe('Reward Model', () => { updatedAt: '2023-01-01T00:00:00.000Z' }; - mockCouchdbService.create.mockResolvedValue(mockCreated); + global.mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const reward = await Reward.create(rewardData); @@ -413,8 +397,8 @@ describe('Reward Model', () => { updatedAt: '2023-01-01T00:00:00.000Z' }; - mockCouchdbService.getById.mockResolvedValue(mockReward); - mockCouchdbService.update.mockResolvedValue({ + global.mockCouchdbService.getById.mockResolvedValue(mockReward); + global.mockCouchdbService.updateDocument.mockResolvedValue({ ...mockReward, redeemedBy: [ { @@ -460,7 +444,7 @@ describe('Reward Model', () => { updatedAt: '2023-01-01T00:00:00.000Z' }; - mockCouchdbService.create.mockResolvedValue(mockCreated); + global.mockCouchdbService.createDocument.mockResolvedValue(mockCreated); const reward = await Reward.create(rewardData); @@ -488,8 +472,8 @@ describe('Reward Model', () => { updatedAt: '2023-01-01T00:00:00.000Z' }; - mockCouchdbService.getById.mockResolvedValue(mockReward); - mockCouchdbService.update.mockResolvedValue({ + global.mockCouchdbService.getById.mockResolvedValue(mockReward); + global.mockCouchdbService.updateDocument.mockResolvedValue({ ...mockReward, isActive: false, _rev: '2-def', @@ -518,7 +502,7 @@ describe('Reward Model', () => { updatedAt: '2023-01-01T00:00:00.000Z' }; - mockCouchdbService.getById.mockResolvedValue(mockReward); + global.mockCouchdbService.getById.mockResolvedValue(mockReward); const reward = await Reward.findById('reward_123'); expect(reward).toBeDefined(); @@ -527,7 +511,7 @@ describe('Reward Model', () => { }); it('should return null when reward not found', async () => { - mockCouchdbService.getById.mockResolvedValue(null); + global.mockCouchdbService.getById.mockResolvedValue(null); const reward = await Reward.findById('nonexistent'); expect(reward).toBeNull(); diff --git a/backend/models/Reward.js b/backend/models/Reward.js index d0704d1..7f5e934 100644 --- a/backend/models/Reward.js +++ b/backend/models/Reward.js @@ -1,272 +1,451 @@ const couchdbService = require("../services/couchdbService"); +const { + ValidationError, + NotFoundError, + DatabaseError, + withErrorHandling, + createErrorContext +} = require("../utils/modelErrors"); class Reward { - static async create(rewardData) { - const reward = { - _id: `reward_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, - type: "reward", - name: rewardData.name, - description: rewardData.description, - cost: rewardData.cost, - isPremium: rewardData.isPremium || false, - isActive: rewardData.isActive !== undefined ? rewardData.isActive : true, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString() - }; + constructor(data) { + // Handle both new documents and database documents + const isNew = !data._id; + + // For new documents, validate required fields + if (isNew) { + if (!data.name || data.name.trim() === '') { + throw new ValidationError('Name is required', 'name', data.name); + } + if (!data.description || data.description.trim() === '') { + throw new ValidationError('Description is required', 'description', data.description); + } + if (data.cost === undefined || data.cost === null || isNaN(data.cost)) { + throw new ValidationError('Cost is required and must be a number', 'cost', data.cost); + } + if (data.cost < 0) { + throw new ValidationError('Cost must be non-negative', 'cost', data.cost); + } + } - return await couchdbService.create(reward); + // Assign properties + this._id = data._id || null; + this._rev = data._rev || null; + this.type = data.type || "reward"; + this.name = data.name; + this.description = data.description; + this.cost = Number(data.cost); + this.category = data.category || null; + this.isPremium = Boolean(data.isPremium); + this.isActive = data.isActive !== undefined ? Boolean(data.isActive) : true; + this.redeemedBy = data.redeemedBy || []; + this.createdAt = data.createdAt || new Date().toISOString(); + this.updatedAt = data.updatedAt || new Date().toISOString(); + } + + toJSON() { + return { + _id: this._id, + _rev: this._rev, + type: this.type, + name: this.name, + description: this.description, + cost: this.cost, + category: this.category, + isPremium: this.isPremium, + isActive: this.isActive, + redeemedBy: this.redeemedBy, + createdAt: this.createdAt, + updatedAt: this.updatedAt + }; + } + + async save() { + const errorContext = createErrorContext('Reward', 'save', { + rewardId: this._id, + name: this.name + }); + + return await withErrorHandling(async () => { + if (this._id) { + // Update existing document + const updatedDoc = await couchdbService.updateDocument(this._id, this.toJSON()); + Object.assign(this, updatedDoc); + return this; + } else { + // Create new document + this._id = `reward_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; + const createdDoc = await couchdbService.createDocument(this.toJSON()); + Object.assign(this, createdDoc); + return this; + } + }, errorContext); + } + + static async create(rewardData) { + const errorContext = createErrorContext('Reward', 'create', { + name: rewardData?.name, + cost: rewardData?.cost + }); + + return await withErrorHandling(async () => { + const reward = new Reward(rewardData); + reward._id = `reward_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; + + const createdReward = await couchdbService.createDocument(reward.toJSON()); + return new Reward(createdReward); + }, errorContext); } static async findById(rewardId) { - return await couchdbService.getById(rewardId); + const errorContext = createErrorContext('Reward', 'findById', { rewardId }); + + return await withErrorHandling(async () => { + const doc = await couchdbService.getById(rewardId); + if (doc && doc.type === "reward") { + return new Reward(doc); + } + return null; + }, errorContext); } static async find(query = {}, options = {}) { - const defaultQuery = { - type: "reward", - ...query - }; + const errorContext = createErrorContext('Reward', 'find', { query, options }); + + return await withErrorHandling(async () => { + const defaultQuery = { + type: "reward", + ...query + }; - return await couchdbService.find({ - selector: defaultQuery, - ...options - }); + const docs = await couchdbService.find({ + selector: defaultQuery, + ...options + }); + + return docs.map(doc => new Reward(doc)); + }, errorContext); } static async findOne(query) { - const rewards = await this.find(query, { limit: 1 }); - return rewards[0] || null; + const errorContext = createErrorContext('Reward', 'findOne', { query }); + + return await withErrorHandling(async () => { + const rewards = await this.find(query, { limit: 1 }); + return rewards[0] || null; + }, errorContext); } static async update(rewardId, updateData) { - const reward = await this.findById(rewardId); - if (!reward) { - throw new Error("Reward not found"); - } + const errorContext = createErrorContext('Reward', 'update', { rewardId, updateData }); + + return await withErrorHandling(async () => { + const existingReward = await this.findById(rewardId); + if (!existingReward) { + throw new NotFoundError('Reward', rewardId); + } - const updatedReward = { - ...reward, - ...updateData, - updatedAt: new Date().toISOString() - }; - - return await couchdbService.update(rewardId, updatedReward); + // Update fields + Object.keys(updateData).forEach(key => { + if (key !== '_id' && key !== '_rev' && key !== 'type') { + existingReward[key] = updateData[key]; + } + }); + + existingReward.updatedAt = new Date().toISOString(); + + const updatedDoc = await couchdbService.updateDocument(rewardId, existingReward.toJSON()); + return new Reward(updatedDoc); + }, errorContext); } static async delete(rewardId) { - return await couchdbService.delete(rewardId); + const errorContext = createErrorContext('Reward', 'delete', { rewardId }); + + return await withErrorHandling(async () => { + const reward = await this.findById(rewardId); + if (!reward) { + throw new NotFoundError('Reward', rewardId); + } + + return await couchdbService.deleteDocument(rewardId); + }, errorContext); } static async findByCostRange(minCost, maxCost) { - return await couchdbService.find({ - selector: { - type: "reward", - cost: { $gte: minCost, $lte: maxCost } - }, - sort: [{ cost: "asc" }] - }); + const errorContext = createErrorContext('Reward', 'findByCostRange', { minCost, maxCost }); + + return await withErrorHandling(async () => { + const docs = await couchdbService.find({ + selector: { + type: "reward", + cost: { $gte: minCost, $lte: maxCost } + }, + sort: [{ cost: "asc" }] + }); + + return docs.map(doc => new Reward(doc)); + }, errorContext); } static async findByPremiumStatus(isPremium) { - return await this.find({ isPremium }); + const errorContext = createErrorContext('Reward', 'findByPremiumStatus', { isPremium }); + + return await withErrorHandling(async () => { + return await this.find({ isPremium }); + }, errorContext); } static async getActiveRewards() { - return await this.find({ isActive: true }); + const errorContext = createErrorContext('Reward', 'getActiveRewards', {}); + + return await withErrorHandling(async () => { + return await this.find({ isActive: true }); + }, errorContext); } static async getPremiumRewards() { - return await this.find({ isPremium: true, isActive: true }); + const errorContext = createErrorContext('Reward', 'getPremiumRewards', {}); + + return await withErrorHandling(async () => { + return await this.find({ isPremium: true, isActive: true }); + }, errorContext); } static async getRegularRewards() { - return await this.find({ isPremium: false, isActive: true }); + const errorContext = createErrorContext('Reward', 'getRegularRewards', {}); + + return await withErrorHandling(async () => { + return await this.find({ isPremium: false, isActive: true }); + }, errorContext); } static async getAllPaginated(page = 1, limit = 10) { - const skip = (page - 1) * limit; + const errorContext = createErrorContext('Reward', 'getAllPaginated', { page, limit }); - const rewards = await couchdbService.find({ - selector: { type: "reward" }, - sort: [{ cost: "asc" }], - skip, - limit - }); + return await withErrorHandling(async () => { + const skip = (page - 1) * limit; + + const rewards = await couchdbService.find({ + selector: { type: "reward" }, + sort: [{ cost: "asc" }], + skip, + limit + }); - // Get total count - const totalCount = await couchdbService.find({ - selector: { type: "reward" }, - fields: ["_id"] - }); + // Get total count + const totalCount = await couchdbService.find({ + selector: { type: "reward" }, + fields: ["_id"] + }); - return { - rewards, - pagination: { - page, - limit, - totalCount: totalCount.length, - totalPages: Math.ceil(totalCount.length / limit) - } - }; + return { + rewards: rewards.map(doc => new Reward(doc)), + pagination: { + page, + limit, + totalCount: totalCount.length, + totalPages: Math.ceil(totalCount.length / limit) + } + }; + }, errorContext); } static async redeemReward(userId, rewardId) { - const reward = await this.findById(rewardId); - if (!reward) { - throw new Error("Reward not found"); - } - - if (!reward.isActive) { - throw new Error("Reward is not available"); - } - - const user = await couchdbService.findUserById(userId); - if (!user) { - throw new Error("User not found"); - } - - if (user.points < reward.cost) { - throw new Error("Not enough points"); - } - - if (reward.isPremium && !user.isPremium) { - throw new Error("Premium reward not available"); - } - - // Deduct points using couchdbService method - const updatedUser = await couchdbService.updateUserPoints( - userId, - -reward.cost, - `Redeemed reward: ${reward.name}`, - { - entityType: 'Reward', - entityId: rewardId, - entityName: reward.name + const errorContext = createErrorContext('Reward', 'redeemReward', { userId, rewardId }); + + return await withErrorHandling(async () => { + const reward = await this.findById(rewardId); + if (!reward) { + throw new NotFoundError('Reward', rewardId); } - ); - // Create redemption record - const redemption = { - _id: `redemption_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, - type: "reward_redemption", - user: { - userId: userId, - name: user.name - }, - reward: { - rewardId: rewardId, - name: reward.name, - description: reward.description, - cost: reward.cost - }, - pointsDeducted: reward.cost, - balanceAfter: updatedUser.points, - redeemedAt: new Date().toISOString() - }; + if (!reward.isActive) { + throw new ValidationError('Reward is not available', 'isActive', reward.isActive); + } - await couchdbService.create(redemption); + const user = await couchdbService.findUserById(userId); + if (!user) { + throw new NotFoundError('User', userId); + } - return { - redemption, - pointsDeducted: reward.cost, - newBalance: updatedUser.points - }; + if (user.points < reward.cost) { + throw new ValidationError('Not enough points to redeem this reward', 'points', user.points); + } + + if (reward.isPremium && !user.isPremium) { + throw new ValidationError('Premium reward not available for non-premium users', 'isPremium', user.isPremium); + } + + // Deduct points using couchdbService method + const updatedUser = await couchdbService.updateUserPoints( + userId, + -reward.cost, + `Redeemed reward: ${reward.name}`, + { + entityType: 'Reward', + entityId: rewardId, + entityName: reward.name + } + ); + + // Create redemption record + const redemption = { + _id: `redemption_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, + type: "reward_redemption", + user: { + userId: userId, + name: user.name + }, + reward: { + rewardId: rewardId, + name: reward.name, + description: reward.description, + cost: reward.cost + }, + pointsDeducted: reward.cost, + balanceAfter: updatedUser.points, + redeemedAt: new Date().toISOString() + }; + + await couchdbService.create(redemption); + + return { + redemption, + pointsDeducted: reward.cost, + newBalance: updatedUser.points + }; + }, errorContext); } static async getUserRedemptions(userId, limit = 20) { - return await couchdbService.find({ - selector: { - type: "reward_redemption", - "user.userId": userId - }, - sort: [{ redeemedAt: "desc" }], - limit - }); + const errorContext = createErrorContext('Reward', 'getUserRedemptions', { userId, limit }); + + return await withErrorHandling(async () => { + return await couchdbService.find({ + selector: { + type: "reward_redemption", + "user.userId": userId + }, + sort: [{ redeemedAt: "desc" }], + limit + }); + }, errorContext); } static async getRewardStats(rewardId) { - const redemptions = await couchdbService.find({ - selector: { - type: "reward_redemption", - "reward.rewardId": rewardId - } - }); + const errorContext = createErrorContext('Reward', 'getRewardStats', { rewardId }); + + return await withErrorHandling(async () => { + const redemptions = await couchdbService.find({ + selector: { + type: "reward_redemption", + "reward.rewardId": rewardId + } + }); - return { - totalRedemptions: redemptions.length, - totalPointsSpent: redemptions.reduce((sum, r) => sum + r.pointsDeducted, 0), - lastRedeemed: redemptions.length > 0 ? redemptions[0].redeemedAt : null - }; + return { + totalRedemptions: redemptions.length, + totalPointsSpent: redemptions.reduce((sum, r) => sum + r.pointsDeducted, 0), + lastRedeemed: redemptions.length > 0 ? redemptions[0].redeemedAt : null + }; + }, errorContext); } static async getCatalogStats() { - const rewards = await this.getActiveRewards(); - const premium = await this.getPremiumRewards(); - const regular = await this.getRegularRewards(); + const errorContext = createErrorContext('Reward', 'getCatalogStats', {}); + + return await withErrorHandling(async () => { + const rewards = await this.getActiveRewards(); + const premium = await this.getPremiumRewards(); + const regular = await this.getRegularRewards(); - return { - totalRewards: rewards.length, - premiumRewards: premium.length, - regularRewards: regular.length, - averageCost: rewards.reduce((sum, r) => sum + r.cost, 0) / rewards.length || 0, - minCost: Math.min(...rewards.map(r => r.cost)), - maxCost: Math.max(...rewards.map(r => r.cost)) - }; + return { + totalRewards: rewards.length, + premiumRewards: premium.length, + regularRewards: regular.length, + averageCost: rewards.reduce((sum, r) => sum + r.cost, 0) / rewards.length || 0, + minCost: rewards.length > 0 ? Math.min(...rewards.map(r => r.cost)) : 0, + maxCost: rewards.length > 0 ? Math.max(...rewards.map(r => r.cost)) : 0 + }; + }, errorContext); } static async searchRewards(searchTerm, options = {}) { - const query = { - selector: { - type: "reward", - isActive: true, - $or: [ - { name: { $regex: searchTerm, $options: "i" } }, - { description: { $regex: searchTerm, $options: "i" } } - ] - }, - ...options - }; + const errorContext = createErrorContext('Reward', 'searchRewards', { searchTerm, options }); + + return await withErrorHandling(async () => { + const query = { + selector: { + type: "reward", + isActive: true, + $or: [ + { name: { $regex: searchTerm, $options: "i" } }, + { description: { $regex: searchTerm, $options: "i" } } + ] + }, + ...options + }; - return await couchdbService.find(query); + const docs = await couchdbService.find(query); + return docs.map(doc => new Reward(doc)); + }, errorContext); } // Migration helper static async migrateFromMongo(mongoReward) { - const rewardData = { - name: mongoReward.name, - description: mongoReward.description, - cost: mongoReward.cost, - isPremium: mongoReward.isPremium || false, - isActive: true - }; + const errorContext = createErrorContext('Reward', 'migrateFromMongo', { + mongoRewardId: mongoReward._id + }); + + return await withErrorHandling(async () => { + const rewardData = { + name: mongoReward.name, + description: mongoReward.description, + cost: mongoReward.cost, + isPremium: mongoReward.isPremium || false, + isActive: true + }; - return await this.create(rewardData); + return await this.create(rewardData); + }, errorContext); } // Bulk operations for admin static async bulkCreate(rewardsData) { - const rewards = rewardsData.map(data => ({ - _id: `reward_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, - type: "reward", - name: data.name, - description: data.description, - cost: data.cost, - isPremium: data.isPremium || false, - isActive: data.isActive !== undefined ? data.isActive : true, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString() - })); + const errorContext = createErrorContext('Reward', 'bulkCreate', { + count: rewardsData.length + }); + + return await withErrorHandling(async () => { + const rewards = rewardsData.map(data => ({ + _id: `reward_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, + type: "reward", + name: data.name, + description: data.description, + cost: data.cost, + isPremium: data.isPremium || false, + isActive: data.isActive !== undefined ? data.isActive : true, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() + })); - return await couchdbService.bulkDocs(rewards); + const docs = await couchdbService.bulkDocs(rewards); + return docs.map(doc => new Reward(doc)); + }, errorContext); } static async toggleActiveStatus(rewardId) { - const reward = await this.findById(rewardId); - if (!reward) { - throw new Error("Reward not found"); - } + const errorContext = createErrorContext('Reward', 'toggleActiveStatus', { rewardId }); + + return await withErrorHandling(async () => { + const reward = await this.findById(rewardId); + if (!reward) { + throw new NotFoundError('Reward', rewardId); + } - return await this.update(rewardId, { isActive: !reward.isActive }); + return await this.update(rewardId, { isActive: !reward.isActive }); + }, errorContext); } }