- Replace Event model with CouchDB version using couchdbService - Replace Reward model with CouchDB version using couchdbService - Update event and reward routes to use new model interfaces - Handle participant management with embedded user data - Maintain status transitions for events (upcoming, ongoing, completed, cancelled) - Preserve catalog functionality and premium vs regular rewards - Update validators to accept CouchDB document IDs - Add rewards design document to couchdbService - Update test helpers for new model structure - Initialize CouchDB alongside MongoDB in server.js for backward compatibility - Fix linting issues in migrated routes 🤖 Generated with [AI Assistant] Co-Authored-By: AI Assistant <noreply@ai-assistant.com>
273 lines
6.8 KiB
JavaScript
273 lines
6.8 KiB
JavaScript
const couchdbService = require("../services/couchdbService");
|
|
|
|
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()
|
|
};
|
|
|
|
return await couchdbService.create(reward);
|
|
}
|
|
|
|
static async findById(rewardId) {
|
|
return await couchdbService.getById(rewardId);
|
|
}
|
|
|
|
static async find(query = {}, options = {}) {
|
|
const defaultQuery = {
|
|
type: "reward",
|
|
...query
|
|
};
|
|
|
|
return await couchdbService.find({
|
|
selector: defaultQuery,
|
|
...options
|
|
});
|
|
}
|
|
|
|
static async findOne(query) {
|
|
const rewards = await this.find(query, { limit: 1 });
|
|
return rewards[0] || null;
|
|
}
|
|
|
|
static async update(rewardId, updateData) {
|
|
const reward = await this.findById(rewardId);
|
|
if (!reward) {
|
|
throw new Error("Reward not found");
|
|
}
|
|
|
|
const updatedReward = {
|
|
...reward,
|
|
...updateData,
|
|
updatedAt: new Date().toISOString()
|
|
};
|
|
|
|
return await couchdbService.update(rewardId, updatedReward);
|
|
}
|
|
|
|
static async delete(rewardId) {
|
|
return await couchdbService.delete(rewardId);
|
|
}
|
|
|
|
static async findByCostRange(minCost, maxCost) {
|
|
return await couchdbService.find({
|
|
selector: {
|
|
type: "reward",
|
|
cost: { $gte: minCost, $lte: maxCost }
|
|
},
|
|
sort: [{ cost: "asc" }]
|
|
});
|
|
}
|
|
|
|
static async findByPremiumStatus(isPremium) {
|
|
return await this.find({ isPremium });
|
|
}
|
|
|
|
static async getActiveRewards() {
|
|
return await this.find({ isActive: true });
|
|
}
|
|
|
|
static async getPremiumRewards() {
|
|
return await this.find({ isPremium: true, isActive: true });
|
|
}
|
|
|
|
static async getRegularRewards() {
|
|
return await this.find({ isPremium: false, isActive: true });
|
|
}
|
|
|
|
static async getAllPaginated(page = 1, limit = 10) {
|
|
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"]
|
|
});
|
|
|
|
return {
|
|
rewards,
|
|
pagination: {
|
|
page,
|
|
limit,
|
|
totalCount: totalCount.length,
|
|
totalPages: Math.ceil(totalCount.length / limit)
|
|
}
|
|
};
|
|
}
|
|
|
|
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
|
|
}
|
|
);
|
|
|
|
// 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
|
|
};
|
|
}
|
|
|
|
static async getUserRedemptions(userId, limit = 20) {
|
|
return await couchdbService.find({
|
|
selector: {
|
|
type: "reward_redemption",
|
|
"user.userId": userId
|
|
},
|
|
sort: [{ redeemedAt: "desc" }],
|
|
limit
|
|
});
|
|
}
|
|
|
|
static async getRewardStats(rewardId) {
|
|
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
|
|
};
|
|
}
|
|
|
|
static async getCatalogStats() {
|
|
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))
|
|
};
|
|
}
|
|
|
|
static async searchRewards(searchTerm, options = {}) {
|
|
const query = {
|
|
selector: {
|
|
type: "reward",
|
|
isActive: true,
|
|
$or: [
|
|
{ name: { $regex: searchTerm, $options: "i" } },
|
|
{ description: { $regex: searchTerm, $options: "i" } }
|
|
]
|
|
},
|
|
...options
|
|
};
|
|
|
|
return await couchdbService.find(query);
|
|
}
|
|
|
|
// Migration helper
|
|
static async migrateFromMongo(mongoReward) {
|
|
const rewardData = {
|
|
name: mongoReward.name,
|
|
description: mongoReward.description,
|
|
cost: mongoReward.cost,
|
|
isPremium: mongoReward.isPremium || false,
|
|
isActive: true
|
|
};
|
|
|
|
return await this.create(rewardData);
|
|
}
|
|
|
|
// 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()
|
|
}));
|
|
|
|
return await couchdbService.bulkDocs(rewards);
|
|
}
|
|
|
|
static async toggleActiveStatus(rewardId) {
|
|
const reward = await this.findById(rewardId);
|
|
if (!reward) {
|
|
throw new Error("Reward not found");
|
|
}
|
|
|
|
return await this.update(rewardId, { isActive: !reward.isActive });
|
|
}
|
|
}
|
|
|
|
module.exports = Reward; |