# Complete Admin Features Implementation Plan ## 🎯 Project Goal Fully implement all admin panel features so they actually work and enforce the configured settings across the entire TurboTrades platform. --- ## 📋 Phase 1: Maintenance Mode & Core Infrastructure (Priority: CRITICAL) ### 1.1 Maintenance Mode Enforcement **Files to Create/Modify:** - `middleware/maintenance.js` (exists, needs enhancement) - `index.js` (register middleware globally) - `frontend/src/views/MaintenancePage.vue` (new) **Implementation:** ```javascript // Global hook in index.js fastify.addHook('preHandler', async (request, reply) => { // Skip for certain routes (health, auth callback) const skipRoutes = ['/health', '/api/health', '/auth/steam/callback']; if (skipRoutes.includes(request.url)) return; const config = await SiteConfig.getConfig(); if (config.maintenance.enabled) { // Check scheduled times if (config.maintenance.scheduledStart && config.maintenance.scheduledEnd) { const now = new Date(); if (now < config.maintenance.scheduledStart || now > config.maintenance.scheduledEnd) { return; // Outside maintenance window } } // Allow admins and whitelisted users if (request.user?.isAdmin) return; if (config.maintenance.allowedSteamIds?.includes(request.user?.steamId)) return; // Block everyone else return reply.status(503).send({ success: false, error: 'Maintenance Mode', message: config.maintenance.message || 'Site is under maintenance', scheduledEnd: config.maintenance.scheduledEnd }); } }); ``` **Frontend:** - Create maintenance page component - Show maintenance message - Show countdown if scheduled end time exists - Admin bypass indicator **Testing:** - Toggle maintenance ON → non-admin users blocked ✓ - Admin users can still access ✓ - Whitelisted Steam IDs can access ✓ - Scheduled maintenance activates/deactivates ✓ --- ## 📋 Phase 2: Market Settings Integration (Priority: HIGH) ### 2.1 Market Enable/Disable **Files to Modify:** - `routes/market.js` - `frontend/src/views/MarketPage.vue` **Implementation:** ```javascript // Add to ALL market routes const checkMarketEnabled = async (request, reply) => { const config = await SiteConfig.getConfig(); if (!config.market.enabled) { return reply.status(503).send({ success: false, message: 'Marketplace is currently disabled' }); } }; // Apply to routes fastify.get('/listings', { preHandler: [checkMarketEnabled] }, ...); fastify.post('/listings', { preHandler: [authenticate, checkMarketEnabled] }, ...); ``` **Frontend:** - Show "Market Disabled" message when trying to access - Disable market navigation when disabled - Admin can still access for configuration ### 2.2 Price Limits Enforcement **Implementation:** ```javascript // In listing creation const config = await SiteConfig.getConfig(); if (price < config.market.minListingPrice) { return reply.status(400).send({ success: false, message: `Price must be at least $${config.market.minListingPrice}` }); } if (price > config.market.maxListingPrice) { return reply.status(400).send({ success: false, message: `Price cannot exceed $${config.market.maxListingPrice}` }); } ``` **Frontend Validation:** - Add min/max attributes to price inputs - Show real-time validation - Fetch limits from `/api/config/status` ### 2.3 Commission Application **Implementation:** ```javascript // When item is sold const config = await SiteConfig.getConfig(); const commission = salePrice * config.market.commission; const sellerProceeds = salePrice - commission; // Update seller balance seller.balance += sellerProceeds; await seller.save(); // Record commission await Transaction.create({ user: seller._id, type: 'market_sale', amount: sellerProceeds, description: `Sold ${item.name}`, metadata: { itemId: item._id, salePrice: salePrice, commission: commission, commissionRate: config.market.commission } }); // Record platform revenue await Transaction.create({ type: 'platform_commission', amount: commission, description: `Commission from sale of ${item.name}`, metadata: { itemId: item._id, salePrice: salePrice, seller: seller._id } }); ``` ### 2.4 Auto Price Updates **Files to Create:** - `services/priceUpdater.js` - `jobs/updatePrices.js` **Implementation:** ```javascript // services/priceUpdater.js class PriceUpdater { async updateAllPrices() { const config = await SiteConfig.getConfig(); if (!config.market.autoUpdatePrices) return; // Fetch latest prices from external API (Steam, CSGOFloat, etc) const items = await Item.find({ status: 'listed' }); for (const item of items) { const newPrice = await this.fetchLatestPrice(item); if (newPrice && Math.abs(newPrice - item.price) > 0.01) { item.marketPrice = newPrice; // Optionally update listing price within bounds await item.save(); } } } async start() { const config = await SiteConfig.getConfig(); const interval = config.market.priceUpdateInterval || 3600000; // 1 hour default setInterval(() => this.updateAllPrices(), interval); } } ``` --- ## 📋 Phase 3: Trading System Implementation (Priority: HIGH) ### 3.1 Deposit System **Files to Create:** - `routes/trading.js` (new) - `models/Deposit.js` (new) - `services/steamBot.js` (enhance existing) **Database Schema:** ```javascript // models/Deposit.js const depositSchema = new Schema({ user: { type: Schema.Types.ObjectId, ref: 'User', required: true }, items: [{ assetId: String, name: String, marketHashName: String, iconUrl: String, rarity: String, value: Number }], totalValue: { type: Number, required: true }, tradeOfferId: String, botId: String, status: { type: String, enum: ['pending', 'accepted', 'declined', 'cancelled', 'completed', 'failed'], default: 'pending' }, promotion: { id: String, code: String, bonusAmount: Number, bonusPercentage: Number }, bonusApplied: { type: Number, default: 0 }, finalAmount: Number, createdAt: { type: Date, default: Date.now }, completedAt: Date }); ``` **API Endpoints:** ```javascript // POST /api/trading/deposit/initiate // - Check if deposits enabled // - Check minimum deposit // - Validate items // - Check for active promotions // - Create trade offer via Steam bot // - Return trade offer URL // POST /api/trading/deposit/cancel // - Cancel pending deposit // - Cancel trade offer // GET /api/trading/deposit/history // - Get user's deposit history ``` **Implementation:** ```javascript // POST /api/trading/deposit/initiate fastify.post('/deposit/initiate', { preHandler: [authenticate], schema: { body: { type: 'object', required: ['items'], properties: { items: { type: 'array' }, promoCode: { type: 'string' } } } } }, async (request, reply) => { const config = await SiteConfig.getConfig(); // Check if deposits enabled if (!config.trading.enabled || !config.trading.depositEnabled) { return reply.status(503).send({ success: false, message: 'Deposits are currently disabled' }); } const { items, promoCode } = request.body; // Calculate total value let totalValue = items.reduce((sum, item) => sum + item.value, 0); // Check minimum deposit if (totalValue < config.trading.minDeposit) { return reply.status(400).send({ success: false, message: `Minimum deposit is $${config.trading.minDeposit}` }); } // Check max items if (items.length > config.trading.maxItemsPerTrade) { return reply.status(400).send({ success: false, message: `Maximum ${config.trading.maxItemsPerTrade} items per trade` }); } // Check for active promotion let promotion = null; let bonusAmount = 0; if (promoCode) { const promoResult = await validateAndApplyPromotion( request.user, promoCode, totalValue, config ); if (promoResult.valid) { promotion = promoResult.promotion; bonusAmount = promoResult.bonusAmount; } } const finalAmount = totalValue + bonusAmount; // Create deposit record const deposit = await Deposit.create({ user: request.user._id, items, totalValue, bonusApplied: bonusAmount, finalAmount, promotion: promotion ? { id: promotion.id, code: promotion.code, bonusAmount: promotion.bonusAmount, bonusPercentage: promotion.bonusPercentage } : null, status: 'pending' }); // Create trade offer via Steam bot const tradeOffer = await steamBot.createDepositOffer( request.user.tradeUrl, items, deposit._id ); deposit.tradeOfferId = tradeOffer.id; deposit.botId = tradeOffer.botId; await deposit.save(); return reply.send({ success: true, deposit: { id: deposit._id, totalValue, bonusAmount, finalAmount, tradeUrl: tradeOffer.url } }); }); ``` ### 3.2 Withdrawal System **Files to Create:** - `models/Withdrawal.js` - Routes in `routes/trading.js` **Database Schema:** ```javascript const withdrawalSchema = new Schema({ user: { type: Schema.Types.ObjectId, ref: 'User', required: true }, items: [{ itemId: { type: Schema.Types.ObjectId, ref: 'Item' }, name: String, value: Number }], totalValue: { type: Number, required: true }, fee: { type: Number, required: true }, feePercentage: Number, amountDeducted: Number, tradeOfferId: String, botId: String, status: { type: String, enum: ['pending', 'processing', 'sent', 'accepted', 'declined', 'cancelled'], default: 'pending' }, createdAt: { type: Date, default: Date.now }, processedAt: Date }); ``` **Implementation:** ```javascript // POST /api/trading/withdraw/request fastify.post('/withdraw/request', { preHandler: [authenticate] }, async (request, reply) => { const config = await SiteConfig.getConfig(); // Check if withdrawals enabled if (!config.trading.enabled || !config.trading.withdrawEnabled) { return reply.status(503).send({ success: false, message: 'Withdrawals are currently disabled' }); } // Check trade URL if (!request.user.tradeUrl) { return reply.status(400).send({ success: false, message: 'Please set your trade URL first' }); } const { itemIds } = request.body; // Fetch items from user's inventory const items = await Item.find({ _id: { $in: itemIds }, owner: request.user._id, status: 'owned' }); if (items.length !== itemIds.length) { return reply.status(400).send({ success: false, message: 'Some items not found or not owned by you' }); } // Calculate total value const totalValue = items.reduce((sum, item) => sum + item.value, 0); // Check minimum withdrawal if (totalValue < config.trading.minWithdraw) { return reply.status(400).send({ success: false, message: `Minimum withdrawal is $${config.trading.minWithdraw}` }); } // Calculate fee const feePercentage = config.trading.withdrawFee; const fee = totalValue * feePercentage; const amountDeducted = totalValue + fee; // Check user balance (if paying fee from balance) if (request.user.balance < fee) { return reply.status(400).send({ success: false, message: `Insufficient balance for withdrawal fee ($${fee.toFixed(2)})` }); } // Create withdrawal const withdrawal = await Withdrawal.create({ user: request.user._id, items: items.map(item => ({ itemId: item._id, name: item.name, value: item.value })), totalValue, fee, feePercentage, amountDeducted, status: 'pending' }); // Deduct fee from balance request.user.balance -= fee; await request.user.save(); // Mark items as withdrawing await Item.updateMany( { _id: { $in: itemIds } }, { status: 'withdrawing', withdrawalId: withdrawal._id } ); // Create transaction record await Transaction.create({ user: request.user._id, type: 'withdrawal_fee', amount: -fee, description: `Withdrawal fee for ${items.length} items`, metadata: { withdrawalId: withdrawal._id, itemCount: items.length, totalValue } }); return reply.send({ success: true, withdrawal: { id: withdrawal._id, totalValue, fee, status: 'pending', message: 'Withdrawal request created. Admin will process it soon.' } }); }); ``` --- ## 📋 Phase 4: Promotions System (Priority: HIGH) ### 4.1 Promotion Validation & Application **Files to Create:** - `services/promotionService.js` **Implementation:** ```javascript // services/promotionService.js class PromotionService { async validatePromoCode(code, user, amount) { const config = await SiteConfig.getConfig(); // Find promotion const promotion = config.promotions.find(p => p.code === code && p.enabled ); if (!promotion) { return { valid: false, message: 'Invalid promo code' }; } // Check dates const now = new Date(); if (promotion.startDate && now < new Date(promotion.startDate)) { return { valid: false, message: 'Promotion not started yet' }; } if (promotion.endDate && now > new Date(promotion.endDate)) { return { valid: false, message: 'Promotion has expired' }; } // Check new users only if (promotion.newUsersOnly) { const firstDeposit = await Deposit.findOne({ user: user._id, status: 'completed' }); if (firstDeposit) { return { valid: false, message: 'Promotion is for new users only' }; } } // Check minimum deposit if (amount < promotion.minDeposit) { return { valid: false, message: `Minimum deposit for this promo is $${promotion.minDeposit}` }; } // Check usage limits const usageCount = await PromoUsage.countDocuments({ promotionId: promotion.id, user: user._id }); if (promotion.maxUsesPerUser && usageCount >= promotion.maxUsesPerUser) { return { valid: false, message: 'You have used this promotion too many times' }; } const totalUsage = await PromoUsage.countDocuments({ promotionId: promotion.id }); if (promotion.maxTotalUses && totalUsage >= promotion.maxTotalUses) { return { valid: false, message: 'Promotion usage limit reached' }; } // Calculate bonus let bonusAmount = 0; if (promotion.bonusPercentage > 0) { bonusAmount = amount * (promotion.bonusPercentage / 100); } if (promotion.bonusAmount > 0) { bonusAmount = promotion.bonusAmount; } // Apply max bonus cap if (promotion.maxBonus > 0 && bonusAmount > promotion.maxBonus) { bonusAmount = promotion.maxBonus; } return { valid: true, promotion, bonusAmount }; } async recordUsage(promotion, user, deposit) { await PromoUsage.create({ promotionId: promotion.id, promotionCode: promotion.code, user: user._id, depositId: deposit._id, bonusAmount: deposit.bonusApplied, usedAt: new Date() }); } } ``` ### 4.2 Frontend Promo Code Input **Files to Modify:** - `frontend/src/views/DepositPage.vue` **Implementation:** ```vue Promo Code (Optional) Apply ✓ {{ promoResult.promotion.name }}: +${{ promoResult.bonusAmount.toFixed(2) }} bonus! {{ promoResult.error }} Deposit Value: ${{ depositValue.toFixed(2) }} Bonus: +${{ promoResult.bonusAmount.toFixed(2) }} Total: ${{ (depositValue + promoResult.bonusAmount).toFixed(2) }} ``` --- ## 📋 Phase 5: Frontend Integration (Priority: MEDIUM) ### 5.1 Config Status Endpoint **Files to Modify:** - `routes/config.js` **Implementation:** ```javascript // GET /api/config/status - Public endpoint fastify.get('/status', async (request, reply) => { const config = await SiteConfig.getConfig(); return reply.send({ success: true, maintenance: { enabled: config.maintenance.enabled, message: config.maintenance.message, scheduledEnd: config.maintenance.scheduledEnd }, trading: { enabled: config.trading.enabled, depositEnabled: config.trading.depositEnabled, withdrawEnabled: config.trading.withdrawEnabled, minDeposit: config.trading.minDeposit, minWithdraw: config.trading.minWithdraw, withdrawFee: config.trading.withdrawFee }, market: { enabled: config.market.enabled, minListingPrice: config.market.minListingPrice, maxListingPrice: config.market.maxListingPrice, commission: config.market.commission } }); }); ``` ### 5.2 Frontend Status Store **Files to Create:** - `frontend/src/stores/config.js` **Implementation:** ```javascript import { defineStore } from 'pinia'; import axios from '@/utils/axios'; export const useConfigStore = defineStore('config', { state: () => ({ status: { maintenance: { enabled: false }, trading: { enabled: true }, market: { enabled: true } }, loaded: false }), getters: { isMaintenanceMode: (state) => state.status.maintenance.enabled, isTradingEnabled: (state) => state.status.trading.enabled, isMarketEnabled: (state) => state.status.market.enabled, canDeposit: (state) => state.status.trading.depositEnabled, canWithdraw: (state) => state.status.trading.withdrawEnabled }, actions: { async fetchStatus() { try { const response = await axios.get('/api/config/status'); if (response.data.success) { this.status = response.data; this.loaded = true; } } catch (error) { console.error('Failed to fetch config status:', error); } } } }); ``` ### 5.3 Route Guards **Files to Modify:** - `frontend/src/router/index.js` **Implementation:** ```javascript router.beforeEach(async (to, from, next) => { const configStore = useConfigStore(); if (!configStore.loaded) { await configStore.fetchStatus(); } // Block market access if disabled if (to.path.startsWith('/market') && !configStore.isMarketEnabled) { toast.warning('Marketplace is currently disabled'); next({ name: 'Home' }); return; } // Block deposit if disabled if (to.path === '/deposit' && !configStore.canDeposit) { toast.warning('Deposits are currently disabled'); next({ name: 'Home' }); return; } // Block withdraw if disabled if (to.path === '/withdraw' && !configStore.canWithdraw) { toast.warning('Withdrawals are currently disabled'); next({ name: 'Home' }); return; } next(); }); ``` --- ## 📋 Phase 6: Testing & Polish (Priority: MEDIUM) ### 6.1 Admin Testing Checklist - [ ] Toggle maintenance ON → site blocks non-admins - [ ] Toggle maintenance OFF → site accessible again - [ ] Scheduled maintenance activates at correct time - [ ] Disable deposits → deposit page blocked - [ ] Disable withdrawals → withdraw page blocked - [ ] Disable market → market page blocked - [ ] Change min deposit → enforced on deposit attempts - [ ] Change min withdraw → enforced on withdraw attempts - [ ] Change commission → applied to new sales - [ ] Apply promo code → bonus calculated correctly - [ ] Promo usage limit → blocked after max uses - [ ] New user only promo → blocked for existing users ### 6.2 Error Handling - Graceful degradation if config fetch fails - Clear error messages for users - Admin notifications for system issues - Fallback to default values if config missing ### 6.3 Performance Optimization - Cache config in memory (refresh every 5 minutes) - Avoid fetching config on every request - Use Redis for config caching in production - Batch price updates --- ## 📋 Phase 7: Documentation & Deployment (Priority: LOW) ### 7.1 API Documentation - Document all new endpoints - Add request/response examples - Error code reference - Rate limiting info ### 7.2 Admin Guide - How to enable/disable features - How to create promotions - How to schedule maintenance - Common troubleshooting ### 7.3 Deployment Checklist - [ ] Environment variables configured - [ ] Database migrations run - [ ] Steam bot configured - [ ] Price API keys set - [ ] Monitoring enabled - [ ] Backup strategy in place --- ## 🚀 Implementation Order ### Week 1: Critical Infrastructure 1. Day 1-2: Maintenance mode enforcement 2. Day 3-4: Market settings integration 3. Day 5: Config status endpoint & frontend store ### Week 2: Trading System 1. Day 1-3: Deposit system implementation 2. Day 4-5: Withdrawal system implementation ### Week 3: Promotions & Polish 1. Day 1-2: Promotions validation & application 2. Day 3-4: Frontend integration & UI 3. Day 5: Testing & bug fixes ### Week 4: Advanced Features 1. Day 1-2: Auto price updates 2. Day 3: Scheduled maintenance automation 3. Day 4-5: Documentation & final testing --- ## 📊 Success Metrics ### Functionality - [ ] All admin toggles actually work - [ ] Settings are enforced across the platform - [ ] Promotions apply correctly - [ ] Commission is calculated and recorded - [ ] Limits are respected ### Performance - [ ] Config fetch < 50ms - [ ] No performance degradation from checks - [ ] Cache hit rate > 95% ### User Experience - [ ] Clear error messages - [ ] No unexpected blocking - [ ] Smooth transitions - [ ] Proper feedback --- ## 🎯 Final Deliverables 1. **Backend** - Trading routes fully implemented - All admin settings enforced - Promotion system working - Proper error handling - Comprehensive logging 2. **Frontend** - Deposit/withdraw pages functional - Promo code input working - Real-time validation - Config-aware navigation - Clear status indicators 3. **Admin Panel** - All settings actually work - Real-time effect on site - Usage statistics visible - Easy testing/debugging 4. **Documentation** - API documentation complete - Admin guide written - Deployment guide ready - Troubleshooting reference --- **Estimated Total Time:** 3-4 weeks of focused development **Complexity:** Medium-High **Risk Level:** Medium (Steam bot integration, real money handling) **Priority:** High (Core functionality for platform)