- 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.
939 lines
23 KiB
Markdown
939 lines
23 KiB
Markdown
# 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
|
|
<template>
|
|
<div class="deposit-page">
|
|
<!-- ... existing deposit UI ... -->
|
|
|
|
<div class="promo-section">
|
|
<label>Promo Code (Optional)</label>
|
|
<div class="promo-input-group">
|
|
<input
|
|
v-model="promoCode"
|
|
placeholder="Enter promo code"
|
|
@input="validatePromo"
|
|
/>
|
|
<button @click="applyPromo" :disabled="!promoCode">
|
|
Apply
|
|
</button>
|
|
</div>
|
|
|
|
<div v-if="promoResult.valid" class="promo-success">
|
|
✓ {{ promoResult.promotion.name }}: +${{ promoResult.bonusAmount.toFixed(2) }} bonus!
|
|
</div>
|
|
|
|
<div v-if="promoResult.error" class="promo-error">
|
|
{{ promoResult.error }}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Show total with bonus -->
|
|
<div class="deposit-summary">
|
|
<div>Deposit Value: ${{ depositValue.toFixed(2) }}</div>
|
|
<div v-if="promoResult.bonusAmount > 0" class="bonus-line">
|
|
Bonus: +${{ promoResult.bonusAmount.toFixed(2) }}
|
|
</div>
|
|
<div class="total-line">
|
|
Total: ${{ (depositValue + promoResult.bonusAmount).toFixed(2) }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
const promoCode = ref('');
|
|
const promoResult = ref({ valid: false, bonusAmount: 0 });
|
|
|
|
const validatePromo = async () => {
|
|
if (!promoCode.value) {
|
|
promoResult.value = { valid: false, bonusAmount: 0 };
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await axios.post('/api/config/promotions/validate', {
|
|
code: promoCode.value,
|
|
amount: depositValue.value
|
|
});
|
|
|
|
if (response.data.valid) {
|
|
promoResult.value = response.data;
|
|
} else {
|
|
promoResult.value = { valid: false, error: response.data.message };
|
|
}
|
|
} catch (error) {
|
|
promoResult.value = { valid: false, error: 'Failed to validate promo code' };
|
|
}
|
|
};
|
|
</script>
|
|
```
|
|
|
|
---
|
|
|
|
## 📋 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) |