|
|
const express = require('express'); |
|
|
const mongoose = require('mongoose'); |
|
|
const bcrypt = require('bcryptjs'); |
|
|
const jwt = require('jsonwebtoken'); |
|
|
const nodemailer = require('nodemailer'); |
|
|
const rateLimit = require('express-rate-limit'); |
|
|
const path = require('path'); |
|
|
const fs = require('fs'); |
|
|
const { Resend } = require('resend'); |
|
|
const crypto = require('crypto'); |
|
|
const syntaxError = require('syntax-error'); |
|
|
const tmpRouter = require('./tmp'); |
|
|
|
|
|
|
|
|
const app = express(); |
|
|
app.use(express.json()); |
|
|
|
|
|
app.set('trust proxy', true); |
|
|
|
|
|
const getRealIP = (req, res, next) => { |
|
|
let ip; |
|
|
|
|
|
if (req.headers['cf-connecting-ip']) { |
|
|
ip = req.headers['cf-connecting-ip']; |
|
|
} else if (req.headers['x-real-ip']) { |
|
|
ip = req.headers['x-real-ip']; |
|
|
} else if (req.headers['x-forwarded-for']) { |
|
|
ip = req.headers['x-forwarded-for'].split(',')[0].trim(); |
|
|
} else { |
|
|
ip = req.socket.remoteAddress || req.connection.remoteAddress || '0.0.0.0'; |
|
|
} |
|
|
|
|
|
if (ip.startsWith('::ffff:')) { |
|
|
ip = ip.substring(7); |
|
|
} |
|
|
|
|
|
req.realIP = ip; |
|
|
next(); |
|
|
}; |
|
|
|
|
|
app.use(getRealIP); |
|
|
app.use(express.static('public')); |
|
|
|
|
|
const JWT_SECRET = process.env.JWT; |
|
|
const MONGODB_URI = process.env.MONGODB; |
|
|
|
|
|
mongoose.connect(MONGODB_URI) |
|
|
.then(() => console.log('Connected to MongoDB')) |
|
|
.catch(err => console.error('MongoDB connection error:', err)); |
|
|
|
|
|
const activitySchema = new mongoose.Schema({ |
|
|
userId: { type: String, required: true }, |
|
|
username: String, |
|
|
type: { |
|
|
type: String, |
|
|
enum: ['api_key_generated', 'limit_updated', 'profile_updated', 'redeem_code_used', 'premium_activated', 'api_request', 'temp_banned', 'ban_removed', 'limit_reset', 'role_expired', 'role_assigned', 'api_key_updated'], |
|
|
required: true |
|
|
}, |
|
|
description: String, |
|
|
metadata: Object, |
|
|
timestamp: { type: Date, default: Date.now } |
|
|
}); |
|
|
|
|
|
const userSchema = new mongoose.Schema({ |
|
|
username: { type: String, required: true, unique: true }, |
|
|
email: { type: String, required: true, unique: true }, |
|
|
password: { type: String, required: true }, |
|
|
apikey: { type: String, required: true, unique: true }, |
|
|
profileUrl: { type: String, default: 'https://files.catbox.moe/8l6hhm' }, |
|
|
verified: { type: Boolean, default: true }, |
|
|
role: { type: String, enum: ['user', 'admin'], default: 'user' }, |
|
|
userRole: { type: String, enum: ['cheap', 'premium', 'vip', 'supreme'], default: null }, |
|
|
userRoleExpiresAt: { type: Date, default: null }, |
|
|
premium: { type: Boolean, default: false }, |
|
|
premiumExpiredAt: { type: Date, default: null }, |
|
|
limit: { type: Number, default: 30 }, |
|
|
requests: { type: Number, default: 0 }, |
|
|
requestsToday: { type: Number, default: 0 }, |
|
|
lastReset: { type: Date, default: Date.now }, |
|
|
banned: { type: Boolean, default: false }, |
|
|
tempBanned: { type: Boolean, default: false }, |
|
|
tempBanUntil: { type: Date, default: null }, |
|
|
tempBanReason: { type: String, default: null }, |
|
|
ipAddress: String, |
|
|
createdAt: { type: Date, default: Date.now } |
|
|
}); |
|
|
|
|
|
const pendingVerificationSchema = new mongoose.Schema({ |
|
|
email: { type: String, required: true, unique: true }, |
|
|
username: { type: String, required: true }, |
|
|
password: { type: String, required: true }, |
|
|
verificationCode: { type: String, required: true }, |
|
|
ipAddress: String, |
|
|
ispInfo: Object, |
|
|
expiresAt: { type: Date, default: () => new Date(Date.now() + 15 * 60 * 1000) }, |
|
|
createdAt: { type: Date, default: Date.now } |
|
|
}); |
|
|
|
|
|
pendingVerificationSchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 }); |
|
|
|
|
|
const redeemCodeSchema = new mongoose.Schema({ |
|
|
code: { type: String, required: true, unique: true }, |
|
|
type: { type: String, enum: ['limit', 'premium', 'both'], required: true }, |
|
|
limitValue: { type: Number, default: 0 }, |
|
|
codeExpired: { type: Date, required: true }, |
|
|
premiumExpired: { type: Date, default: null }, |
|
|
used: { type: Boolean, default: false }, |
|
|
usedBy: String, |
|
|
createdBy: { type: String, required: true }, |
|
|
createdAt: { type: Date, default: Date.now } |
|
|
}); |
|
|
|
|
|
redeemCodeSchema.index({ codeExpired: 1 }, { expireAfterSeconds: 0 }); |
|
|
|
|
|
const requestLogSchema = new mongoose.Schema({ |
|
|
userId: String, |
|
|
username: String, |
|
|
apikey: String, |
|
|
endpoint: String, |
|
|
ipAddress: String, |
|
|
userAgent: String, |
|
|
timestamp: { type: Date, default: Date.now }, |
|
|
success: Boolean, |
|
|
responseTime: Number, |
|
|
limitDeducted: { type: Number, default: 1 } |
|
|
}); |
|
|
|
|
|
const banListSchema = new mongoose.Schema({ |
|
|
ipAddress: String, |
|
|
bannedUntil: Date, |
|
|
reason: String, |
|
|
createdAt: { type: Date, default: Date.now } |
|
|
}); |
|
|
|
|
|
banListSchema.index({ bannedUntil: 1 }, { expireAfterSeconds: 0 }); |
|
|
|
|
|
const roleSchema = new mongoose.Schema({ |
|
|
userId: { type: String, required: true }, |
|
|
roleName: { type: String, enum: ['cheap', 'premium', 'vip', 'supreme'], required: true }, |
|
|
customApiKey: { type: String, unique: true, sparse: true }, |
|
|
expiresAt: { type: Date, required: true }, |
|
|
createdAt: { type: Date, default: Date.now }, |
|
|
createdBy: String |
|
|
}); |
|
|
|
|
|
const UserRole = mongoose.model('UserRole', roleSchema); |
|
|
const Activity = mongoose.model('Activity', activitySchema); |
|
|
const User = mongoose.model('User', userSchema); |
|
|
const PendingVerification = mongoose.model('PendingVerification', pendingVerificationSchema); |
|
|
const RedeemCode = mongoose.model('RedeemCode', redeemCodeSchema); |
|
|
const RequestLog = mongoose.model('RequestLog', requestLogSchema); |
|
|
const BanList = mongoose.model('BanList', banListSchema); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const generateApiKey = () => { |
|
|
return 'DHX-' + crypto.randomBytes(3).toString('hex').toUpperCase(); |
|
|
}; |
|
|
|
|
|
const generateVerificationCode = () => { |
|
|
return Math.random().toString(36).substring(2, 8).toUpperCase(); |
|
|
}; |
|
|
|
|
|
const logActivity = async (userId, username, type, description, metadata = {}) => { |
|
|
try { |
|
|
await Activity.create({ |
|
|
userId, |
|
|
username, |
|
|
type, |
|
|
description, |
|
|
metadata, |
|
|
timestamp: new Date() |
|
|
}); |
|
|
} catch (error) { |
|
|
console.error('Activity logging error:', error); |
|
|
} |
|
|
}; |
|
|
|
|
|
const getTimeAgo = (date) => { |
|
|
const now = new Date(); |
|
|
const diff = now - new Date(date); |
|
|
const minutes = Math.floor(diff / 60000); |
|
|
const hours = Math.floor(diff / 3600000); |
|
|
const days = Math.floor(diff / 86400000); |
|
|
|
|
|
if (minutes < 1) return 'Just now'; |
|
|
if (minutes < 60) return `${minutes} minutes ago`; |
|
|
if (hours < 24) return `${hours} hours ago`; |
|
|
return `${days} days ago`; |
|
|
}; |
|
|
|
|
|
const getNextMidnight = () => { |
|
|
const now = new Date(); |
|
|
const midnight = new Date(now); |
|
|
midnight.setHours(24, 0, 0, 0); |
|
|
return midnight; |
|
|
}; |
|
|
|
|
|
const getTimeUntilMidnight = () => { |
|
|
const now = new Date(); |
|
|
const midnight = getNextMidnight(); |
|
|
const diff = midnight - now; |
|
|
|
|
|
const hours = Math.floor(diff / (1000 * 60 * 60)); |
|
|
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); |
|
|
const seconds = Math.floor((diff % (1000 * 60)) / 1000); |
|
|
|
|
|
return { hours, minutes, seconds, totalMs: diff }; |
|
|
}; |
|
|
|
|
|
const apiRateLimit = rateLimit({ |
|
|
windowMs: 10 * 1000, |
|
|
max: 3, |
|
|
message: { success: false, error: 'Too many requests, please try again later' }, |
|
|
standardHeaders: true, |
|
|
legacyHeaders: false, |
|
|
keyGenerator: (req) => req.realIP, |
|
|
handler: async (req, res) => { |
|
|
const ip = req.realIP; |
|
|
const bannedUntil = new Date(Date.now() + 10 * 60 * 1000); |
|
|
try { |
|
|
await BanList.create({ ipAddress: ip, bannedUntil, reason: 'Rate limit exceeded' }); |
|
|
} catch (error) { |
|
|
console.error('Ban list creation error:', error); |
|
|
} |
|
|
|
|
|
const randomStatus = Math.random() > 0.5 ? 429 : 403; |
|
|
res.status(randomStatus).json({ |
|
|
success: false, |
|
|
error: randomStatus === 429 ? 'Too many requests' : 'Forbidden - IP temporarily banned' |
|
|
}); |
|
|
} |
|
|
}); |
|
|
|
|
|
const authRateLimit = rateLimit({ |
|
|
windowMs: 5 * 60 * 1000, |
|
|
max: 10, |
|
|
message: { success: false, error: '429 Forbidden Request flood detected' }, |
|
|
standardHeaders: true, |
|
|
legacyHeaders: false, |
|
|
keyGenerator: (req) => req.realIP, |
|
|
handler: async (req, res) => { |
|
|
const ip = req.realIP; |
|
|
const bannedUntil = new Date(Date.now() + 30 * 60 * 1000); |
|
|
|
|
|
try { |
|
|
const existingBan = await BanList.findOne({ ipAddress: ip }); |
|
|
|
|
|
if (!existingBan) { |
|
|
await BanList.create({ |
|
|
ipAddress: ip, |
|
|
bannedUntil, |
|
|
reason: 'Authentication flood detected' |
|
|
}); |
|
|
console.log(`IP ${ip} banned for 30 minutes due to auth flood`); |
|
|
} |
|
|
} catch (error) { |
|
|
console.error('Auth ban list creation error:', error); |
|
|
} |
|
|
|
|
|
res.status(429).json({ |
|
|
success: false, |
|
|
error: '429 Forbidden Request flood detected', |
|
|
bannedUntil: bannedUntil.toISOString(), |
|
|
message: 'Your IP has been temporarily blocked for 30 minutes' |
|
|
}); |
|
|
} |
|
|
}); |
|
|
|
|
|
const checkBanned = async (req, res, next) => { |
|
|
const ip = req.realIP; |
|
|
try { |
|
|
const banned = await BanList.findOne({ |
|
|
ipAddress: ip, |
|
|
bannedUntil: { $gt: new Date() } |
|
|
}); |
|
|
|
|
|
if (banned) { |
|
|
return res.status(403).json({ |
|
|
success: false, |
|
|
error: 'IP is temporarily banned', |
|
|
bannedUntil: banned.bannedUntil, |
|
|
reason: banned.reason |
|
|
}); |
|
|
} |
|
|
} catch (error) { |
|
|
console.error('Check banned error:', error); |
|
|
} |
|
|
next(); |
|
|
}; |
|
|
|
|
|
const checkTempBan = async (user) => { |
|
|
if (user.tempBanned && user.tempBanUntil) { |
|
|
if (new Date() > user.tempBanUntil) { |
|
|
user.tempBanned = false; |
|
|
user.tempBanUntil = null; |
|
|
user.tempBanReason = null; |
|
|
await user.save(); |
|
|
|
|
|
await logActivity(user._id, user.username, 'ban_removed', 'Temporary ban expired automatically'); |
|
|
return false; |
|
|
} |
|
|
return true; |
|
|
} |
|
|
return false; |
|
|
}; |
|
|
|
|
|
const checkPremiumExpiry = async (user) => { |
|
|
if (user.premium && user.premiumExpiredAt) { |
|
|
if (new Date() > user.premiumExpiredAt) { |
|
|
user.premium = false; |
|
|
user.premiumExpiredAt = null; |
|
|
await user.save(); |
|
|
return true; |
|
|
} |
|
|
} |
|
|
return false; |
|
|
}; |
|
|
|
|
|
const checkRoleExpiry = async (user) => { |
|
|
if (user.userRoleExpiresAt && new Date() > user.userRoleExpiresAt) { |
|
|
const oldRole = user.userRole; |
|
|
const oldLimit = user.limit; |
|
|
|
|
|
user.userRole = null; |
|
|
user.userRoleExpiresAt = null; |
|
|
user.premium = false; |
|
|
user.premiumExpiredAt = null; |
|
|
|
|
|
if (user.limit <= 30) { |
|
|
user.limit = 30; |
|
|
} |
|
|
|
|
|
await user.save(); |
|
|
await UserRole.deleteOne({ userId: user._id }); |
|
|
|
|
|
await logActivity(user._id, user.username, 'role_expired', `Role ${oldRole} expired, limit ${oldLimit} → ${user.limit}`); |
|
|
return true; |
|
|
} |
|
|
return false; |
|
|
}; |
|
|
|
|
|
const resetUserLimitIfNeeded = async (user) => { |
|
|
const now = new Date(); |
|
|
const lastReset = new Date(user.lastReset); |
|
|
|
|
|
now.setHours(0, 0, 0, 0); |
|
|
lastReset.setHours(0, 0, 0, 0); |
|
|
|
|
|
const needsReset = now.getTime() !== lastReset.getTime(); |
|
|
|
|
|
if (needsReset) { |
|
|
if (user.userRole) { |
|
|
const roleConfig = { |
|
|
cheap: 500, |
|
|
premium: 1500, |
|
|
vip: 2500, |
|
|
supreme: 3000 |
|
|
}; |
|
|
|
|
|
const oldLimit = user.limit; |
|
|
user.limit = roleConfig[user.userRole] || 30; |
|
|
user.requestsToday = 0; |
|
|
user.lastReset = new Date(); |
|
|
|
|
|
await user.save(); |
|
|
|
|
|
await logActivity(user._id, user.username, 'limit_reset', `Daily limit reset from ${oldLimit} to ${user.limit} (Role: ${user.userRole})`); |
|
|
|
|
|
return true; |
|
|
} else { |
|
|
if (user.limit <= 30) { |
|
|
user.limit = 30; |
|
|
user.requestsToday = 0; |
|
|
user.lastReset = new Date(); |
|
|
|
|
|
await user.save(); |
|
|
|
|
|
await logActivity(user._id, user.username, 'limit_reset', 'Daily limit reset to 30 (No role)'); |
|
|
|
|
|
return true; |
|
|
} else { |
|
|
user.requestsToday = 0; |
|
|
user.lastReset = new Date(); |
|
|
|
|
|
await user.save(); |
|
|
|
|
|
await logActivity(user._id, user.username, 'limit_reset', `Requests reset, limit remains ${user.limit} (waiting to reach 30)`); |
|
|
|
|
|
return true; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
return false; |
|
|
}; |
|
|
|
|
|
const authenticate = async (req, res, next) => { |
|
|
const token = req.headers.authorization?.replace('Bearer ', ''); |
|
|
if (!token) { |
|
|
return res.status(401).json({ success: false, error: 'No token provided' }); |
|
|
} |
|
|
|
|
|
try { |
|
|
const decoded = jwt.verify(token, JWT_SECRET); |
|
|
req.user = await User.findById(decoded.userId); |
|
|
if (!req.user) { |
|
|
return res.status(401).json({ success: false, error: 'User not found' }); |
|
|
} |
|
|
|
|
|
await checkPremiumExpiry(req.user); |
|
|
await checkRoleExpiry(req.user); |
|
|
await resetUserLimitIfNeeded(req.user); |
|
|
|
|
|
next(); |
|
|
} catch (error) { |
|
|
return res.status(401).json({ success: false, error: 'Invalid token' }); |
|
|
} |
|
|
}; |
|
|
|
|
|
const validateApiKey = async (req, res, next) => { |
|
|
const { key } = req.query; |
|
|
if (!key) { |
|
|
return res.status(400).json({ success: false, error: 'API key required' }); |
|
|
} |
|
|
|
|
|
try { |
|
|
const user = await User.findOne({ apikey: key }); |
|
|
if (!user || user.banned) { |
|
|
return res.status(401).json({ success: false, error: 'Invalid or banned API key' }); |
|
|
} |
|
|
|
|
|
await checkPremiumExpiry(user); |
|
|
await checkRoleExpiry(user); |
|
|
await resetUserLimitIfNeeded(user); |
|
|
|
|
|
const isTempBanned = await checkTempBan(user); |
|
|
if (isTempBanned) { |
|
|
const banUntil = new Date(user.tempBanUntil); |
|
|
return res.status(403).json({ |
|
|
success: false, |
|
|
error: `Account temporarily banned until ${banUntil.toLocaleString()}. Reason: ${user.tempBanReason || 'No reason provided'}` |
|
|
}); |
|
|
} |
|
|
|
|
|
const limitDeduction = req.limitDeduction || 1; |
|
|
|
|
|
if (user.role !== 'admin' && user.requestsToday >= user.limit) { |
|
|
return res.status(429).json({ success: false, error: 'Daily limit exceeded' }); |
|
|
} |
|
|
|
|
|
user.requests++; |
|
|
user.requestsToday += limitDeduction; |
|
|
await user.save(); |
|
|
|
|
|
await logActivity(user._id, user.username, 'api_request', 'API request made', { |
|
|
endpoint: req.path, |
|
|
method: req.method, |
|
|
ip: req.realIP, |
|
|
limitDeducted: limitDeduction |
|
|
}); |
|
|
|
|
|
req.apiUser = user; |
|
|
req.limitDeducted = limitDeduction; |
|
|
next(); |
|
|
} catch (error) { |
|
|
console.error('API key validation error:', error); |
|
|
return res.status(500).json({ success: false, error: 'Internal server error' }); |
|
|
} |
|
|
}; |
|
|
|
|
|
const BLOCKED_ISP_KEYWORDS = [ |
|
|
'digitalocean', 'linode', 'vultr', 'ovh', 'hetzner', |
|
|
'contabo', 'amazon', 'aws', 'google cloud', 'microsoft azure', |
|
|
'scaleway', 'ramnode', 'buyvm', 'hostinger', 'namecheap', |
|
|
'godaddy', 'hostgator', 'bluehost', 'siteground', |
|
|
'cloudflare', 'fastly', 'maxcdn', 'keycdn', 'bunnycdn', |
|
|
'rackspace', 'packet', 'equinix', 'servermania', |
|
|
'quadranet', 'psychz', 'choopa', 'fdcservers', 'nobistech', |
|
|
'colocrossing', 'hostus', 'reliablesite', 'serverpronto', |
|
|
'wholesaleinternet', 'online.net', 'nforce', 'leaseweb', |
|
|
'expressvpn', 'nordvpn', 'surfshark', 'cyberghost', 'purevpn', |
|
|
'ipvanish', 'tunnelbear', 'protonvpn', 'mullvad', |
|
|
'tor exit', 'proxy', 'vpn', 'anonymous' |
|
|
]; |
|
|
|
|
|
const detectSuspiciousISP = async (ip) => { |
|
|
try { |
|
|
const response = await fetch(`https://ipinfo.io/${ip}?token=790c300f1388ce`); |
|
|
const data = await response.json(); |
|
|
|
|
|
const isp = (data.org || '').toLowerCase(); |
|
|
const company = (data.company?.name || '').toLowerCase(); |
|
|
const asn = (data.asn?.name || '').toLowerCase(); |
|
|
|
|
|
const suspiciousKeywords = BLOCKED_ISP_KEYWORDS.some(keyword => |
|
|
isp.includes(keyword) || company.includes(keyword) || asn.includes(keyword) |
|
|
); |
|
|
|
|
|
return { |
|
|
isSuspicious: suspiciousKeywords, |
|
|
isp: data.org, |
|
|
country: data.country, |
|
|
region: data.region, |
|
|
city: data.city |
|
|
}; |
|
|
} catch (error) { |
|
|
console.error('ISP detection error:', error); |
|
|
return { isSuspicious: false }; |
|
|
} |
|
|
}; |
|
|
|
|
|
async function hashPassword() { |
|
|
const password = process.env.DEFAULT_PASSWORD; |
|
|
const hashedPassword = await bcrypt.hash(password, 12); |
|
|
console.log(hashedPassword); |
|
|
return hashedPassword; |
|
|
} |
|
|
let herxaj = process.env.APIKEY |
|
|
|
|
|
const initializeAdmin = async () => { |
|
|
try { |
|
|
const hashedPassword = await bcrypt.hash(process.env.DEFAULT_PASSWORD, 12); |
|
|
let admin = await User.findOne({ username: 'HERXA' }); |
|
|
if (!admin) { |
|
|
admin = new User({ |
|
|
username: 'HERXA', |
|
|
email: 'admin@dashx.com', |
|
|
password: hashedPassword, |
|
|
apikey: herxaj, |
|
|
profileUrl: 'https://files.catbox.moe/8l6hhm', |
|
|
verified: true, |
|
|
role: 'admin', |
|
|
premium: true, |
|
|
limit: 9999, |
|
|
ipAddress: '127.0.0.1' |
|
|
}); |
|
|
await admin.save(); |
|
|
} else { |
|
|
admin.apikey = 'DHX-M3SA'; |
|
|
admin.limit = 9999; |
|
|
admin.role = 'admin'; |
|
|
admin.premium = true; |
|
|
await admin.save(); |
|
|
} |
|
|
} catch (error) { |
|
|
console.error('Admin initialization error:', error); |
|
|
} |
|
|
}; |
|
|
|
|
|
app.get('/', (req, res) => { |
|
|
res.sendFile(path.join(__dirname, 'public', 'index.html')); |
|
|
}); |
|
|
|
|
|
app.get('/auth', (req, res) => { |
|
|
res.sendFile(path.join(__dirname, 'public', 'auth.html')); |
|
|
}); |
|
|
|
|
|
app.get('/denied', (req, res) => { |
|
|
res.sendFile(path.join(__dirname, 'public', 'denied.html')); |
|
|
}); |
|
|
|
|
|
app.get('/dashboard', (req, res) => { |
|
|
res.sendFile(path.join(__dirname, 'public', 'dashboard.html')); |
|
|
}); |
|
|
|
|
|
app.get('/profile', (req, res) => { |
|
|
res.sendFile(path.join(__dirname, 'public', 'profile.html')); |
|
|
}); |
|
|
|
|
|
app.get('/check-ip', (req, res) => { |
|
|
res.json({ |
|
|
realIP: req.realIP, |
|
|
headers: { |
|
|
'x-forwarded-for': req.headers['x-forwarded-for'], |
|
|
'x-real-ip': req.headers['x-real-ip'], |
|
|
'cf-connecting-ip': req.headers['cf-connecting-ip'] |
|
|
}, |
|
|
socket: req.socket.remoteAddress |
|
|
}); |
|
|
}); |
|
|
|
|
|
app.get('/api/stats', async (req, res) => { |
|
|
try { |
|
|
const totalUsers = await User.countDocuments(); |
|
|
const totalRequests = await RequestLog.countDocuments(); |
|
|
|
|
|
const todayStart = new Date(); |
|
|
todayStart.setHours(0, 0, 0, 0); |
|
|
|
|
|
const todayRequests = await RequestLog.countDocuments({ |
|
|
timestamp: { $gte: todayStart } |
|
|
}); |
|
|
|
|
|
res.json({ |
|
|
success: true, |
|
|
stats: { |
|
|
totalUsers, |
|
|
totalRequests, |
|
|
todayRequests |
|
|
} |
|
|
}); |
|
|
} catch (error) { |
|
|
console.error('Stats error:', error); |
|
|
res.status(500).json({ |
|
|
success: false, |
|
|
error: 'Failed to load statistics' |
|
|
}); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.get('/api/plugins', (req, res) => { |
|
|
const plugins = []; |
|
|
|
|
|
try { |
|
|
Object.keys(global.plugins).forEach(file => { |
|
|
try { |
|
|
const plugin = global.plugins[file]; |
|
|
if (plugin && plugin.enabled) { |
|
|
plugins.push({ |
|
|
name: plugin.name || 'Unknown Plugin', |
|
|
description: plugin.description || 'No description', |
|
|
type: plugin.type || 'GET', |
|
|
routes: plugin.routes || [], |
|
|
main: plugin.main || [], |
|
|
tags: plugin.tags || [], |
|
|
parameters: plugin.parameters || {}, |
|
|
limit: plugin.limit || 1 |
|
|
}); |
|
|
} |
|
|
} catch (error) { |
|
|
console.error(`Error loading plugin info ${file}:`, error.message); |
|
|
} |
|
|
}); |
|
|
} catch (error) { |
|
|
console.error('Error reading plugins:', error); |
|
|
} |
|
|
|
|
|
res.json({ success: true, plugins }); |
|
|
}); |
|
|
|
|
|
app.get('/DB/delete', validateApiKey, async (req, res) => { |
|
|
try { |
|
|
if (req.apiUser.role !== 'admin') { |
|
|
return res.status(403).json({ success: false, error: 'Admin access required' }); |
|
|
} |
|
|
|
|
|
const deleteResults = {}; |
|
|
|
|
|
const userResult = await User.deleteMany({ |
|
|
_id: { $ne: req.apiUser._id } |
|
|
}); |
|
|
deleteResults.users = userResult.deletedCount; |
|
|
|
|
|
const pendingResult = await PendingVerification.deleteMany({}); |
|
|
deleteResults.pendingVerifications = pendingResult.deletedCount; |
|
|
|
|
|
const redeemResult = await RedeemCode.deleteMany({}); |
|
|
deleteResults.redeemCodes = redeemResult.deletedCount; |
|
|
|
|
|
const logResult = await RequestLog.deleteMany({}); |
|
|
deleteResults.requestLogs = logResult.deletedCount; |
|
|
|
|
|
const banResult = await BanList.deleteMany({}); |
|
|
deleteResults.banList = banResult.deletedCount; |
|
|
|
|
|
const activityResult = await Activity.deleteMany({}); |
|
|
deleteResults.activities = activityResult.deletedCount; |
|
|
|
|
|
res.json({ |
|
|
success: true, |
|
|
message: 'Database cleared successfully', |
|
|
deletedCounts: deleteResults, |
|
|
timestamp: new Date().toISOString() |
|
|
}); |
|
|
|
|
|
} catch (error) { |
|
|
console.error('Database deletion error:', error); |
|
|
res.status(500).json({ |
|
|
success: false, |
|
|
error: 'Failed to delete database data', |
|
|
details: error.message |
|
|
}); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.post('/api/auth/register', checkBanned, authRateLimit, async (req, res) => { |
|
|
try { |
|
|
const { username, email, password } = req.body; |
|
|
const ipAddress = req.realIP; |
|
|
|
|
|
if (!username || !email || !password) { |
|
|
return res.status(400).json({ |
|
|
success: false, |
|
|
error: 'Username, email, and password are required' |
|
|
}); |
|
|
} |
|
|
|
|
|
if (password.length < 6) { |
|
|
return res.status(400).json({ |
|
|
success: false, |
|
|
error: 'Password must be at least 6 characters long' |
|
|
}); |
|
|
} |
|
|
|
|
|
const ispCheck = await detectSuspiciousISP(ipAddress); |
|
|
if (ispCheck.isSuspicious) { |
|
|
return res.status(403).json({ |
|
|
success: false, |
|
|
error: 'Registration not allowed from this network. Please use a different connection.', |
|
|
blocked_reason: 'suspicious_isp' |
|
|
}); |
|
|
} |
|
|
|
|
|
const existingUser = await User.findOne({ |
|
|
$or: [{ email }, { username }, { ipAddress }] |
|
|
}); |
|
|
|
|
|
if (existingUser) { |
|
|
return res.status(400).json({ |
|
|
success: false, |
|
|
error: 'User already exists with this email, username, or IP address' |
|
|
}); |
|
|
} |
|
|
|
|
|
const existingPending = await PendingVerification.findOne({ email }); |
|
|
if (existingPending) { |
|
|
await PendingVerification.deleteOne({ _id: existingPending._id }); |
|
|
} |
|
|
|
|
|
const verificationCode = generateVerificationCode(); |
|
|
const hashedPassword = await bcrypt.hash(password, 12); |
|
|
|
|
|
const pendingVerification = new PendingVerification({ |
|
|
email, |
|
|
username, |
|
|
password: hashedPassword, |
|
|
verificationCode, |
|
|
ipAddress, |
|
|
ispInfo: { |
|
|
isp: ispCheck.isp, |
|
|
country: ispCheck.country, |
|
|
region: ispCheck.region, |
|
|
city: ispCheck.city, |
|
|
registeredAt: new Date() |
|
|
} |
|
|
}); |
|
|
|
|
|
await pendingVerification.save(); |
|
|
const resend = new Resend(process.env.RESEND_API_KEY); |
|
|
const emailTemplate = ` |
|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>DashX Verification</title> |
|
|
</head> |
|
|
<body style="margin: 0; padding: 0; font-family: 'Arial', sans-serif; background: linear-gradient(135deg, #853030 0%, #292727 100%); min-height: 100vh;"> |
|
|
<div style="max-width: 600px; margin: 0 auto; padding: 20px;"> |
|
|
<div style="background: #ffffff; border-radius: 20px; box-shadow: 0 20px 40px rgba(0,0,0,0.1); overflow: hidden;"> |
|
|
<div style="background: linear-gradient(135deg, #853030 0%, #292727 100%); padding: 40px 30px; text-align: center;"> |
|
|
<h1 style="color: #ffffff; margin: 0; font-size: 32px; font-weight: 700; text-shadow: 0 2px 4px rgba(0,0,0,0.1);"> |
|
|
🚀 DashX |
|
|
</h1> |
|
|
<p style="color: #e8eaf6; margin: 10px 0 0 0; font-size: 16px; opacity: 0.9;"> |
|
|
API Dashboard Platform |
|
|
</p> |
|
|
</div> |
|
|
<div style="padding: 40px 30px;"> |
|
|
<div style="text-align: center; margin-bottom: 30px;"> |
|
|
<h2 style="color: #2c3e50; margin: 0 0 15px 0; font-size: 28px; font-weight: 600;"> |
|
|
Welcome to DashX! |
|
|
</h2> |
|
|
<p style="color: #7f8c8d; margin: 0; font-size: 16px; line-height: 1.6;"> |
|
|
Please verify your email address to complete registration. |
|
|
</p> |
|
|
</div> |
|
|
<div style="background: linear-gradient(135deg, #f8f9ff 0%, #e8eaf6 100%); border-radius: 15px; padding: 30px; text-align: center; margin: 30px 0; border: 2px solid #e3f2fd;"> |
|
|
<p style="color: #853030; margin: 0 0 15px 0; font-size: 18px; font-weight: 600;"> |
|
|
Your Verification Code |
|
|
</p> |
|
|
<div style="background: #ffffff; border-radius: 10px; padding: 20px; margin: 15px 0; box-shadow: 0 4px 12px rgba(0,0,0,0.1);"> |
|
|
<span style="font-size: 36px; font-weight: 700; color: #853030; letter-spacing: 8px; font-family: 'Courier New', monospace;"> |
|
|
${verificationCode} |
|
|
</span> |
|
|
</div> |
|
|
<p style="color: #7986cb; margin: 15px 0 0 0; font-size: 14px;"> |
|
|
This code will expire in 15 minutes |
|
|
</p> |
|
|
</div> |
|
|
</div> |
|
|
<div style="background: #f8f9fa; padding: 25px 30px; text-align: center; border-top: 1px solid #e9ecef;"> |
|
|
<p style="color: #6c757d; margin: 0 0 10px 0; font-size: 14px;"> |
|
|
Need help? Contact our support team |
|
|
</p> |
|
|
<p style="color: #adb5bd; margin: 0; font-size: 12px;"> |
|
|
© 2025 DashX. All rights reserved. |
|
|
</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</body> |
|
|
</html> |
|
|
`; |
|
|
const EMAIL_API_URL = process.env.API_URL |
|
|
const EMAIL_API_KEY = process.env.EMAIL_API_KEY; |
|
|
|
|
|
|
|
|
const response = await fetch('https://mail-sooty-gamma.vercel.app/api/send-email', { |
|
|
method: 'POST', |
|
|
headers: { |
|
|
'Content-Type': 'application/json', |
|
|
'x-api-key': EMAIL_API_KEY |
|
|
}, |
|
|
body: JSON.stringify({ |
|
|
target: email, |
|
|
subject: 'DashX - Email Verification', |
|
|
html: emailTemplate |
|
|
}) |
|
|
}); |
|
|
|
|
|
res.json({ |
|
|
success: true, |
|
|
message: 'Verification code sent to your email. Please verify within 15 minutes.', |
|
|
expiresIn: '15 minutes' |
|
|
}); |
|
|
|
|
|
} catch (error) { |
|
|
console.error('Registration error:', error); |
|
|
res.status(500).json({ success: false, error: 'Internal server error' }); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.post('/api/auth/verify', checkBanned, authRateLimit, async (req, res) => { |
|
|
try { |
|
|
const { email, code } = req.body; |
|
|
|
|
|
if (!email || !code) { |
|
|
return res.status(400).json({ |
|
|
success: false, |
|
|
error: 'Email and verification code are required' |
|
|
}); |
|
|
} |
|
|
|
|
|
const pendingVerification = await PendingVerification.findOne({ |
|
|
email, |
|
|
verificationCode: code |
|
|
}); |
|
|
|
|
|
if (!pendingVerification) { |
|
|
return res.status(400).json({ |
|
|
success: false, |
|
|
error: 'Invalid verification code or code has expired' |
|
|
}); |
|
|
} |
|
|
|
|
|
const existingUser = await User.findOne({ |
|
|
$or: [{ email: pendingVerification.email }, { username: pendingVerification.username }] |
|
|
}); |
|
|
|
|
|
if (existingUser) { |
|
|
await PendingVerification.deleteOne({ _id: pendingVerification._id }); |
|
|
return res.status(400).json({ |
|
|
success: false, |
|
|
error: 'User already exists in database' |
|
|
}); |
|
|
} |
|
|
|
|
|
const apikey = generateApiKey(); |
|
|
|
|
|
const user = new User({ |
|
|
username: pendingVerification.username, |
|
|
email: pendingVerification.email, |
|
|
password: pendingVerification.password, |
|
|
apikey: apikey, |
|
|
profileUrl: 'https://files.catbox.moe/8l6hhm', |
|
|
verified: true, |
|
|
ipAddress: pendingVerification.ipAddress, |
|
|
role: 'user', |
|
|
premium: false, |
|
|
limit: 30, |
|
|
requests: 0, |
|
|
requestsToday: 0, |
|
|
lastReset: new Date(), |
|
|
banned: false |
|
|
}); |
|
|
|
|
|
await user.save(); |
|
|
await PendingVerification.deleteOne({ _id: pendingVerification._id }); |
|
|
|
|
|
await logActivity(user._id, user.username, 'api_key_generated', 'Account created and verified successfully', { |
|
|
apikey: user.apikey, |
|
|
registrationIP: pendingVerification.ipAddress |
|
|
}); |
|
|
|
|
|
const token = jwt.sign({ userId: user._id }, JWT_SECRET, { expiresIn: '30d' }); |
|
|
|
|
|
res.json({ |
|
|
success: true, |
|
|
token, |
|
|
user: { |
|
|
id: user._id, |
|
|
username: user.username, |
|
|
email: user.email, |
|
|
apikey: user.apikey, |
|
|
profileUrl: user.profileUrl, |
|
|
premium: user.premium, |
|
|
limit: user.limit |
|
|
}, |
|
|
message: 'Account created and verified successfully!' |
|
|
}); |
|
|
|
|
|
} catch (error) { |
|
|
console.error('Verification error:', error); |
|
|
res.status(500).json({ success: false, error: 'Internal server error' }); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.get('/api/admin/search-users', authenticate, async (req, res) => { |
|
|
try { |
|
|
if (req.user.role !== 'admin') { |
|
|
return res.status(403).json({ success: false, error: 'Admin access required' }); |
|
|
} |
|
|
|
|
|
const { query } = req.query; |
|
|
if (!query) { |
|
|
return res.json({ success: true, users: [] }); |
|
|
} |
|
|
|
|
|
const users = await User.find({ |
|
|
$or: [ |
|
|
{ username: { $regex: query, $options: 'i' } }, |
|
|
{ email: { $regex: query, $options: 'i' } } |
|
|
], |
|
|
role: { $ne: 'admin' } |
|
|
}).limit(10).select('username email limit premium role'); |
|
|
|
|
|
res.json({ success: true, users }); |
|
|
} catch (error) { |
|
|
console.error('Search users error:', error); |
|
|
res.status(500).json({ success: false, error: 'Internal server error' }); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.get('/api/admin/user-role/:userId', authenticate, async (req, res) => { |
|
|
try { |
|
|
if (req.user.role !== 'admin') { |
|
|
return res.status(403).json({ success: false, error: 'Admin access required' }); |
|
|
} |
|
|
|
|
|
const userRole = await UserRole.findOne({ userId: req.params.userId }); |
|
|
res.json({ success: true, role: userRole }); |
|
|
} catch (error) { |
|
|
res.status(500).json({ success: false, error: 'Internal server error' }); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.post('/api/auth/login', checkBanned, authRateLimit, async (req, res) => { |
|
|
try { |
|
|
const { email, password } = req.body; |
|
|
|
|
|
if (!email || !password) { |
|
|
return res.status(400).json({ |
|
|
success: false, |
|
|
error: 'Email and password are required' |
|
|
}); |
|
|
} |
|
|
|
|
|
const user = await User.findOne({ email }); |
|
|
if (!user) { |
|
|
return res.status(400).json({ success: false, error: 'Invalid credentials' }); |
|
|
} |
|
|
|
|
|
const isValidPassword = await bcrypt.compare(password, user.password); |
|
|
if (!isValidPassword) { |
|
|
return res.status(400).json({ success: false, error: 'Invalid credentials' }); |
|
|
} |
|
|
|
|
|
if (user.banned) { |
|
|
return res.status(403).json({ success: false, error: 'Account is permanently banned' }); |
|
|
} |
|
|
|
|
|
await checkPremiumExpiry(user); |
|
|
await checkRoleExpiry(user); |
|
|
await resetUserLimitIfNeeded(user); |
|
|
|
|
|
const isTempBanned = await checkTempBan(user); |
|
|
if (isTempBanned) { |
|
|
const banUntil = new Date(user.tempBanUntil); |
|
|
return res.status(403).json({ |
|
|
success: false, |
|
|
error: `Account temporarily banned until ${banUntil.toLocaleString()}. Reason: ${user.tempBanReason || 'No reason provided'}` |
|
|
}); |
|
|
} |
|
|
|
|
|
const token = jwt.sign({ userId: user._id }, JWT_SECRET, { expiresIn: '30d' }); |
|
|
|
|
|
res.json({ |
|
|
success: true, |
|
|
token, |
|
|
user: { |
|
|
id: user._id, |
|
|
username: user.username, |
|
|
email: user.email, |
|
|
apikey: user.apikey, |
|
|
profileUrl: user.profileUrl, |
|
|
premium: user.premium, |
|
|
premiumExpiredAt: user.premiumExpiredAt, |
|
|
limit: user.limit, |
|
|
role: user.role |
|
|
} |
|
|
}); |
|
|
} catch (error) { |
|
|
console.error('Login error:', error); |
|
|
res.status(500).json({ success: false, error: 'Internal server error' }); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.post('/api/auth/admin-login', checkBanned, authRateLimit, async (req, res) => { |
|
|
try { |
|
|
const { username, password } = req.body; |
|
|
|
|
|
if (username === 'HERXA' && password === 'BTXHZ') { |
|
|
let admin = await User.findOne({ username: 'HERXA' }); |
|
|
if (!admin) { |
|
|
admin = new User({ |
|
|
username: 'HERXA', |
|
|
email: 'admin@dashx.com', |
|
|
password: await bcrypt.hash('BTXHZ', 12), |
|
|
apikey: 'DHX-M3SA', |
|
|
profileUrl: 'https://files.catbox.moe/8l6hhm', |
|
|
verified: true, |
|
|
role: 'admin', |
|
|
premium: true, |
|
|
limit: 9999, |
|
|
ipAddress: req.realIP |
|
|
}); |
|
|
await admin.save(); |
|
|
} else { |
|
|
admin.apikey = 'DHX-M3SA'; |
|
|
admin.limit = 9999; |
|
|
admin.role = 'admin'; |
|
|
admin.premium = true; |
|
|
await admin.save(); |
|
|
} |
|
|
|
|
|
const token = jwt.sign({ userId: admin._id }, JWT_SECRET, { expiresIn: '30d' }); |
|
|
return res.json({ |
|
|
success: true, |
|
|
token, |
|
|
user: { |
|
|
id: admin._id, |
|
|
username: admin.username, |
|
|
role: admin.role, |
|
|
apikey: admin.apikey, |
|
|
profileUrl: admin.profileUrl, |
|
|
limit: admin.limit |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
const admin = await User.findOne({ username, role: 'admin' }); |
|
|
if (!admin || !await bcrypt.compare(password, admin.password)) { |
|
|
return res.status(400).json({ success: false, error: 'Invalid admin credentials' }); |
|
|
} |
|
|
|
|
|
const token = jwt.sign({ userId: admin._id }, JWT_SECRET, { expiresIn: '30d' }); |
|
|
res.json({ |
|
|
success: true, |
|
|
token, |
|
|
user: { |
|
|
id: admin._id, |
|
|
username: admin.username, |
|
|
role: admin.role, |
|
|
apikey: admin.apikey, |
|
|
profileUrl: admin.profileUrl, |
|
|
limit: admin.limit |
|
|
} |
|
|
}); |
|
|
} catch (error) { |
|
|
console.error('Admin login error:', error); |
|
|
res.status(500).json({ success: false, error: 'Internal server error' }); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.get('/api/user/profile', authenticate, async (req, res) => { |
|
|
try { |
|
|
const personalTodayStart = new Date(); |
|
|
personalTodayStart.setHours(0, 0, 0, 0); |
|
|
|
|
|
const userTodayRequestCount = await RequestLog.countDocuments({ |
|
|
$or: [ |
|
|
{ userId: req.user._id.toString() }, |
|
|
{ userId: req.user._id } |
|
|
], |
|
|
timestamp: { $gte: personalTodayStart } |
|
|
}); |
|
|
|
|
|
const userTotalRequestCount = await RequestLog.countDocuments({ |
|
|
$or: [ |
|
|
{ userId: req.user._id.toString() }, |
|
|
{ userId: req.user._id } |
|
|
] |
|
|
}); |
|
|
|
|
|
const timeUntilReset = getTimeUntilMidnight(); |
|
|
|
|
|
res.json({ |
|
|
success: true, |
|
|
user: { |
|
|
id: req.user._id, |
|
|
username: req.user.username, |
|
|
email: req.user.email, |
|
|
apikey: req.user.apikey, |
|
|
profileUrl: req.user.profileUrl, |
|
|
premium: req.user.premium, |
|
|
premiumExpiredAt: req.user.premiumExpiredAt, |
|
|
limit: req.user.limit, |
|
|
requests: userTotalRequestCount, |
|
|
requestsToday: userTodayRequestCount, |
|
|
lastReset: req.user.lastReset, |
|
|
nextResetTime: getNextMidnight(), |
|
|
hoursUntilReset: timeUntilReset.hours, |
|
|
minutesUntilReset: timeUntilReset.minutes, |
|
|
secondsUntilReset: timeUntilReset.seconds, |
|
|
role: req.user.role, |
|
|
userRole: req.user.userRole, |
|
|
roleExpiresAt: req.user.userRoleExpiresAt, |
|
|
createdAt: req.user.createdAt, |
|
|
tempBanned: req.user.tempBanned, |
|
|
tempBanUntil: req.user.tempBanUntil, |
|
|
tempBanReason: req.user.tempBanReason |
|
|
} |
|
|
}); |
|
|
} catch (error) { |
|
|
console.error('Profile fetch error:', error); |
|
|
res.status(500).json({ |
|
|
success: false, |
|
|
error: 'Failed to load profile' |
|
|
}); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.get('/api/user/activities', authenticate, async (req, res) => { |
|
|
try { |
|
|
const activities = await Activity.find({ userId: req.user._id }) |
|
|
.sort({ timestamp: -1 }) |
|
|
.limit(50); |
|
|
|
|
|
const requestLogs = await RequestLog.find({ userId: req.user._id }) |
|
|
.sort({ timestamp: -1 }) |
|
|
.limit(20); |
|
|
|
|
|
res.json({ |
|
|
success: true, |
|
|
activities: activities.map(activity => ({ |
|
|
type: activity.type, |
|
|
description: activity.description, |
|
|
timestamp: activity.timestamp, |
|
|
timeAgo: getTimeAgo(activity.timestamp), |
|
|
metadata: activity.metadata |
|
|
})), |
|
|
requestLogs: requestLogs.map(log => ({ |
|
|
endpoint: log.endpoint, |
|
|
timestamp: log.timestamp, |
|
|
timeAgo: getTimeAgo(log.timestamp), |
|
|
success: log.success, |
|
|
ipAddress: log.ipAddress, |
|
|
limitDeducted: log.limitDeducted |
|
|
})) |
|
|
}); |
|
|
} catch (error) { |
|
|
console.error('Activities fetch error:', error); |
|
|
res.status(500).json({ success: false, error: 'Internal server error' }); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.get('/api/user/usage-stats', authenticate, async (req, res) => { |
|
|
try { |
|
|
const userId = req.user._id.toString(); |
|
|
|
|
|
const todayStart = new Date(); |
|
|
todayStart.setHours(0, 0, 0, 0); |
|
|
|
|
|
const userTodayRequests = await RequestLog.countDocuments({ |
|
|
userId, |
|
|
timestamp: { $gte: todayStart } |
|
|
}); |
|
|
|
|
|
const userTotalRequests = await RequestLog.countDocuments({ |
|
|
userId |
|
|
}); |
|
|
|
|
|
const topEndpoints = await RequestLog.aggregate([ |
|
|
{ $match: { userId } }, |
|
|
{ $group: { _id: '$endpoint', count: { $sum: 1 } } }, |
|
|
{ $sort: { count: -1 } }, |
|
|
{ $limit: 10 } |
|
|
]); |
|
|
|
|
|
const recentIPs = await RequestLog.aggregate([ |
|
|
{ $match: { userId } }, |
|
|
{ $group: { |
|
|
_id: '$ipAddress', |
|
|
count: { $sum: 1 }, |
|
|
lastUsed: { $max: '$timestamp' } |
|
|
}}, |
|
|
{ $sort: { lastUsed: -1 } }, |
|
|
{ $limit: 10 } |
|
|
]); |
|
|
|
|
|
const dailyUsage = await RequestLog.aggregate([ |
|
|
{ $match: { |
|
|
userId, |
|
|
timestamp: { $gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) } |
|
|
}}, |
|
|
{ $group: { |
|
|
_id: { $dateToString: { format: '%Y-%m-%d', date: '$timestamp' } }, |
|
|
requests: { $sum: 1 }, |
|
|
limitUsed: { $sum: '$limitDeducted' } |
|
|
}}, |
|
|
{ $sort: { _id: 1 } } |
|
|
]); |
|
|
|
|
|
res.json({ |
|
|
success: true, |
|
|
stats: { |
|
|
totalRequests: userTotalRequests, |
|
|
todayRequests: userTodayRequests, |
|
|
topEndpoints: topEndpoints.map(ep => ({ |
|
|
endpoint: ep._id, |
|
|
count: ep.count |
|
|
})), |
|
|
recentIPs: recentIPs.map(ip => ({ |
|
|
address: ip._id, |
|
|
count: ip.count, |
|
|
lastUsed: ip.lastUsed, |
|
|
timeAgo: getTimeAgo(ip.lastUsed) |
|
|
})), |
|
|
dailyUsage: dailyUsage.map(day => ({ |
|
|
date: day._id, |
|
|
requests: day.requests, |
|
|
limitUsed: day.limitUsed |
|
|
})) |
|
|
} |
|
|
}); |
|
|
} catch (error) { |
|
|
console.error('Usage stats error:', error); |
|
|
res.status(500).json({ success: false, error: 'Internal server error' }); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.put('/api/user/profile', authenticate, async (req, res) => { |
|
|
try { |
|
|
const { username, profileUrl, customApiKey } = req.body; |
|
|
let updated = false; |
|
|
|
|
|
if (username && username !== req.user.username) { |
|
|
const existing = await User.findOne({ username }); |
|
|
if (existing) { |
|
|
return res.status(400).json({ success: false, error: 'Username already taken' }); |
|
|
} |
|
|
|
|
|
const oldUsername = req.user.username; |
|
|
req.user.username = username; |
|
|
updated = true; |
|
|
|
|
|
await logActivity(req.user._id, req.user.username, 'profile_updated', `Username changed from ${oldUsername} to ${username}`, { |
|
|
oldUsername, |
|
|
newUsername: username |
|
|
}); |
|
|
} |
|
|
|
|
|
if (profileUrl !== undefined && profileUrl !== req.user.profileUrl) { |
|
|
req.user.profileUrl = profileUrl; |
|
|
updated = true; |
|
|
|
|
|
await logActivity(req.user._id, req.user.username, 'profile_updated', 'Profile URL updated', { |
|
|
newProfileUrl: profileUrl |
|
|
}); |
|
|
} |
|
|
|
|
|
if (customApiKey && (req.user.premium || req.user.role === 'admin')) { |
|
|
const existing = await User.findOne({ apikey: customApiKey }); |
|
|
if (existing && existing._id.toString() !== req.user._id.toString()) { |
|
|
return res.status(400).json({ success: false, error: 'API key already in use' }); |
|
|
} |
|
|
|
|
|
const oldApiKey = req.user.apikey; |
|
|
req.user.apikey = customApiKey; |
|
|
updated = true; |
|
|
|
|
|
await logActivity(req.user._id, req.user.username, 'api_key_updated', 'Custom API key set', { |
|
|
oldApiKey, |
|
|
newApiKey: customApiKey |
|
|
}); |
|
|
} |
|
|
|
|
|
if (updated) { |
|
|
await req.user.save(); |
|
|
} |
|
|
|
|
|
res.json({ success: true, message: 'Profile updated successfully' }); |
|
|
} catch (error) { |
|
|
console.error('Profile update error:', error); |
|
|
res.status(500).json({ success: false, error: 'Internal server error' }); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.delete('/api/user/account', authenticate, async (req, res) => { |
|
|
try { |
|
|
if (req.user.role === 'admin') { |
|
|
return res.status(403).json({ success: false, error: 'Cannot delete admin account' }); |
|
|
} |
|
|
|
|
|
await User.deleteOne({ _id: req.user._id }); |
|
|
await Activity.deleteMany({ userId: req.user._id }); |
|
|
res.json({ success: true, message: 'Account deleted successfully' }); |
|
|
} catch (error) { |
|
|
console.error('Account deletion error:', error); |
|
|
res.status(500).json({ success: false, error: 'Internal server error' }); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.get('/api/stats/all', async (req, res) => { |
|
|
try { |
|
|
const totalUsers = await User.countDocuments(); |
|
|
const totalRequests = await RequestLog.countDocuments(); |
|
|
|
|
|
const todayStart = new Date(); |
|
|
todayStart.setHours(0, 0, 0, 0); |
|
|
|
|
|
const todayRequests = await RequestLog.countDocuments({ |
|
|
timestamp: { $gte: todayStart } |
|
|
}); |
|
|
|
|
|
const topEndpoints = await RequestLog.aggregate([ |
|
|
{ $group: { |
|
|
_id: '$endpoint', |
|
|
count: { $sum: 1 }, |
|
|
successCount: { |
|
|
$sum: { $cond: ['$success', 1, 0] } |
|
|
}, |
|
|
failCount: { |
|
|
$sum: { $cond: ['$success', 0, 1] } |
|
|
} |
|
|
}}, |
|
|
{ $sort: { count: -1 } }, |
|
|
{ $limit: 10 } |
|
|
]); |
|
|
|
|
|
const successCount = await RequestLog.countDocuments({ success: true }); |
|
|
const failCount = await RequestLog.countDocuments({ success: false }); |
|
|
const successRate = totalRequests > 0 ? ((successCount / totalRequests) * 100).toFixed(2) : 0; |
|
|
const failRate = totalRequests > 0 ? ((failCount / totalRequests) * 100).toFixed(2) : 0; |
|
|
|
|
|
const sevenDaysAgo = new Date(); |
|
|
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7); |
|
|
|
|
|
const dailyRequests = await RequestLog.aggregate([ |
|
|
{ |
|
|
$match: { |
|
|
timestamp: { $gte: sevenDaysAgo } |
|
|
} |
|
|
}, |
|
|
{ |
|
|
$group: { |
|
|
_id: { $dateToString: { format: '%Y-%m-%d', date: '$timestamp' } }, |
|
|
count: { $sum: 1 }, |
|
|
success: { $sum: { $cond: ['$success', 1, 0] } }, |
|
|
fail: { $sum: { $cond: ['$success', 0, 1] } } |
|
|
} |
|
|
}, |
|
|
{ $sort: { _id: 1 } } |
|
|
]); |
|
|
|
|
|
const yesterday = new Date(); |
|
|
yesterday.setHours(yesterday.getHours() - 24); |
|
|
|
|
|
const hourlyRequests = await RequestLog.aggregate([ |
|
|
{ |
|
|
$match: { |
|
|
timestamp: { $gte: yesterday } |
|
|
} |
|
|
}, |
|
|
{ |
|
|
$group: { |
|
|
_id: { $dateToString: { format: '%Y-%m-%d %H:00', date: '$timestamp' } }, |
|
|
count: { $sum: 1 } |
|
|
} |
|
|
}, |
|
|
{ $sort: { _id: 1 } } |
|
|
]); |
|
|
|
|
|
res.json({ |
|
|
success: true, |
|
|
stats: { |
|
|
totalUsers, |
|
|
totalRequests, |
|
|
todayRequests, |
|
|
topEndpoints: topEndpoints.map(ep => ({ |
|
|
endpoint: ep._id, |
|
|
count: ep.count, |
|
|
successCount: ep.successCount, |
|
|
failCount: ep.failCount |
|
|
})), |
|
|
successRate: parseFloat(successRate), |
|
|
failRate: parseFloat(failRate), |
|
|
successCount, |
|
|
failCount, |
|
|
dailyRequests: dailyRequests.map(day => ({ |
|
|
date: day._id, |
|
|
count: day.count, |
|
|
success: day.success, |
|
|
fail: day.fail |
|
|
})), |
|
|
hourlyRequests: hourlyRequests.map(hour => ({ |
|
|
hour: hour._id, |
|
|
count: hour.count |
|
|
})) |
|
|
} |
|
|
}); |
|
|
} catch (error) { |
|
|
console.error('All stats error:', error); |
|
|
res.status(500).json({ |
|
|
success: false, |
|
|
error: 'Failed to load statistics' |
|
|
}); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.post('/api/user/regenerate-key', authenticate, async (req, res) => { |
|
|
try { |
|
|
if (!req.user.premium && req.user.role !== 'admin') { |
|
|
return res.status(403).json({ success: false, error: 'Premium feature only' }); |
|
|
} |
|
|
|
|
|
const oldApikey = req.user.apikey; |
|
|
|
|
|
if (req.user.role === 'admin') { |
|
|
req.user.apikey = 'DHX-M3SA'; |
|
|
} else { |
|
|
req.user.apikey = generateApiKey(); |
|
|
} |
|
|
|
|
|
await req.user.save(); |
|
|
|
|
|
await logActivity(req.user._id, req.user.username, 'api_key_generated', 'API key regenerated', { |
|
|
oldApikey, |
|
|
newApikey: req.user.apikey |
|
|
}); |
|
|
|
|
|
res.json({ success: true, apikey: req.user.apikey }); |
|
|
} catch (error) { |
|
|
console.error('API key regeneration error:', error); |
|
|
res.status(500).json({ success: false, error: 'Internal server error' }); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.post('/api/user/redeem', authenticate, async (req, res) => { |
|
|
try { |
|
|
const { code } = req.body; |
|
|
|
|
|
if (!code) { |
|
|
return res.status(400).json({ |
|
|
success: false, |
|
|
error: 'Redeem code is required' |
|
|
}); |
|
|
} |
|
|
|
|
|
const redeemCode = await RedeemCode.findOne({ code }); |
|
|
|
|
|
if (!redeemCode) { |
|
|
return res.status(400).json({ |
|
|
success: false, |
|
|
error: 'This redeem code is not available anymore or never exist' |
|
|
}); |
|
|
} |
|
|
|
|
|
if (redeemCode.used) { |
|
|
return res.status(400).json({ |
|
|
success: false, |
|
|
error: 'This redeem code has already been used' |
|
|
}); |
|
|
} |
|
|
|
|
|
if (new Date() > redeemCode.codeExpired) { |
|
|
return res.status(400).json({ |
|
|
success: false, |
|
|
error: 'This redeem code is not available anymore or never exist' |
|
|
}); |
|
|
} |
|
|
|
|
|
let benefits = []; |
|
|
|
|
|
if (redeemCode.type === 'limit' || redeemCode.type === 'both') { |
|
|
req.user.limit += redeemCode.limitValue; |
|
|
benefits.push(`+${redeemCode.limitValue} API requests`); |
|
|
} |
|
|
|
|
|
if (redeemCode.type === 'premium' || redeemCode.type === 'both') { |
|
|
req.user.premium = true; |
|
|
req.user.premiumExpiredAt = redeemCode.premiumExpired; |
|
|
benefits.push(`Premium activated until ${redeemCode.premiumExpired.toLocaleString()}`); |
|
|
} |
|
|
|
|
|
if (redeemCode.type === 'premium' && redeemCode.limitValue > 0) { |
|
|
req.user.limit += redeemCode.limitValue; |
|
|
benefits.push(`+${redeemCode.limitValue} API requests (bonus)`); |
|
|
} |
|
|
|
|
|
redeemCode.used = true; |
|
|
redeemCode.usedBy = req.user.username; |
|
|
|
|
|
await req.user.save(); |
|
|
await redeemCode.save(); |
|
|
|
|
|
await logActivity(req.user._id, req.user.username, 'redeem_code_used', `Redeem code used: ${benefits.join(', ')}`, { |
|
|
code: code, |
|
|
type: redeemCode.type, |
|
|
benefits |
|
|
}); |
|
|
|
|
|
res.json({ |
|
|
success: true, |
|
|
message: 'Redeem code used successfully', |
|
|
benefits |
|
|
}); |
|
|
} catch (error) { |
|
|
console.error('Redeem code error:', error); |
|
|
res.status(500).json({ success: false, error: 'Internal server error' }); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.get('/api/admin/stats', authenticate, async (req, res) => { |
|
|
try { |
|
|
if (req.user.role !== 'admin') { |
|
|
return res.status(403).json({ success: false, error: 'Admin access required' }); |
|
|
} |
|
|
|
|
|
const totalUsers = await User.countDocuments(); |
|
|
const pendingUsers = await PendingVerification.countDocuments(); |
|
|
const totalRequests = await RequestLog.countDocuments(); |
|
|
|
|
|
const todayStart = new Date(); |
|
|
todayStart.setHours(0, 0, 0, 0); |
|
|
|
|
|
const todayRequests = await RequestLog.countDocuments({ |
|
|
timestamp: { $gte: todayStart } |
|
|
}); |
|
|
|
|
|
res.json({ |
|
|
success: true, |
|
|
stats: { |
|
|
totalUsers, |
|
|
pendingUsers, |
|
|
totalRequests, |
|
|
todayRequests |
|
|
} |
|
|
}); |
|
|
} catch (error) { |
|
|
console.error('Admin stats error:', error); |
|
|
res.status(500).json({ success: false, error: 'Internal server error' }); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.get('/api/server-stats', async (req, res) => { |
|
|
try { |
|
|
const totalUsers = await User.countDocuments(); |
|
|
const regularUsers = await User.countDocuments({ |
|
|
$and: [ |
|
|
{ premium: false }, |
|
|
{ $or: [{ userRole: null }, { userRole: { $exists: false } }] } |
|
|
] |
|
|
}); |
|
|
const premiumUsers = await User.countDocuments({ premium: true }); |
|
|
|
|
|
const cheapCount = await User.countDocuments({ userRole: 'cheap' }); |
|
|
const premiumRoleCount = await User.countDocuments({ userRole: 'premium' }); |
|
|
const vipCount = await User.countDocuments({ userRole: 'vip' }); |
|
|
const supremeCount = await User.countDocuments({ userRole: 'supreme' }); |
|
|
|
|
|
let dbSize = '0 MB'; |
|
|
let storageSize = '0 GB'; |
|
|
|
|
|
try { |
|
|
const dbStats = await mongoose.connection.db.stats(); |
|
|
dbSize = (dbStats.dataSize / 1024 / 1024).toFixed(2) + ' MB'; |
|
|
storageSize = (dbStats.storageSize / 1024 / 1024 / 1024).toFixed(2) + ' GB'; |
|
|
} catch (dbError) { |
|
|
console.log('DB stats not available:', dbError.message); |
|
|
} |
|
|
|
|
|
const memUsage = process.memoryUsage(); |
|
|
const ramUsage = (memUsage.heapUsed / 1024 / 1024).toFixed(2) + ' MB'; |
|
|
|
|
|
const cpuUsage = process.cpuUsage(); |
|
|
const cpuPercent = ((cpuUsage.user + cpuUsage.system) / 1000000).toFixed(2); |
|
|
|
|
|
res.json({ |
|
|
success: true, |
|
|
stats: { |
|
|
totalUsers, |
|
|
regularUsers, |
|
|
premiumUsers, |
|
|
roleDistribution: { |
|
|
regular: regularUsers, |
|
|
cheap: cheapCount, |
|
|
premium: premiumRoleCount, |
|
|
vip: vipCount, |
|
|
supreme: supremeCount |
|
|
}, |
|
|
database: { |
|
|
size: dbSize |
|
|
}, |
|
|
system: { |
|
|
ramUsage: ramUsage, |
|
|
cpuUsage: cpuPercent, |
|
|
storageUsed: storageSize |
|
|
} |
|
|
} |
|
|
}); |
|
|
} catch (error) { |
|
|
console.error('Server stats error:', error); |
|
|
res.status(500).json({ |
|
|
success: false, |
|
|
error: 'Internal server error', |
|
|
details: error.message |
|
|
}); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.post('/api/admin/set-role', authenticate, async (req, res) => { |
|
|
try { |
|
|
if (req.user.role !== 'admin') { |
|
|
return res.status(403).json({ success: false, error: 'Admin access required' }); |
|
|
} |
|
|
|
|
|
const { userId, roleName, customApiKey } = req.body; |
|
|
|
|
|
if (!userId || !roleName) { |
|
|
return res.status(400).json({ success: false, error: 'User ID and role name required' }); |
|
|
} |
|
|
|
|
|
const user = await User.findById(userId); |
|
|
if (!user) { |
|
|
return res.status(404).json({ success: false, error: 'User not found' }); |
|
|
} |
|
|
|
|
|
if (user.role === 'admin') { |
|
|
return res.status(403).json({ success: false, error: 'Cannot modify admin users' }); |
|
|
} |
|
|
|
|
|
const roleConfig = { |
|
|
cheap: { limit: 500, premium: true }, |
|
|
premium: { limit: 1500, premium: true }, |
|
|
vip: { limit: 2500, premium: true }, |
|
|
supreme: { limit: 3000, premium: true } |
|
|
}; |
|
|
|
|
|
if (!roleConfig[roleName]) { |
|
|
return res.status(400).json({ success: false, error: 'Invalid role name' }); |
|
|
} |
|
|
|
|
|
const expiresAt = new Date(); |
|
|
expiresAt.setMonth(expiresAt.getMonth() + 1); |
|
|
|
|
|
let finalApiKey = user.apikey; |
|
|
if (customApiKey && customApiKey.trim()) { |
|
|
const existing = await User.findOne({ apikey: customApiKey }); |
|
|
if (existing && existing._id.toString() !== userId) { |
|
|
return res.status(400).json({ success: false, error: 'API key already in use' }); |
|
|
} |
|
|
user.apikey = customApiKey; |
|
|
finalApiKey = customApiKey; |
|
|
} |
|
|
|
|
|
user.userRole = roleName; |
|
|
user.userRoleExpiresAt = expiresAt; |
|
|
user.limit = roleConfig[roleName].limit; |
|
|
user.premium = roleConfig[roleName].premium; |
|
|
user.premiumExpiredAt = expiresAt; |
|
|
|
|
|
await user.save(); |
|
|
|
|
|
const userRole = await UserRole.findOne({ userId: user._id }); |
|
|
if (userRole) { |
|
|
userRole.roleName = roleName; |
|
|
userRole.customApiKey = customApiKey || null; |
|
|
userRole.expiresAt = expiresAt; |
|
|
userRole.createdBy = req.user.username; |
|
|
await userRole.save(); |
|
|
} else { |
|
|
await UserRole.create({ |
|
|
userId: user._id, |
|
|
roleName, |
|
|
customApiKey: customApiKey || null, |
|
|
expiresAt, |
|
|
createdBy: req.user.username |
|
|
}); |
|
|
} |
|
|
|
|
|
await logActivity(user._id, user.username, 'role_assigned', `Role ${roleName} assigned by admin`, { |
|
|
roleName, |
|
|
limit: user.limit, |
|
|
expiresAt, |
|
|
customApiKey: customApiKey || 'none', |
|
|
assignedBy: req.user.username |
|
|
}); |
|
|
|
|
|
res.json({ |
|
|
success: true, |
|
|
message: `Role ${roleName} assigned successfully`, |
|
|
expiresAt, |
|
|
newLimit: user.limit, |
|
|
apiKey: user.apikey |
|
|
}); |
|
|
} catch (error) { |
|
|
console.error('Set role error:', error); |
|
|
res.status(500).json({ success: false, error: 'Internal server error' }); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.get('/api/admin/users', authenticate, async (req, res) => { |
|
|
try { |
|
|
if (req.user.role !== 'admin') { |
|
|
return res.status(403).json({ success: false, error: 'Admin access required' }); |
|
|
} |
|
|
|
|
|
const users = await User.find({}, { |
|
|
password: 0 |
|
|
}).sort({ createdAt: -1 }); |
|
|
|
|
|
res.json({ |
|
|
success: true, |
|
|
users: users.map(user => ({ |
|
|
id: user._id, |
|
|
username: user.username, |
|
|
email: user.email, |
|
|
role: user.role, |
|
|
userRole: user.userRole, |
|
|
userRoleExpiresAt: user.userRoleExpiresAt, |
|
|
premium: user.premium, |
|
|
premiumExpiredAt: user.premiumExpiredAt, |
|
|
banned: user.banned, |
|
|
tempBanned: user.tempBanned, |
|
|
tempBanUntil: user.tempBanUntil, |
|
|
tempBanReason: user.tempBanReason, |
|
|
limit: user.limit, |
|
|
requests: user.requests, |
|
|
requestsToday: user.requestsToday, |
|
|
createdAt: user.createdAt, |
|
|
ipAddress: user.ipAddress |
|
|
})) |
|
|
}); |
|
|
} catch (error) { |
|
|
console.error('Admin users fetch error:', error); |
|
|
res.status(500).json({ success: false, error: 'Internal server error' }); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.post('/api/admin/temp-ban', authenticate, async (req, res) => { |
|
|
try { |
|
|
if (req.user.role !== 'admin') { |
|
|
return res.status(403).json({ success: false, error: 'Admin access required' }); |
|
|
} |
|
|
|
|
|
const { userId, banUntil, reason } = req.body; |
|
|
|
|
|
if (!userId || !banUntil) { |
|
|
return res.status(400).json({ |
|
|
success: false, |
|
|
error: 'User ID and ban until date are required' |
|
|
}); |
|
|
} |
|
|
|
|
|
const user = await User.findById(userId); |
|
|
if (!user) { |
|
|
return res.status(404).json({ success: false, error: 'User not found' }); |
|
|
} |
|
|
|
|
|
if (user.role === 'admin') { |
|
|
return res.status(403).json({ success: false, error: 'Cannot ban admin users' }); |
|
|
} |
|
|
|
|
|
const banUntilDate = new Date(banUntil); |
|
|
if (banUntilDate <= new Date()) { |
|
|
return res.status(400).json({ |
|
|
success: false, |
|
|
error: 'Ban until date must be in the future' |
|
|
}); |
|
|
} |
|
|
|
|
|
user.tempBanned = true; |
|
|
user.tempBanUntil = banUntilDate; |
|
|
user.tempBanReason = reason || 'No reason provided'; |
|
|
|
|
|
await user.save(); |
|
|
|
|
|
await logActivity(user._id, user.username, 'temp_banned', `User temporarily banned until ${banUntilDate.toLocaleString()}`, { |
|
|
bannedBy: req.user.username, |
|
|
banUntil: banUntilDate, |
|
|
reason: user.tempBanReason |
|
|
}); |
|
|
|
|
|
res.json({ |
|
|
success: true, |
|
|
message: `User ${user.username} temporarily banned until ${banUntilDate.toLocaleString()}` |
|
|
}); |
|
|
} catch (error) { |
|
|
console.error('Temp ban error:', error); |
|
|
res.status(500).json({ success: false, error: 'Internal server error' }); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.post('/api/admin/remove-temp-ban', authenticate, async (req, res) => { |
|
|
try { |
|
|
if (req.user.role !== 'admin') { |
|
|
return res.status(403).json({ success: false, error: 'Admin access required' }); |
|
|
} |
|
|
|
|
|
const { userId } = req.body; |
|
|
|
|
|
if (!userId) { |
|
|
return res.status(400).json({ |
|
|
success: false, |
|
|
error: 'User ID is required' |
|
|
}); |
|
|
} |
|
|
|
|
|
const user = await User.findById(userId); |
|
|
if (!user) { |
|
|
return res.status(404).json({ success: false, error: 'User not found' }); |
|
|
} |
|
|
|
|
|
user.tempBanned = false; |
|
|
user.tempBanUntil = null; |
|
|
user.tempBanReason = null; |
|
|
|
|
|
await user.save(); |
|
|
|
|
|
await logActivity(user._id, user.username, 'ban_removed', 'Temporary ban removed by admin', { |
|
|
removedBy: req.user.username |
|
|
}); |
|
|
|
|
|
res.json({ |
|
|
success: true, |
|
|
message: `Temporary ban removed for user ${user.username}` |
|
|
}); |
|
|
} catch (error) { |
|
|
console.error('Remove temp ban error:', error); |
|
|
res.status(500).json({ success: false, error: 'Internal server error' }); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.post('/api/admin/redeem-code', authenticate, async (req, res) => { |
|
|
try { |
|
|
if (req.user.role !== 'admin') { |
|
|
return res.status(403).json({ success: false, error: 'Admin access required' }); |
|
|
} |
|
|
|
|
|
const { type, limitValue, codeExpired, premiumExpired } = req.body; |
|
|
|
|
|
if (!type || !codeExpired) { |
|
|
return res.status(400).json({ |
|
|
success: false, |
|
|
error: 'Type and code expiration date are required' |
|
|
}); |
|
|
} |
|
|
|
|
|
const codeExpiredDate = new Date(codeExpired); |
|
|
if (codeExpiredDate <= new Date()) { |
|
|
return res.status(400).json({ |
|
|
success: false, |
|
|
error: 'Code expiration date must be in the future' |
|
|
}); |
|
|
} |
|
|
|
|
|
if (type === 'limit' && (!limitValue || limitValue <= 0)) { |
|
|
return res.status(400).json({ |
|
|
success: false, |
|
|
error: 'Limit value must be greater than 0 for Limit Only type' |
|
|
}); |
|
|
} |
|
|
|
|
|
if (type === 'premium' && !premiumExpired) { |
|
|
return res.status(400).json({ |
|
|
success: false, |
|
|
error: 'Premium expiration date is required for Premium Only type' |
|
|
}); |
|
|
} |
|
|
|
|
|
if (type === 'both') { |
|
|
if (!limitValue || limitValue <= 0) { |
|
|
return res.status(400).json({ |
|
|
success: false, |
|
|
error: 'Limit value must be greater than 0 for Both type' |
|
|
}); |
|
|
} |
|
|
if (!premiumExpired) { |
|
|
return res.status(400).json({ |
|
|
success: false, |
|
|
error: 'Premium expiration date is required for Both type' |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
let premiumExpiredDate = null; |
|
|
if (premiumExpired) { |
|
|
premiumExpiredDate = new Date(premiumExpired); |
|
|
if (premiumExpiredDate <= new Date()) { |
|
|
return res.status(400).json({ |
|
|
success: false, |
|
|
error: 'Premium expiration date must be in the future' |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
const code = crypto.randomBytes(4).toString('hex').toUpperCase(); |
|
|
|
|
|
const redeemCode = new RedeemCode({ |
|
|
code, |
|
|
type, |
|
|
limitValue: parseInt(limitValue) || 0, |
|
|
codeExpired: codeExpiredDate, |
|
|
premiumExpired: premiumExpiredDate, |
|
|
createdBy: req.user.username |
|
|
}); |
|
|
|
|
|
await redeemCode.save(); |
|
|
|
|
|
await logActivity(req.user._id, req.user.username, 'redeem_code_created', `Created redeem code: ${code}`, { |
|
|
code, |
|
|
type, |
|
|
limitValue, |
|
|
codeExpired: codeExpiredDate, |
|
|
premiumExpired: premiumExpiredDate |
|
|
}); |
|
|
|
|
|
res.json({ success: true, code }); |
|
|
} catch (error) { |
|
|
console.error('Redeem code creation error:', error); |
|
|
res.status(500).json({ success: false, error: 'Internal server error' }); |
|
|
} |
|
|
}); |
|
|
|
|
|
app.use('/api', checkBanned); |
|
|
app.use('/api', apiRateLimit); |
|
|
|
|
|
let pluginsDir = path.join(__dirname, "plugins"); |
|
|
let isJavaScriptFile = (fileName) => /\.js$/.test(fileName); |
|
|
global.plugins = {}; |
|
|
|
|
|
const loadPlugins = () => { |
|
|
if (!fs.existsSync(pluginsDir)) { |
|
|
console.log('Plugins directory not found, creating...'); |
|
|
try { |
|
|
fs.mkdirSync(pluginsDir, { recursive: true }); |
|
|
} catch (error) { |
|
|
console.error('Error creating plugins directory:', error); |
|
|
} |
|
|
return; |
|
|
} |
|
|
|
|
|
for (let pluginFile of fs.readdirSync(pluginsDir).filter(isJavaScriptFile)) { |
|
|
try { |
|
|
delete require.cache[require.resolve(path.join(pluginsDir, pluginFile))]; |
|
|
global.plugins[pluginFile] = require(path.join(pluginsDir, pluginFile)); |
|
|
} catch (error) { |
|
|
console.error(`Error loading plugin ${pluginFile}:`, error.message); |
|
|
delete global.plugins[pluginFile]; |
|
|
} |
|
|
} |
|
|
|
|
|
console.log('Loaded plugins:', Object.keys(global.plugins)); |
|
|
}; |
|
|
|
|
|
global.reload = (event, filename) => { |
|
|
if (/\.js$/.test(filename)) { |
|
|
let fullFilePath = path.join(pluginsDir, filename); |
|
|
if (fullFilePath in require.cache) { |
|
|
delete require.cache[fullFilePath]; |
|
|
if (fs.existsSync(fullFilePath)) { |
|
|
console.log(`♻️ Re-requiring plugin '${filename}'`); |
|
|
} else { |
|
|
console.log(`🗑️ Deleted plugin '${filename}'`); |
|
|
return delete global.plugins[filename]; |
|
|
} |
|
|
} else { |
|
|
console.log(`🔁 Requiring new plugin '${filename}'`); |
|
|
} |
|
|
|
|
|
let errorCheck = syntaxError(fs.readFileSync(fullFilePath), filename); |
|
|
if (errorCheck) { |
|
|
console.error(`❌ Syntax error while loading '${filename}':\n${errorCheck}`); |
|
|
} else { |
|
|
try { |
|
|
global.plugins[filename] = require(fullFilePath); |
|
|
reloadHandler(); |
|
|
} catch (error) { |
|
|
console.error(`Error loading plugin ${filename}:`, error); |
|
|
} finally { |
|
|
global.plugins = Object.fromEntries(Object.entries(global.plugins).sort(([a], [b]) => a.localeCompare(b))); |
|
|
} |
|
|
} |
|
|
} |
|
|
}; |
|
|
|
|
|
const reloadHandler = () => { |
|
|
const routes = []; |
|
|
|
|
|
Object.keys(global.plugins).forEach(file => { |
|
|
const plugin = global.plugins[file]; |
|
|
if (plugin && plugin.enabled && plugin.routes && plugin.handler) { |
|
|
plugin.routes.forEach(route => { |
|
|
routes.push({ route, plugin, file }); |
|
|
}); |
|
|
} |
|
|
}); |
|
|
|
|
|
routes.forEach(({ route, plugin, file }) => { |
|
|
const method = (plugin.type || 'get').toLowerCase(); |
|
|
if (app[method] && typeof app[method] === 'function') { |
|
|
app[method](`/${route}`, (req, res, next) => { |
|
|
req.limitDeduction = plugin.limit || 1; |
|
|
next(); |
|
|
}, validateApiKey, async (req, res) => { |
|
|
const startTime = Date.now(); |
|
|
try { |
|
|
await plugin.handler(req, res); |
|
|
|
|
|
await RequestLog.create({ |
|
|
userId: req.apiUser._id, |
|
|
username: req.apiUser.username, |
|
|
apikey: req.apiUser.apikey, |
|
|
endpoint: route, |
|
|
ipAddress: req.realIP, |
|
|
userAgent: req.get('User-Agent'), |
|
|
success: true, |
|
|
responseTime: Date.now() - startTime, |
|
|
limitDeducted: req.limitDeducted || 1 |
|
|
}); |
|
|
} catch (error) { |
|
|
console.error(`Plugin ${plugin.name} error:`, error); |
|
|
|
|
|
await RequestLog.create({ |
|
|
userId: req.apiUser._id, |
|
|
username: req.apiUser.username, |
|
|
apikey: req.apiUser.apikey, |
|
|
endpoint: route, |
|
|
ipAddress: req.realIP, |
|
|
userAgent: req.get('User-Agent'), |
|
|
success: false, |
|
|
responseTime: Date.now() - startTime, |
|
|
limitDeducted: req.limitDeducted || 1 |
|
|
}); |
|
|
|
|
|
if (!res.headersSent) { |
|
|
res.status(500).json({ |
|
|
success: false, |
|
|
error: 'Plugin execution failed' |
|
|
}); |
|
|
} |
|
|
} |
|
|
}); |
|
|
console.log(`✅ Registered route: ${method.toUpperCase()} /${route} from ${file}`); |
|
|
} |
|
|
}); |
|
|
}; |
|
|
|
|
|
Object.freeze(global.reload); |
|
|
fs.watch(pluginsDir, global.reload); |
|
|
|
|
|
setInterval(async () => { |
|
|
const now = new Date(); |
|
|
|
|
|
if (now.getHours() === 0 && now.getMinutes() === 0) { |
|
|
console.log('Running daily limit reset at midnight...'); |
|
|
|
|
|
try { |
|
|
const users = await User.find({}); |
|
|
|
|
|
for (const user of users) { |
|
|
await resetUserLimitIfNeeded(user); |
|
|
} |
|
|
|
|
|
console.log('Daily limit reset completed for all users'); |
|
|
} catch (error) { |
|
|
console.error('Daily reset error:', error); |
|
|
} |
|
|
} |
|
|
}, 60000); |
|
|
|
|
|
initializeAdmin(); |
|
|
loadPlugins(); |
|
|
reloadHandler(); |
|
|
app.use('/', tmpRouter); |
|
|
|
|
|
|
|
|
app.use((err, req, res, next) => { |
|
|
console.error('Unhandled error:', err); |
|
|
res.status(500).json({ |
|
|
success: false, |
|
|
error: 'Internal server error' |
|
|
}); |
|
|
}); |
|
|
|
|
|
const PORT = process.env.PORT || 7860; |
|
|
app.listen(PORT, () => { |
|
|
console.log(`DashX API Server running on port ${PORT}`); |
|
|
console.log('=== ADMIN ACCOUNT INFO ==='); |
|
|
console.log('Username: HERXA'); |
|
|
console.log('Password: #HIDDEN#'); |
|
|
console.log('API Key: #HIDDEN#'); |
|
|
console.log('Limit: 9999'); |
|
|
console.log('========================='); |
|
|
console.log('Database connected successfully'); |
|
|
console.log('Plugin auto-reload enabled'); |
|
|
console.log('Daily reset cron job started'); |
|
|
}); |