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:
2026-01-10 21:57:55 +00:00
parent b90cdd59df
commit 63c578b0ae
52 changed files with 21810 additions and 61 deletions

112
models/PromoUsage.js Normal file
View 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);