feat: Complete admin panel implementation
- Add user management system with all CRUD operations - Add promotion statistics dashboard with export - Simplify Trading & Market settings UI - Fix promotion schema (dates now optional) - Add missing API endpoints and PATCH support - Add comprehensive documentation - Fix critical bugs (deletePromotion, duplicate endpoints) All features tested and production-ready.
This commit is contained in:
112
models/PromoUsage.js
Normal file
112
models/PromoUsage.js
Normal file
@@ -0,0 +1,112 @@
|
||||
import mongoose from "mongoose";
|
||||
|
||||
const PromoUsageSchema = new mongoose.Schema(
|
||||
{
|
||||
userId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "User",
|
||||
required: true,
|
||||
},
|
||||
promoId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
promoCode: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
promoName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
promoType: {
|
||||
type: String,
|
||||
enum: ["deposit_bonus", "discount", "free_item", "custom"],
|
||||
required: true,
|
||||
},
|
||||
// Bonus received
|
||||
bonusAmount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
discountAmount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
// Context of usage
|
||||
transactionId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "Transaction",
|
||||
default: null,
|
||||
},
|
||||
depositAmount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
// Metadata
|
||||
usedAt: {
|
||||
type: Date,
|
||||
default: Date.now,
|
||||
},
|
||||
ipAddress: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
{ timestamps: true }
|
||||
);
|
||||
|
||||
// Index for quick lookups
|
||||
PromoUsageSchema.index({ userId: 1, promoId: 1 });
|
||||
PromoUsageSchema.index({ promoId: 1 });
|
||||
PromoUsageSchema.index({ userId: 1 });
|
||||
|
||||
// Static method to check if user has used a promo
|
||||
PromoUsageSchema.statics.hasUserUsedPromo = async function (userId, promoId) {
|
||||
const usage = await this.findOne({ userId, promoId });
|
||||
return !!usage;
|
||||
};
|
||||
|
||||
// Static method to get user's promo usage count
|
||||
PromoUsageSchema.statics.getUserPromoCount = async function (userId, promoId) {
|
||||
return await this.countDocuments({ userId, promoId });
|
||||
};
|
||||
|
||||
// Static method to get total promo usage count
|
||||
PromoUsageSchema.statics.getPromoTotalUses = async function (promoId) {
|
||||
return await this.countDocuments({ promoId });
|
||||
};
|
||||
|
||||
// Static method to get promo usage stats
|
||||
PromoUsageSchema.statics.getPromoStats = async function (promoId) {
|
||||
const stats = await this.aggregate([
|
||||
{ $match: { promoId } },
|
||||
{
|
||||
$group: {
|
||||
_id: "$promoId",
|
||||
totalUses: { $sum: 1 },
|
||||
totalBonusGiven: { $sum: "$bonusAmount" },
|
||||
totalDiscountGiven: { $sum: "$discountAmount" },
|
||||
uniqueUsers: { $addToSet: "$userId" },
|
||||
averageBonusPerUse: { $avg: "$bonusAmount" },
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
if (stats.length === 0) {
|
||||
return {
|
||||
totalUses: 0,
|
||||
totalBonusGiven: 0,
|
||||
totalDiscountGiven: 0,
|
||||
uniqueUsers: 0,
|
||||
averageBonusPerUse: 0,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...stats[0],
|
||||
uniqueUsers: stats[0].uniqueUsers.length,
|
||||
};
|
||||
};
|
||||
|
||||
export default mongoose.model("PromoUsage", PromoUsageSchema);
|
||||
Reference in New Issue
Block a user