initial commit
This commit is contained in:
183
app/api/admin/billing/route.ts
Normal file
183
app/api/admin/billing/route.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { withAdminAuth } from '@/lib/admin-middleware'
|
||||
import { connectDB } from '@/lib/mongodb'
|
||||
import { Billing } from '@/models/billing'
|
||||
import { Transaction } from '@/models/transaction'
|
||||
import { z } from 'zod'
|
||||
|
||||
const BillingUpdateSchema = z.object({
|
||||
payment_status: z.enum(['pending', 'completed', 'failed', 'refunded']).optional(),
|
||||
service_status: z.enum(['active', 'inactive', 'suspended', 'cancelled']).optional(),
|
||||
amount: z.number().min(0).optional(),
|
||||
notes: z.string().optional(),
|
||||
})
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
return withAdminAuth(request, async (req, admin) => {
|
||||
try {
|
||||
await connectDB()
|
||||
|
||||
const { searchParams } = new URL(request.url)
|
||||
const page = parseInt(searchParams.get('page') || '1')
|
||||
const limit = parseInt(searchParams.get('limit') || '20')
|
||||
const search = searchParams.get('search') || ''
|
||||
const serviceType = searchParams.get('serviceType') || ''
|
||||
const paymentStatus = searchParams.get('paymentStatus') || ''
|
||||
const dateFrom = searchParams.get('dateFrom')
|
||||
const dateTo = searchParams.get('dateTo')
|
||||
|
||||
const skip = (page - 1) * limit
|
||||
|
||||
// Build filter query
|
||||
const filter: any = {}
|
||||
|
||||
if (search) {
|
||||
filter.$or = [
|
||||
{ user_email: { $regex: search, $options: 'i' } },
|
||||
{ silicon_id: { $regex: search, $options: 'i' } },
|
||||
{ service_name: { $regex: search, $options: 'i' } },
|
||||
]
|
||||
}
|
||||
|
||||
if (serviceType && serviceType !== 'all') {
|
||||
filter.service_type = serviceType
|
||||
}
|
||||
|
||||
if (paymentStatus && paymentStatus !== 'all') {
|
||||
filter.payment_status = paymentStatus
|
||||
}
|
||||
|
||||
if (dateFrom || dateTo) {
|
||||
filter.created_at = {}
|
||||
if (dateFrom) filter.created_at.$gte = new Date(dateFrom)
|
||||
if (dateTo) filter.created_at.$lte = new Date(dateTo)
|
||||
}
|
||||
|
||||
const [billings, totalBillings] = await Promise.all([
|
||||
Billing.find(filter).sort({ created_at: -1 }).skip(skip).limit(limit),
|
||||
Billing.countDocuments(filter),
|
||||
])
|
||||
|
||||
const totalPages = Math.ceil(totalBillings / limit)
|
||||
|
||||
// Calculate summary statistics for current filter
|
||||
const summaryStats = await Billing.aggregate([
|
||||
{ $match: filter },
|
||||
{
|
||||
$group: {
|
||||
_id: null,
|
||||
totalAmount: { $sum: '$amount' },
|
||||
totalTax: { $sum: '$tax_amount' },
|
||||
totalDiscount: { $sum: '$discount_applied' },
|
||||
completedCount: {
|
||||
$sum: { $cond: [{ $eq: ['$payment_status', 'completed'] }, 1, 0] },
|
||||
},
|
||||
pendingCount: {
|
||||
$sum: { $cond: [{ $eq: ['$payment_status', 'pending'] }, 1, 0] },
|
||||
},
|
||||
failedCount: {
|
||||
$sum: { $cond: [{ $eq: ['$payment_status', 'failed'] }, 1, 0] },
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
return NextResponse.json({
|
||||
billings,
|
||||
pagination: {
|
||||
currentPage: page,
|
||||
totalPages,
|
||||
totalBillings,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1,
|
||||
},
|
||||
summary: summaryStats[0] || {
|
||||
totalAmount: 0,
|
||||
totalTax: 0,
|
||||
totalDiscount: 0,
|
||||
completedCount: 0,
|
||||
pendingCount: 0,
|
||||
failedCount: 0,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Admin billing fetch error:', error)
|
||||
return NextResponse.json({ error: 'Failed to fetch billing data' }, { status: 500 })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
return withAdminAuth(request, async (req, admin) => {
|
||||
try {
|
||||
await connectDB()
|
||||
|
||||
const body = await request.json()
|
||||
const { action, billingId, data } = body
|
||||
|
||||
if (action === 'update') {
|
||||
const validatedData = BillingUpdateSchema.parse(data)
|
||||
|
||||
const billing = await Billing.findByIdAndUpdate(
|
||||
billingId,
|
||||
{
|
||||
...validatedData,
|
||||
updated_at: new Date(),
|
||||
},
|
||||
{ new: true, runValidators: true }
|
||||
)
|
||||
|
||||
if (!billing) {
|
||||
return NextResponse.json({ error: 'Billing record not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
return NextResponse.json({ billing })
|
||||
}
|
||||
|
||||
if (action === 'refund') {
|
||||
const billing = await Billing.findById(billingId)
|
||||
|
||||
if (!billing) {
|
||||
return NextResponse.json({ error: 'Billing record not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
if (billing.payment_status !== 'paid') {
|
||||
return NextResponse.json({ error: 'Can only refund paid payments' }, { status: 400 })
|
||||
}
|
||||
|
||||
// Update billing status
|
||||
billing.payment_status = 'refunded'
|
||||
billing.status = 'cancelled'
|
||||
await billing.save()
|
||||
|
||||
// Create refund transaction
|
||||
const refundTransaction = new Transaction({
|
||||
userId: billing.user_id,
|
||||
type: 'credit',
|
||||
amount: billing.amount,
|
||||
description: `Refund for ${billing.service_name}`,
|
||||
status: 'completed',
|
||||
reference: `refund_${billing._id}`,
|
||||
metadata: {
|
||||
originalBillingId: billing._id,
|
||||
refundedBy: admin.id,
|
||||
refundReason: data.refundReason || 'Admin refund',
|
||||
},
|
||||
})
|
||||
|
||||
await refundTransaction.save()
|
||||
|
||||
return NextResponse.json({
|
||||
billing,
|
||||
transaction: refundTransaction,
|
||||
message: 'Refund processed successfully',
|
||||
})
|
||||
}
|
||||
|
||||
return NextResponse.json({ error: 'Invalid action' }, { status: 400 })
|
||||
} catch (error) {
|
||||
console.error('Admin billing action error:', error)
|
||||
return NextResponse.json({ error: 'Failed to perform billing action' }, { status: 500 })
|
||||
}
|
||||
})
|
||||
}
|
||||
155
app/api/admin/dashboard/route.ts
Normal file
155
app/api/admin/dashboard/route.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { withAdminAuth } from '@/lib/admin-middleware'
|
||||
import { connectDB } from '@/lib/mongodb'
|
||||
import { User } from '@/models/user'
|
||||
import { Transaction } from '@/models/transaction'
|
||||
import { Billing } from '@/models/billing'
|
||||
import { DeveloperRequest } from '@/models/developer-request'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
return withAdminAuth(request, async (req, admin) => {
|
||||
try {
|
||||
await connectDB()
|
||||
|
||||
// Get current date for time-based queries
|
||||
const now = new Date()
|
||||
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1)
|
||||
const startOfWeek = new Date(now.setDate(now.getDate() - now.getDay()))
|
||||
const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||||
|
||||
// User statistics
|
||||
const totalUsers = await User.countDocuments()
|
||||
const activeUsers = await User.countDocuments({ lastLogin: { $gte: startOfMonth } })
|
||||
const newUsersThisMonth = await User.countDocuments({ createdAt: { $gte: startOfMonth } })
|
||||
const verifiedUsers = await User.countDocuments({ isVerified: true })
|
||||
|
||||
// Financial statistics
|
||||
const totalRevenue = await Billing.aggregate([
|
||||
{ $group: { _id: null, total: { $sum: '$amount' } } },
|
||||
])
|
||||
|
||||
const monthlyRevenue = await Billing.aggregate([
|
||||
{ $match: { created_at: { $gte: startOfMonth } } },
|
||||
{ $group: { _id: null, total: { $sum: '$amount' } } },
|
||||
])
|
||||
|
||||
const weeklyRevenue = await Billing.aggregate([
|
||||
{ $match: { created_at: { $gte: startOfWeek } } },
|
||||
{ $group: { _id: null, total: { $sum: '$amount' } } },
|
||||
])
|
||||
|
||||
// Transaction statistics
|
||||
const totalTransactions = await Transaction.countDocuments()
|
||||
const monthlyTransactions = await Transaction.countDocuments({
|
||||
createdAt: { $gte: startOfMonth },
|
||||
})
|
||||
|
||||
// Service statistics
|
||||
const totalServices = await Billing.countDocuments()
|
||||
const activeServices = await Billing.countDocuments({
|
||||
payment_status: 'paid',
|
||||
service_status: 1, // 1 = active
|
||||
})
|
||||
|
||||
// Developer requests
|
||||
const totalDeveloperRequests = await DeveloperRequest.countDocuments()
|
||||
const pendingDeveloperRequests = await DeveloperRequest.countDocuments({
|
||||
status: 'pending',
|
||||
})
|
||||
|
||||
// Recent activity (last 7 days)
|
||||
const recentUsers = await User.find({ createdAt: { $gte: startOfWeek } })
|
||||
.select('name email siliconId createdAt')
|
||||
.sort({ createdAt: -1 })
|
||||
.limit(10)
|
||||
|
||||
const recentTransactions = await Transaction.find({ createdAt: { $gte: startOfWeek } })
|
||||
.populate('userId', 'name email siliconId')
|
||||
.sort({ createdAt: -1 })
|
||||
.limit(10)
|
||||
|
||||
// Service breakdown
|
||||
const serviceBreakdown = await Billing.aggregate([
|
||||
{
|
||||
$group: {
|
||||
_id: '$service_type',
|
||||
count: { $sum: 1 },
|
||||
revenue: { $sum: '$amount' },
|
||||
},
|
||||
},
|
||||
{ $sort: { count: -1 } },
|
||||
])
|
||||
|
||||
// Monthly growth data (last 6 months)
|
||||
const sixMonthsAgo = new Date()
|
||||
sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6)
|
||||
|
||||
const monthlyGrowth = await User.aggregate([
|
||||
{ $match: { createdAt: { $gte: sixMonthsAgo } } },
|
||||
{
|
||||
$group: {
|
||||
_id: {
|
||||
year: { $year: '$createdAt' },
|
||||
month: { $month: '$createdAt' },
|
||||
},
|
||||
count: { $sum: 1 },
|
||||
},
|
||||
},
|
||||
{ $sort: { '_id.year': 1, '_id.month': 1 } },
|
||||
])
|
||||
|
||||
const revenueGrowth = await Billing.aggregate([
|
||||
{ $match: { created_at: { $gte: sixMonthsAgo } } },
|
||||
{
|
||||
$group: {
|
||||
_id: {
|
||||
year: { $year: '$created_at' },
|
||||
month: { $month: '$created_at' },
|
||||
},
|
||||
revenue: { $sum: '$amount' },
|
||||
},
|
||||
},
|
||||
{ $sort: { '_id.year': 1, '_id.month': 1 } },
|
||||
])
|
||||
|
||||
return NextResponse.json({
|
||||
users: {
|
||||
total: totalUsers,
|
||||
active: activeUsers,
|
||||
newThisMonth: newUsersThisMonth,
|
||||
verified: verifiedUsers,
|
||||
verificationRate: totalUsers > 0 ? ((verifiedUsers / totalUsers) * 100).toFixed(1) : 0,
|
||||
},
|
||||
revenue: {
|
||||
total: totalRevenue[0]?.total || 0,
|
||||
monthly: monthlyRevenue[0]?.total || 0,
|
||||
weekly: weeklyRevenue[0]?.total || 0,
|
||||
},
|
||||
transactions: {
|
||||
total: totalTransactions,
|
||||
monthly: monthlyTransactions,
|
||||
},
|
||||
services: {
|
||||
total: totalServices,
|
||||
active: activeServices,
|
||||
breakdown: serviceBreakdown,
|
||||
},
|
||||
developerRequests: {
|
||||
total: totalDeveloperRequests,
|
||||
pending: pendingDeveloperRequests,
|
||||
},
|
||||
recentActivity: {
|
||||
users: recentUsers,
|
||||
transactions: recentTransactions,
|
||||
},
|
||||
growth: {
|
||||
users: monthlyGrowth,
|
||||
revenue: revenueGrowth,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Admin dashboard error:', error)
|
||||
return NextResponse.json({ error: 'Failed to fetch dashboard data' }, { status: 500 })
|
||||
}
|
||||
})
|
||||
}
|
||||
187
app/api/admin/reports/route.ts
Normal file
187
app/api/admin/reports/route.ts
Normal file
@@ -0,0 +1,187 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { withAdminAuth } from '@/lib/admin-middleware'
|
||||
import { connectDB } from '@/lib/mongodb'
|
||||
import { User } from '@/models/user'
|
||||
import { Transaction } from '@/models/transaction'
|
||||
import { Billing } from '@/models/billing'
|
||||
import { DeveloperRequest } from '@/models/developer-request'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
return withAdminAuth(request, async (req, admin) => {
|
||||
try {
|
||||
await connectDB()
|
||||
|
||||
const { searchParams } = new URL(request.url)
|
||||
const reportType = searchParams.get('type')
|
||||
const format = searchParams.get('format') || 'json'
|
||||
const dateFrom = searchParams.get('dateFrom')
|
||||
const dateTo = searchParams.get('dateTo')
|
||||
|
||||
// Build date filter
|
||||
const dateFilter: any = {}
|
||||
if (dateFrom || dateTo) {
|
||||
dateFilter.createdAt = {}
|
||||
if (dateFrom) dateFilter.createdAt.$gte = new Date(dateFrom)
|
||||
if (dateTo) dateFilter.createdAt.$lte = new Date(dateTo)
|
||||
}
|
||||
|
||||
let data: any = {}
|
||||
|
||||
switch (reportType) {
|
||||
case 'users':
|
||||
data = await User.find(dateFilter)
|
||||
.select('-password -refreshToken')
|
||||
.sort({ createdAt: -1 })
|
||||
break
|
||||
|
||||
case 'transactions':
|
||||
data = await Transaction.find(dateFilter)
|
||||
.populate('userId', 'name email siliconId')
|
||||
.sort({ createdAt: -1 })
|
||||
break
|
||||
|
||||
case 'billing':
|
||||
data = await Billing.find(
|
||||
dateFilter.createdAt ? { created_at: dateFilter.createdAt } : {}
|
||||
).sort({ created_at: -1 })
|
||||
break
|
||||
|
||||
case 'developer-requests':
|
||||
const devFilter = dateFilter.createdAt ? { createdAt: dateFilter.createdAt } : {}
|
||||
data = await (DeveloperRequest as any)
|
||||
.find(devFilter)
|
||||
.populate('userId', 'name email siliconId')
|
||||
.sort({ createdAt: -1 })
|
||||
break
|
||||
|
||||
case 'summary':
|
||||
// Generate comprehensive summary report
|
||||
const [users, transactions, billings, developerRequests] = await Promise.all([
|
||||
User.find(dateFilter).select('-password -refreshToken'),
|
||||
Transaction.find(dateFilter).populate('userId', 'name email siliconId'),
|
||||
Billing.find(dateFilter.createdAt ? { created_at: dateFilter.createdAt } : {}),
|
||||
(DeveloperRequest as any)
|
||||
.find(dateFilter.createdAt ? { createdAt: dateFilter.createdAt } : {})
|
||||
.populate('userId', 'name email siliconId'),
|
||||
])
|
||||
|
||||
// Calculate summary statistics
|
||||
const userStats = {
|
||||
total: users.length,
|
||||
verified: users.filter((u) => u.isVerified).length,
|
||||
admins: users.filter((u) => u.role === 'admin').length,
|
||||
totalBalance: users.reduce((sum, u) => sum + (u.balance || 0), 0),
|
||||
}
|
||||
|
||||
const transactionStats = {
|
||||
total: transactions.length,
|
||||
totalAmount: transactions.reduce((sum, t) => sum + t.amount, 0),
|
||||
credits: transactions.filter((t) => t.type === 'credit').length,
|
||||
debits: transactions.filter((t) => t.type === 'debit').length,
|
||||
}
|
||||
|
||||
const billingStats = {
|
||||
total: billings.length,
|
||||
totalRevenue: billings.reduce((sum, b) => sum + b.amount, 0),
|
||||
completed: billings.filter((b) => b.payment_status === 'paid').length,
|
||||
pending: billings.filter((b) => b.payment_status === 'pending').length,
|
||||
}
|
||||
|
||||
const developerStats = {
|
||||
total: developerRequests.length,
|
||||
pending: developerRequests.filter((d) => d.status === 'pending').length,
|
||||
inProgress: developerRequests.filter((d) => d.status === 'in_progress').length,
|
||||
completed: developerRequests.filter((d) => d.status === 'completed').length,
|
||||
}
|
||||
|
||||
data = {
|
||||
summary: {
|
||||
users: userStats,
|
||||
transactions: transactionStats,
|
||||
billing: billingStats,
|
||||
developerRequests: developerStats,
|
||||
},
|
||||
users: users.slice(0, 100), // Limit for performance
|
||||
transactions: transactions.slice(0, 100),
|
||||
billings: billings.slice(0, 100),
|
||||
developerRequests: developerRequests.slice(0, 100),
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
return NextResponse.json({ error: 'Invalid report type' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (format === 'csv') {
|
||||
// Convert to CSV format
|
||||
const csv = convertToCSV(data, reportType)
|
||||
|
||||
return new NextResponse(csv, {
|
||||
headers: {
|
||||
'Content-Type': 'text/csv',
|
||||
'Content-Disposition': `attachment; filename="${reportType}_report_${new Date().toISOString().split('T')[0]}.csv"`,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
reportType,
|
||||
generatedAt: new Date().toISOString(),
|
||||
generatedBy: admin.name,
|
||||
dateRange: { from: dateFrom, to: dateTo },
|
||||
data,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Admin reports error:', error)
|
||||
return NextResponse.json({ error: 'Failed to generate report' }, { status: 500 })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function convertToCSV(data: any, reportType: string): string {
|
||||
if (!Array.isArray(data)) {
|
||||
if (reportType === 'summary' && data.summary) {
|
||||
// For summary reports, create a simple CSV with key metrics
|
||||
const lines = [
|
||||
'Metric,Value',
|
||||
`Total Users,${data.summary.users.total}`,
|
||||
`Verified Users,${data.summary.users.verified}`,
|
||||
`Admin Users,${data.summary.users.admins}`,
|
||||
`Total Balance,${data.summary.users.totalBalance}`,
|
||||
`Total Transactions,${data.summary.transactions.total}`,
|
||||
`Transaction Amount,${data.summary.transactions.totalAmount}`,
|
||||
`Total Billing Records,${data.summary.billing.total}`,
|
||||
`Total Revenue,${data.summary.billing.totalRevenue}`,
|
||||
`Developer Requests,${data.summary.developerRequests.total}`,
|
||||
]
|
||||
return lines.join('\n')
|
||||
}
|
||||
return 'No data available'
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
return 'No data available'
|
||||
}
|
||||
|
||||
// Get headers from first object
|
||||
const headers = Object.keys(data[0]).filter(
|
||||
(key) => typeof data[0][key] !== 'object' || data[0][key] === null
|
||||
)
|
||||
|
||||
// Create CSV content
|
||||
const csvHeaders = headers.join(',')
|
||||
const csvRows = data.map((row) =>
|
||||
headers
|
||||
.map((header) => {
|
||||
const value = row[header]
|
||||
if (value === null || value === undefined) return ''
|
||||
if (typeof value === 'string' && value.includes(',')) {
|
||||
return `"${value.replace(/"/g, '""')}"`
|
||||
}
|
||||
return value
|
||||
})
|
||||
.join(',')
|
||||
)
|
||||
|
||||
return [csvHeaders, ...csvRows].join('\n')
|
||||
}
|
||||
200
app/api/admin/services/route.ts
Normal file
200
app/api/admin/services/route.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { withAdminAuth } from '@/lib/admin-middleware'
|
||||
import { connectDB } from '@/lib/mongodb'
|
||||
import { Billing } from '@/models/billing'
|
||||
import { DeveloperRequest } from '@/models/developer-request'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
return withAdminAuth(request, async (req, admin) => {
|
||||
try {
|
||||
await connectDB()
|
||||
|
||||
const { searchParams } = new URL(request.url)
|
||||
const page = parseInt(searchParams.get('page') || '1')
|
||||
const limit = parseInt(searchParams.get('limit') || '20')
|
||||
const serviceType = searchParams.get('serviceType') || ''
|
||||
const status = searchParams.get('status') || ''
|
||||
const dateFrom = searchParams.get('dateFrom')
|
||||
const dateTo = searchParams.get('dateTo')
|
||||
|
||||
const skip = (page - 1) * limit
|
||||
|
||||
// Build filter query for billing services
|
||||
const billingFilter: any = {}
|
||||
|
||||
if (serviceType && serviceType !== 'all') {
|
||||
billingFilter.service_type = serviceType
|
||||
}
|
||||
|
||||
if (status && status !== 'all') {
|
||||
billingFilter.service_status = status
|
||||
}
|
||||
|
||||
if (dateFrom || dateTo) {
|
||||
billingFilter.created_at = {}
|
||||
if (dateFrom) billingFilter.created_at.$gte = new Date(dateFrom)
|
||||
if (dateTo) billingFilter.created_at.$lte = new Date(dateTo)
|
||||
}
|
||||
|
||||
// Fetch billing services
|
||||
const [billingServices, totalBillingServices] = await Promise.all([
|
||||
Billing.find(billingFilter).sort({ created_at: -1 }).skip(skip).limit(limit),
|
||||
Billing.countDocuments(billingFilter),
|
||||
])
|
||||
|
||||
// Build filter for developer requests
|
||||
const devRequestFilter: any = {}
|
||||
|
||||
if (status && status !== 'all') {
|
||||
devRequestFilter.status = status
|
||||
}
|
||||
|
||||
if (dateFrom || dateTo) {
|
||||
devRequestFilter.createdAt = {}
|
||||
if (dateFrom) devRequestFilter.createdAt.$gte = new Date(dateFrom)
|
||||
if (dateTo) devRequestFilter.createdAt.$lte = new Date(dateTo)
|
||||
}
|
||||
|
||||
// Fetch developer requests
|
||||
const [developerRequests, totalDeveloperRequests] = await Promise.all([
|
||||
(DeveloperRequest as any)
|
||||
.find(devRequestFilter)
|
||||
.populate('userId', 'name email siliconId')
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
(DeveloperRequest as any).countDocuments(devRequestFilter),
|
||||
])
|
||||
|
||||
// Service statistics
|
||||
const serviceStats = await Billing.aggregate([
|
||||
{
|
||||
$group: {
|
||||
_id: '$service_type',
|
||||
count: { $sum: 1 },
|
||||
revenue: { $sum: '$amount' },
|
||||
activeServices: {
|
||||
$sum: { $cond: [{ $eq: ['$service_status', 'active'] }, 1, 0] },
|
||||
},
|
||||
},
|
||||
},
|
||||
{ $sort: { count: -1 } },
|
||||
])
|
||||
|
||||
// Status breakdown
|
||||
const statusBreakdown = await Billing.aggregate([
|
||||
{
|
||||
$group: {
|
||||
_id: '$service_status',
|
||||
count: { $sum: 1 },
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
const totalPages = Math.ceil(Math.max(totalBillingServices, totalDeveloperRequests) / limit)
|
||||
|
||||
return NextResponse.json({
|
||||
billingServices,
|
||||
developerRequests,
|
||||
serviceStats,
|
||||
statusBreakdown,
|
||||
pagination: {
|
||||
currentPage: page,
|
||||
totalPages,
|
||||
totalBillingServices,
|
||||
totalDeveloperRequests,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Admin services fetch error:', error)
|
||||
return NextResponse.json({ error: 'Failed to fetch services data' }, { status: 500 })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
return withAdminAuth(request, async (req, admin) => {
|
||||
try {
|
||||
await connectDB()
|
||||
|
||||
const body = await request.json()
|
||||
const { action, serviceId, serviceType, data } = body
|
||||
|
||||
if (action === 'updateBilling') {
|
||||
const billing = await Billing.findByIdAndUpdate(
|
||||
serviceId,
|
||||
{
|
||||
service_status: data.service_status,
|
||||
updated_at: new Date(),
|
||||
},
|
||||
{ new: true }
|
||||
)
|
||||
|
||||
if (!billing) {
|
||||
return NextResponse.json({ error: 'Service not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
return NextResponse.json({ service: billing })
|
||||
}
|
||||
|
||||
if (action === 'updateDeveloperRequest') {
|
||||
const developerRequest = await (DeveloperRequest as any)
|
||||
.findByIdAndUpdate(
|
||||
serviceId,
|
||||
{
|
||||
status: data.status,
|
||||
assignedDeveloper: data.assignedDeveloper,
|
||||
estimatedCompletionDate: data.estimatedCompletionDate,
|
||||
notes: data.notes,
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
{ new: true }
|
||||
)
|
||||
.populate('userId', 'name email siliconId')
|
||||
|
||||
if (!developerRequest) {
|
||||
return NextResponse.json({ error: 'Developer request not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
return NextResponse.json({ service: developerRequest })
|
||||
}
|
||||
|
||||
if (action === 'cancelService') {
|
||||
if (serviceType === 'billing') {
|
||||
const billing = await Billing.findByIdAndUpdate(
|
||||
serviceId,
|
||||
{
|
||||
service_status: 'cancelled',
|
||||
updated_at: new Date(),
|
||||
},
|
||||
{ new: true }
|
||||
)
|
||||
|
||||
return NextResponse.json({ service: billing })
|
||||
}
|
||||
|
||||
if (serviceType === 'developer') {
|
||||
const updatedDeveloperRequest = await (DeveloperRequest as any)
|
||||
.findByIdAndUpdate(
|
||||
serviceId,
|
||||
{
|
||||
status: 'cancelled',
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
{ new: true }
|
||||
)
|
||||
.populate('userId', 'name email siliconId')
|
||||
|
||||
return NextResponse.json({ service: updatedDeveloperRequest })
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({ error: 'Invalid action or service type' }, { status: 400 })
|
||||
} catch (error) {
|
||||
console.error('Admin service action error:', error)
|
||||
return NextResponse.json({ error: 'Failed to perform service action' }, { status: 500 })
|
||||
}
|
||||
})
|
||||
}
|
||||
117
app/api/admin/settings/route.ts
Normal file
117
app/api/admin/settings/route.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { withAdminAuth } from '@/lib/admin-middleware'
|
||||
import { connectDB } from '@/lib/mongodb'
|
||||
import { User } from '@/models/user'
|
||||
import { z } from 'zod'
|
||||
import { getSystemSettings, updateSystemSettings } from '@/lib/system-settings'
|
||||
|
||||
const SystemSettingsSchema = z.object({
|
||||
maintenanceMode: z.boolean().optional(),
|
||||
registrationEnabled: z.boolean().optional(),
|
||||
emailVerificationRequired: z.boolean().optional(),
|
||||
maxUserBalance: z.number().min(0).optional(),
|
||||
defaultUserRole: z.enum(['user', 'admin']).optional(),
|
||||
systemMessage: z.string().optional(),
|
||||
paymentGatewayEnabled: z.boolean().optional(),
|
||||
developerHireEnabled: z.boolean().optional(),
|
||||
vpsDeploymentEnabled: z.boolean().optional(),
|
||||
kubernetesDeploymentEnabled: z.boolean().optional(),
|
||||
vpnServiceEnabled: z.boolean().optional(),
|
||||
})
|
||||
|
||||
// System settings are now managed by the system-settings service
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
return withAdminAuth(request, async (req, admin) => {
|
||||
try {
|
||||
await connectDB()
|
||||
|
||||
// Get system statistics
|
||||
const totalUsers = await User.countDocuments()
|
||||
const adminUsers = await User.countDocuments({ role: 'admin' })
|
||||
const verifiedUsers = await User.countDocuments({ isVerified: true })
|
||||
const unverifiedUsers = await User.countDocuments({ isVerified: false })
|
||||
|
||||
// Get recent admin activities (mock data for now)
|
||||
const recentActivities = [
|
||||
{
|
||||
id: '1',
|
||||
action: 'User role updated',
|
||||
details: 'Changed user role from user to admin',
|
||||
timestamp: new Date().toISOString(),
|
||||
adminName: admin.name,
|
||||
},
|
||||
]
|
||||
|
||||
return NextResponse.json({
|
||||
settings: await getSystemSettings(),
|
||||
statistics: {
|
||||
totalUsers,
|
||||
adminUsers,
|
||||
verifiedUsers,
|
||||
unverifiedUsers,
|
||||
},
|
||||
recentActivities,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Admin settings fetch error:', error)
|
||||
return NextResponse.json({ error: 'Failed to fetch system settings' }, { status: 500 })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
return withAdminAuth(request, async (req, admin) => {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { action, settings } = body
|
||||
|
||||
if (action === 'updateSettings') {
|
||||
const validatedSettings = SystemSettingsSchema.parse(settings)
|
||||
|
||||
// Update system settings
|
||||
const updatedSettings = {
|
||||
...validatedSettings,
|
||||
lastUpdated: new Date().toISOString(),
|
||||
updatedBy: admin.name,
|
||||
}
|
||||
await updateSystemSettings(updatedSettings)
|
||||
|
||||
return NextResponse.json({
|
||||
settings: await getSystemSettings(),
|
||||
message: 'Settings updated successfully',
|
||||
})
|
||||
}
|
||||
|
||||
if (action === 'clearCache') {
|
||||
// Mock cache clearing - in a real app, this would clear Redis/memory cache
|
||||
return NextResponse.json({
|
||||
message: 'System cache cleared successfully',
|
||||
})
|
||||
}
|
||||
|
||||
if (action === 'backupDatabase') {
|
||||
// Mock database backup - in a real app, this would trigger a backup process
|
||||
return NextResponse.json({
|
||||
message: 'Database backup initiated successfully',
|
||||
backupId: `backup_${Date.now()}`,
|
||||
})
|
||||
}
|
||||
|
||||
if (action === 'sendSystemNotification') {
|
||||
const { message, targetUsers } = body
|
||||
|
||||
// Mock system notification - in a real app, this would send notifications
|
||||
return NextResponse.json({
|
||||
message: `System notification sent to ${targetUsers} users`,
|
||||
notificationId: `notif_${Date.now()}`,
|
||||
})
|
||||
}
|
||||
|
||||
return NextResponse.json({ error: 'Invalid action' }, { status: 400 })
|
||||
} catch (error) {
|
||||
console.error('Admin settings update error:', error)
|
||||
return NextResponse.json({ error: 'Failed to update system settings' }, { status: 500 })
|
||||
}
|
||||
})
|
||||
}
|
||||
163
app/api/admin/users/route.ts
Normal file
163
app/api/admin/users/route.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { withAdminAuth } from '@/lib/admin-middleware'
|
||||
import { connectDB } from '@/lib/mongodb'
|
||||
import { User } from '@/models/user'
|
||||
import { z } from 'zod'
|
||||
|
||||
const UserUpdateSchema = z.object({
|
||||
name: z.string().min(1).optional(),
|
||||
email: z.string().email().optional(),
|
||||
role: z.enum(['user', 'admin']).optional(),
|
||||
isVerified: z.boolean().optional(),
|
||||
balance: z.number().min(0).optional(),
|
||||
})
|
||||
|
||||
const UserCreateSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
email: z.string().email(),
|
||||
password: z.string().min(6),
|
||||
role: z.enum(['user', 'admin']).default('user'),
|
||||
isVerified: z.boolean().default(false),
|
||||
balance: z.number().min(0).default(0),
|
||||
})
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
return withAdminAuth(request, async (req, admin) => {
|
||||
try {
|
||||
await connectDB()
|
||||
|
||||
const { searchParams } = new URL(request.url)
|
||||
const page = parseInt(searchParams.get('page') || '1')
|
||||
const limit = parseInt(searchParams.get('limit') || '20')
|
||||
const search = searchParams.get('search') || ''
|
||||
const role = searchParams.get('role') || ''
|
||||
const verified = searchParams.get('verified') || ''
|
||||
|
||||
const skip = (page - 1) * limit
|
||||
|
||||
// Build filter query
|
||||
const filter: any = {}
|
||||
|
||||
if (search) {
|
||||
filter.$or = [
|
||||
{ name: { $regex: search, $options: 'i' } },
|
||||
{ email: { $regex: search, $options: 'i' } },
|
||||
{ siliconId: { $regex: search, $options: 'i' } },
|
||||
]
|
||||
}
|
||||
|
||||
if (role && role !== 'all') {
|
||||
filter.role = role
|
||||
}
|
||||
|
||||
if (verified && verified !== 'all') {
|
||||
filter.isVerified = verified === 'true'
|
||||
}
|
||||
|
||||
const [users, totalUsers] = await Promise.all([
|
||||
User.find(filter)
|
||||
.select('-password -refreshToken')
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
User.countDocuments(filter),
|
||||
])
|
||||
|
||||
const totalPages = Math.ceil(totalUsers / limit)
|
||||
|
||||
return NextResponse.json({
|
||||
users,
|
||||
pagination: {
|
||||
currentPage: page,
|
||||
totalPages,
|
||||
totalUsers,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Admin users fetch error:', error)
|
||||
return NextResponse.json({ error: 'Failed to fetch users' }, { status: 500 })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
return withAdminAuth(request, async (req, admin) => {
|
||||
try {
|
||||
await connectDB()
|
||||
|
||||
const body = await request.json()
|
||||
const { action, userId, data } = body
|
||||
|
||||
if (action === 'update') {
|
||||
const validatedData = UserUpdateSchema.parse(data)
|
||||
|
||||
const user = await User.findByIdAndUpdate(userId, validatedData, {
|
||||
new: true,
|
||||
runValidators: true,
|
||||
}).select('-password -refreshToken')
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
return NextResponse.json({ user })
|
||||
}
|
||||
|
||||
if (action === 'create') {
|
||||
const validatedData = UserCreateSchema.parse(data)
|
||||
|
||||
// Check if user already exists
|
||||
const existingUser = await User.findOne({ email: validatedData.email })
|
||||
if (existingUser) {
|
||||
return NextResponse.json(
|
||||
{ error: 'User with this email already exists' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// Generate unique Silicon ID
|
||||
const generateSiliconId = () => {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
let result = 'SP'
|
||||
for (let i = 0; i < 8; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
let siliconId = generateSiliconId()
|
||||
while (await User.findOne({ siliconId })) {
|
||||
siliconId = generateSiliconId()
|
||||
}
|
||||
|
||||
const user = new User({
|
||||
...validatedData,
|
||||
siliconId,
|
||||
provider: 'local',
|
||||
})
|
||||
|
||||
await user.save()
|
||||
|
||||
const userResponse = await User.findById(user._id).select('-password -refreshToken')
|
||||
return NextResponse.json({ user: userResponse })
|
||||
}
|
||||
|
||||
if (action === 'delete') {
|
||||
const user = await User.findByIdAndDelete(userId)
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
return NextResponse.json({ message: 'User deleted successfully' })
|
||||
}
|
||||
|
||||
return NextResponse.json({ error: 'Invalid action' }, { status: 400 })
|
||||
} catch (error) {
|
||||
console.error('Admin user action error:', error)
|
||||
return NextResponse.json({ error: 'Failed to perform user action' }, { status: 500 })
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user