initial commit
This commit is contained in:
198
app/api/services/deploy-cloude/route.ts
Normal file
198
app/api/services/deploy-cloude/route.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { authMiddleware } from '@/lib/auth-middleware'
|
||||
import connectDB from '@/lib/mongodb'
|
||||
import { User as UserModel } from '@/models/user'
|
||||
import BillingService from '@/lib/billing-service'
|
||||
import { checkServiceAvailability } from '@/lib/system-settings'
|
||||
|
||||
// VPC subnet mapping based on datacenter
|
||||
const VPC_SUBNET_MAP: { [key: string]: string } = {
|
||||
inmumbaizone2: '3889f7ca-ca19-4851-abc7-5c6b4798b3fe', // Mumbai public subnet
|
||||
inbangalore: 'c17032c9-3cfd-4028-8f2a-f3f5aa8c2976', // Bangalore public subnet
|
||||
innoida: '95c60b59-4925-4b7e-bd05-afbf940d8000', // Delhi/Noida public subnet
|
||||
defra1: '72ea201d-e1e7-4d30-a186-bc709efafad8', // Frankfurt public subnet
|
||||
uslosangeles: '0e253cd1-8ecc-4b65-ae0f-6acbc53b0fb6', // Los Angeles public subnet
|
||||
inbangalore3: 'f687a58b-04f0-4ebe-b583-65788a1d18bf', // Bangalore DC3 public subnet
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
// Check if VPS deployment service is enabled
|
||||
if (!(await checkServiceAvailability('vps'))) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
status: 'error',
|
||||
message: 'VPS deployment service is currently disabled by administrator',
|
||||
},
|
||||
{ status: 503 }
|
||||
)
|
||||
}
|
||||
|
||||
// Check authentication using your auth middleware
|
||||
const user = await authMiddleware(request)
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{ status: 'error', message: 'Unauthorized: Please login to continue' },
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
// Get input data from request
|
||||
const input = await request.json()
|
||||
if (!input) {
|
||||
return NextResponse.json({ status: 'error', message: 'Invalid JSON' }, { status: 400 })
|
||||
}
|
||||
|
||||
// Get API key from environment
|
||||
const UTHO_API_KEY = process.env.UTHO_API_KEY
|
||||
if (!UTHO_API_KEY) {
|
||||
return NextResponse.json(
|
||||
{ status: 'error', message: 'API configuration missing' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
|
||||
// Connect to MongoDB
|
||||
await connectDB()
|
||||
|
||||
// Get user data for billing
|
||||
const userData = await UserModel.findOne({ email: user.email })
|
||||
if (!userData) {
|
||||
return NextResponse.json({ status: 'error', message: 'User not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
const requiredAmount = input.amount || 0
|
||||
|
||||
// Check balance only if amount > 0 (some services might be free)
|
||||
if (requiredAmount > 0) {
|
||||
const currentBalance = userData.balance || 0
|
||||
if (currentBalance < requiredAmount) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
status: 'error',
|
||||
message: `Insufficient balance. Required: ₹${requiredAmount}, Available: ₹${currentBalance}`,
|
||||
code: 'INSUFFICIENT_BALANCE',
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Get VPC subnet based on datacenter
|
||||
const vpcSubnet = VPC_SUBNET_MAP[input.dclocation] || VPC_SUBNET_MAP['inbangalore']
|
||||
|
||||
// Prepare Utho payload - use the exact format from your PHP code
|
||||
const uthoPayload = {
|
||||
dcslug: input.dclocation,
|
||||
planid: input.planid,
|
||||
billingcycle: 'hourly',
|
||||
auth: 'option2',
|
||||
enable_publicip: input.publicip !== false,
|
||||
subnetRequired: false,
|
||||
firewall: '23434645',
|
||||
cpumodel: 'intel',
|
||||
enablebackup: input.backup || false,
|
||||
root_password: input.password,
|
||||
support: 'unmanaged',
|
||||
vpc: vpcSubnet,
|
||||
cloud: [
|
||||
{
|
||||
hostname: input.hostname,
|
||||
},
|
||||
],
|
||||
image: input.image,
|
||||
sshkeys: '',
|
||||
}
|
||||
|
||||
console.log('Sending to Utho API:', uthoPayload)
|
||||
|
||||
// Make API request to Utho
|
||||
const UTHO_API_URL = 'https://api.utho.com/v2/cloud/deploy'
|
||||
const response = await fetch(UTHO_API_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${UTHO_API_KEY}`,
|
||||
'Content-Type': 'application/json',
|
||||
Accept: '*/*',
|
||||
},
|
||||
body: JSON.stringify(uthoPayload),
|
||||
})
|
||||
|
||||
const httpCode = response.status
|
||||
const deploymentSuccess = httpCode >= 200 && httpCode < 300
|
||||
const responseData = await response.json()
|
||||
|
||||
// Add status field like PHP does
|
||||
responseData.status = deploymentSuccess ? 'success' : 'error'
|
||||
|
||||
console.log('Utho API response:', { httpCode, responseData })
|
||||
|
||||
// Process billing using the new comprehensive billing service
|
||||
try {
|
||||
const billingResult = await BillingService.processServiceDeployment({
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
siliconId: userData.siliconId,
|
||||
},
|
||||
service: {
|
||||
name: 'VPS Server',
|
||||
type: 'vps',
|
||||
id: responseData.server_id || responseData.id,
|
||||
instanceId: responseData.server_id || responseData.id,
|
||||
config: {
|
||||
hostname: input.hostname,
|
||||
planid: input.planid,
|
||||
dclocation: input.dclocation,
|
||||
image: input.image,
|
||||
backup: input.backup,
|
||||
publicip: input.publicip,
|
||||
},
|
||||
},
|
||||
amount: requiredAmount,
|
||||
currency: 'INR',
|
||||
cycle: (input.cycle as any) || 'onetime',
|
||||
deploymentSuccess,
|
||||
deploymentResponse: responseData,
|
||||
metadata: {
|
||||
userAgent: request.headers.get('user-agent'),
|
||||
ip: request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip'),
|
||||
uthoPayload,
|
||||
httpCode,
|
||||
},
|
||||
})
|
||||
|
||||
console.log('Billing processed:', {
|
||||
billingId: billingResult.billing.billing_id,
|
||||
transactionId: billingResult.transaction?.transactionId,
|
||||
balanceUpdated: billingResult.balanceUpdated,
|
||||
})
|
||||
} catch (billingError) {
|
||||
console.error('Billing processing failed:', billingError)
|
||||
// Continue even if billing fails, but return error if it's balance-related
|
||||
if (billingError instanceof Error && billingError.message.includes('Insufficient balance')) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
status: 'error',
|
||||
message: billingError.message,
|
||||
code: 'INSUFFICIENT_BALANCE',
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the exact same response format as PHP
|
||||
return NextResponse.json(responseData, { status: httpCode })
|
||||
} catch (error) {
|
||||
console.error('Cloud deployment error:', error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
status: 'error',
|
||||
message: 'Internal server error',
|
||||
details: error instanceof Error ? error.message : 'Unknown error',
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
247
app/api/services/deploy-kubernetes/route.ts
Normal file
247
app/api/services/deploy-kubernetes/route.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { authMiddleware } from '@/lib/auth-middleware'
|
||||
import connectDB from '@/lib/mongodb'
|
||||
import { User as UserModel } from '@/models/user'
|
||||
import BillingService from '@/lib/billing-service'
|
||||
|
||||
// Hardcoded VPC as requested
|
||||
const K8S_VPC = '81b2bd94-61dc-424b-a1ca-ca4c810ed4c4'
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
// Check authentication
|
||||
const user = await authMiddleware(request)
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{ status: 'error', message: 'Unauthorized: Please login to continue' },
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
// Get input data from request
|
||||
const input = await request.json()
|
||||
if (!input) {
|
||||
return NextResponse.json({ status: 'error', message: 'Invalid JSON' }, { status: 400 })
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
if (!input.cluster_label || !input.nodepools) {
|
||||
return NextResponse.json(
|
||||
{ status: 'error', message: 'Cluster label and nodepools are required' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// Get API key from environment
|
||||
const UTHO_API_KEY = process.env.UTHO_API_KEY
|
||||
if (!UTHO_API_KEY) {
|
||||
return NextResponse.json(
|
||||
{ status: 'error', message: 'API configuration missing' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
|
||||
// Connect to MongoDB
|
||||
await connectDB()
|
||||
|
||||
// Get user data for billing
|
||||
const userData = await UserModel.findOne({ email: user.email })
|
||||
if (!userData) {
|
||||
return NextResponse.json({ status: 'error', message: 'User not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
const requiredAmount = input.amount || 0
|
||||
|
||||
// Check balance only if amount > 0
|
||||
if (requiredAmount > 0) {
|
||||
const currentBalance = userData.balance || 0
|
||||
if (currentBalance < requiredAmount) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
status: 'error',
|
||||
message: `Insufficient balance. Required: ₹${requiredAmount}, Available: ₹${currentBalance}`,
|
||||
code: 'INSUFFICIENT_BALANCE',
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare Utho payload for Kubernetes
|
||||
const uthoPayload = {
|
||||
dcslug: 'inmumbaizone2', // Hardcoded as requested
|
||||
cluster_label: input.cluster_label,
|
||||
cluster_version: input.cluster_version || '1.30.0-utho',
|
||||
nodepools: input.nodepools,
|
||||
vpc: K8S_VPC, // Hardcoded VPC
|
||||
network_type: 'publicprivate',
|
||||
cpumodel: 'amd',
|
||||
}
|
||||
|
||||
console.log('Sending to Utho Kubernetes API:', uthoPayload)
|
||||
|
||||
// Make API request to Utho Kubernetes
|
||||
const UTHO_API_URL = 'https://api.utho.com/v2/kubernetes/deploy'
|
||||
const response = await fetch(UTHO_API_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${UTHO_API_KEY}`,
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
body: JSON.stringify(uthoPayload),
|
||||
})
|
||||
|
||||
const httpCode = response.status
|
||||
const deploymentSuccess = httpCode >= 200 && httpCode < 300
|
||||
const responseData = await response.json()
|
||||
|
||||
// Add status field and clusterId for consistency
|
||||
responseData.status = deploymentSuccess ? 'success' : 'error'
|
||||
if (deploymentSuccess && responseData.id) {
|
||||
responseData.clusterId = responseData.id
|
||||
}
|
||||
|
||||
console.log('Utho Kubernetes API response:', { httpCode, responseData })
|
||||
|
||||
// Process billing using the new comprehensive billing service
|
||||
try {
|
||||
const billingResult = await BillingService.processServiceDeployment({
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
siliconId: userData.siliconId,
|
||||
},
|
||||
service: {
|
||||
name: `Kubernetes Cluster - ${input.cluster_label}`,
|
||||
type: 'kubernetes',
|
||||
id: responseData.clusterId || responseData.id,
|
||||
clusterId: responseData.clusterId || responseData.id,
|
||||
config: {
|
||||
cluster_label: input.cluster_label,
|
||||
cluster_version: input.cluster_version,
|
||||
nodepools: input.nodepools,
|
||||
dcslug: 'inmumbaizone2',
|
||||
vpc: K8S_VPC,
|
||||
network_type: 'publicprivate',
|
||||
cpumodel: 'amd',
|
||||
},
|
||||
},
|
||||
amount: requiredAmount,
|
||||
currency: 'INR',
|
||||
cycle: (input.cycle as any) || 'onetime',
|
||||
deploymentSuccess,
|
||||
deploymentResponse: responseData,
|
||||
metadata: {
|
||||
userAgent: request.headers.get('user-agent'),
|
||||
ip: request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip'),
|
||||
uthoPayload,
|
||||
httpCode,
|
||||
},
|
||||
})
|
||||
|
||||
console.log('Billing processed:', {
|
||||
billingId: billingResult.billing.billing_id,
|
||||
transactionId: billingResult.transaction?.transactionId,
|
||||
balanceUpdated: billingResult.balanceUpdated,
|
||||
})
|
||||
} catch (billingError) {
|
||||
console.error('Billing processing failed:', billingError)
|
||||
// Continue even if billing fails, but return error if it's balance-related
|
||||
if (billingError instanceof Error && billingError.message.includes('Insufficient balance')) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
status: 'error',
|
||||
message: billingError.message,
|
||||
code: 'INSUFFICIENT_BALANCE',
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json(responseData, { status: httpCode })
|
||||
} catch (error) {
|
||||
console.error('Kubernetes deployment error:', error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
status: 'error',
|
||||
message: 'Internal server error',
|
||||
details: error instanceof Error ? error.message : 'Unknown error',
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// GET endpoint for downloading kubeconfig
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
// Check authentication
|
||||
const user = await authMiddleware(request)
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{ status: 'error', message: 'Unauthorized: Please login to continue' },
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(request.url)
|
||||
const clusterId = searchParams.get('clusterId')
|
||||
|
||||
if (!clusterId) {
|
||||
return NextResponse.json(
|
||||
{ status: 'error', message: 'Cluster ID is required' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// Get API key from environment
|
||||
const UTHO_API_KEY = process.env.UTHO_API_KEY
|
||||
if (!UTHO_API_KEY) {
|
||||
return NextResponse.json(
|
||||
{ status: 'error', message: 'API configuration missing' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
|
||||
// Download kubeconfig
|
||||
const KUBECONFIG_URL = `https://api.utho.com/v2/kubernetes/${clusterId}/download`
|
||||
const response = await fetch(KUBECONFIG_URL, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${UTHO_API_KEY}`,
|
||||
Accept: 'application/yaml',
|
||||
},
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const kubeconfig = await response.text()
|
||||
|
||||
// Return as downloadable file
|
||||
return new NextResponse(kubeconfig, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/yaml',
|
||||
'Content-Disposition': `attachment; filename="kubeconfig-${clusterId}.yaml"`,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
const errorText = await response.text()
|
||||
console.error('Kubeconfig download failed:', response.status, errorText)
|
||||
return NextResponse.json(
|
||||
{ status: 'error', message: 'Failed to download kubeconfig' },
|
||||
{ status: response.status }
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Kubeconfig download error:', error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
status: 'error',
|
||||
message: 'Internal server error',
|
||||
details: error instanceof Error ? error.message : 'Unknown error',
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
162
app/api/services/deploy-vpn/route.ts
Normal file
162
app/api/services/deploy-vpn/route.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { authMiddleware } from '@/lib/auth-middleware'
|
||||
import connectDB from '@/lib/mongodb'
|
||||
import { User as UserModel } from '@/models/user'
|
||||
import BillingService from '@/lib/billing-service'
|
||||
|
||||
// Define your VPN endpoints
|
||||
const VPN_ENDPOINTS = {
|
||||
america: 'https://wireguard-vpn.3027622.siliconpin.com/vpn',
|
||||
europe: 'https://wireguard.vps20.siliconpin.com/vpn',
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
// Check authentication
|
||||
const user = await authMiddleware(request)
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
// Get data from request body
|
||||
const { orderId, location, plan, amount } = await request.json()
|
||||
|
||||
if (!orderId) {
|
||||
return NextResponse.json({ error: 'Order ID is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
if (!location || !VPN_ENDPOINTS[location as keyof typeof VPN_ENDPOINTS]) {
|
||||
return NextResponse.json({ error: 'Valid VPN location is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
// Connect to MongoDB
|
||||
await connectDB()
|
||||
|
||||
// Get user data for billing
|
||||
const userData = await UserModel.findOne({ email: user.email })
|
||||
if (!userData) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
const requiredAmount = amount || 0
|
||||
|
||||
// Check balance only if amount > 0
|
||||
if (requiredAmount > 0) {
|
||||
const currentBalance = userData.balance || 0
|
||||
if (currentBalance < requiredAmount) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: `Insufficient balance. Required: ₹${requiredAmount}, Available: ₹${currentBalance}`,
|
||||
code: 'INSUFFICIENT_BALANCE',
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Get environment variables
|
||||
const VPN_API_KEY = process.env.VPN_API_KEY
|
||||
if (!VPN_API_KEY) {
|
||||
return NextResponse.json({ error: 'VPN API configuration missing' }, { status: 500 })
|
||||
}
|
||||
|
||||
// Get the endpoint for the selected location
|
||||
const endpoint = VPN_ENDPOINTS[location as keyof typeof VPN_ENDPOINTS]
|
||||
|
||||
// Make API request to the selected VPN endpoint
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-API-Key': VPN_API_KEY,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
new: orderId.toString(),
|
||||
userId: user.id,
|
||||
plan,
|
||||
location,
|
||||
}),
|
||||
})
|
||||
console.log('VPN_API_KEY', VPN_API_KEY)
|
||||
// Handle non-200 responses
|
||||
if (!response.ok) {
|
||||
const errorData = await response.text()
|
||||
throw new Error(`VPN API returned HTTP ${response.status}: ${errorData}`)
|
||||
}
|
||||
|
||||
// Parse the response
|
||||
const vpnData = await response.json()
|
||||
const deploymentSuccess = response.ok && vpnData?.config
|
||||
|
||||
if (!deploymentSuccess) {
|
||||
throw new Error('Invalid response from VPN API: Missing config')
|
||||
}
|
||||
|
||||
// Process billing using the new comprehensive billing service
|
||||
try {
|
||||
const billingResult = await BillingService.processServiceDeployment({
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
siliconId: userData.siliconId,
|
||||
},
|
||||
service: {
|
||||
name: `VPN Service - ${location}`,
|
||||
type: 'vpn',
|
||||
id: orderId.toString(),
|
||||
config: {
|
||||
orderId,
|
||||
location,
|
||||
plan,
|
||||
endpoint: endpoint,
|
||||
},
|
||||
},
|
||||
amount: requiredAmount,
|
||||
currency: 'INR',
|
||||
cycle: 'monthly',
|
||||
deploymentSuccess,
|
||||
deploymentResponse: vpnData,
|
||||
metadata: {
|
||||
userAgent: request.headers.get('user-agent'),
|
||||
ip: request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip'),
|
||||
vpnLocation: location,
|
||||
vpnPlan: plan,
|
||||
},
|
||||
})
|
||||
|
||||
console.log('VPN billing processed:', {
|
||||
billingId: billingResult.billing.billing_id,
|
||||
transactionId: billingResult.transaction?.transactionId,
|
||||
balanceUpdated: billingResult.balanceUpdated,
|
||||
})
|
||||
} catch (billingError) {
|
||||
console.error('Billing processing failed:', billingError)
|
||||
// Continue even if billing fails, but return error if it's balance-related
|
||||
if (billingError instanceof Error && billingError.message.includes('Insufficient balance')) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: billingError.message,
|
||||
code: 'INSUFFICIENT_BALANCE',
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the VPN configuration
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: vpnData,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('VPN deployment error:', error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'VPN deployment failed',
|
||||
details: error instanceof Error ? error.message : 'Unknown error',
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
108
app/api/services/download-hosting-conf/route.ts
Normal file
108
app/api/services/download-hosting-conf/route.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { authMiddleware } from '@/lib/auth-middleware'
|
||||
import connectDB from '@/lib/mongodb'
|
||||
import { User as UserModel } from '@/models/user'
|
||||
import BillingService from '@/lib/billing-service'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
// Check authentication
|
||||
const user = await authMiddleware(request)
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
// Get billing ID from query parameters
|
||||
const { searchParams } = new URL(request.url)
|
||||
const billingId = searchParams.get('billing_id')
|
||||
const amount = parseFloat(searchParams.get('amount') || '0')
|
||||
|
||||
if (!billingId) {
|
||||
return NextResponse.json({ error: 'Billing ID is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
// Connect to MongoDB
|
||||
await connectDB()
|
||||
|
||||
// Get user data for billing
|
||||
const userData = await UserModel.findOne({ email: user.email })
|
||||
if (!userData) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
// Mock hosting configuration (in production, this would come from database)
|
||||
const hostingConfig = {
|
||||
billing_id: billingId,
|
||||
siliconId: user.id,
|
||||
domain: `demo-${billingId}.siliconpin.com`,
|
||||
cp_url: `https://cp-${billingId}.siliconpin.com:2083`,
|
||||
panel: 'cPanel',
|
||||
user_id: `user_${billingId}`,
|
||||
password: `secure_password_${Date.now()}`,
|
||||
server_ip: '192.168.1.100',
|
||||
nameservers: ['ns1.siliconpin.com', 'ns2.siliconpin.com'],
|
||||
ftp_settings: {
|
||||
host: `ftp.demo-${billingId}.siliconpin.com`,
|
||||
username: `user_${billingId}`,
|
||||
port: 21,
|
||||
},
|
||||
database_settings: {
|
||||
host: 'localhost',
|
||||
prefix: `db_${billingId}_`,
|
||||
},
|
||||
ssl_certificate: {
|
||||
enabled: true,
|
||||
type: "Let's Encrypt",
|
||||
auto_renew: true,
|
||||
},
|
||||
}
|
||||
|
||||
const configJson = JSON.stringify(hostingConfig, null, 2)
|
||||
|
||||
// Process billing for hosting configuration download
|
||||
try {
|
||||
await BillingService.processServiceDeployment({
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
siliconId: userData.siliconId,
|
||||
},
|
||||
service: {
|
||||
name: `Hosting Configuration - ${hostingConfig.domain}`,
|
||||
type: 'hosting',
|
||||
id: billingId,
|
||||
config: hostingConfig,
|
||||
},
|
||||
amount: amount,
|
||||
currency: 'INR',
|
||||
cycle: 'onetime',
|
||||
deploymentSuccess: true,
|
||||
deploymentResponse: { configDownloaded: true },
|
||||
metadata: {
|
||||
userAgent: request.headers.get('user-agent'),
|
||||
ip: request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip'),
|
||||
downloadType: 'hosting_config',
|
||||
domain: hostingConfig.domain,
|
||||
},
|
||||
})
|
||||
|
||||
console.log('Hosting config billing processed for:', billingId)
|
||||
} catch (billingError) {
|
||||
console.error('Billing processing failed:', billingError)
|
||||
// Continue with download even if billing fails
|
||||
}
|
||||
|
||||
// Return the config as a downloadable JSON file
|
||||
return new NextResponse(configJson, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Disposition': `attachment; filename="sp_credential_${billingId}.json"`,
|
||||
'Cache-Control': 'no-cache',
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Download error:', error)
|
||||
return NextResponse.json({ error: 'Download failed' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
54
app/api/services/download-kubernetes/route.ts
Normal file
54
app/api/services/download-kubernetes/route.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { authMiddleware } from '@/lib/auth-middleware'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
// Check authentication
|
||||
const user = await authMiddleware(request)
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
// Get cluster ID from query parameters
|
||||
const { searchParams } = new URL(request.url)
|
||||
const clusterId = searchParams.get('cluster_id')
|
||||
|
||||
if (!clusterId) {
|
||||
return NextResponse.json({ error: 'Cluster ID is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
// Mock Kubernetes configuration
|
||||
const kubeConfig = `apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJRWRtTFUzZUNCUXN3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TkRBeE1EUXhOekF6TXpCYUZ3MHpOREF4TURFeE56QXpNekJhTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLLQEKQVFJREFRQUI=
|
||||
server: https://k8s-api.siliconpin.com:6443
|
||||
name: ${clusterId}
|
||||
contexts:
|
||||
- context:
|
||||
cluster: ${clusterId}
|
||||
user: ${clusterId}-admin
|
||||
name: ${clusterId}
|
||||
current-context: ${clusterId}
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: ${clusterId}-admin
|
||||
user:
|
||||
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t
|
||||
client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQ==`
|
||||
|
||||
// Return the config as a downloadable file
|
||||
return new NextResponse(kubeConfig, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'Content-Disposition': `attachment; filename="kubeconfig-${clusterId}.yaml"`,
|
||||
'Cache-Control': 'no-cache',
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Download error:', error)
|
||||
return NextResponse.json({ error: 'Download failed' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
285
app/api/services/hire-developer/route.ts
Normal file
285
app/api/services/hire-developer/route.ts
Normal file
@@ -0,0 +1,285 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { authMiddleware } from '@/lib/auth-middleware'
|
||||
import connectDB from '@/lib/mongodb'
|
||||
import { User as UserModel } from '@/models/user'
|
||||
import { DeveloperRequest, IDeveloperRequest } from '@/models/developer-request'
|
||||
import { Transaction } from '@/models/transaction'
|
||||
import BillingService from '@/lib/billing-service'
|
||||
import { checkServiceAvailability } from '@/lib/system-settings'
|
||||
|
||||
// Schema for developer hire request
|
||||
const HireDeveloperSchema = z.object({
|
||||
planId: z.enum(['hourly', 'daily', 'monthly']),
|
||||
planName: z.string().min(1),
|
||||
planPrice: z.number().positive(),
|
||||
requirements: z.string().min(10, 'Requirements must be at least 10 characters').max(5000),
|
||||
contactInfo: z.object({
|
||||
name: z.string().min(1, 'Name is required'),
|
||||
email: z.string().email('Valid email is required'),
|
||||
phone: z.string().optional(),
|
||||
}),
|
||||
})
|
||||
|
||||
// Schema for response
|
||||
const HireDeveloperResponseSchema = z.object({
|
||||
success: z.boolean(),
|
||||
data: z
|
||||
.object({
|
||||
requestId: z.string(),
|
||||
transactionId: z.string(),
|
||||
status: z.string(),
|
||||
message: z.string(),
|
||||
estimatedResponse: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
error: z
|
||||
.object({
|
||||
message: z.string(),
|
||||
code: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
// Check if developer hire service is enabled
|
||||
if (!(await checkServiceAvailability('developer'))) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: {
|
||||
message: 'Developer hire service is currently disabled by administrator',
|
||||
code: 'SERVICE_DISABLED',
|
||||
},
|
||||
},
|
||||
{ status: 503 }
|
||||
)
|
||||
}
|
||||
|
||||
// Authenticate user
|
||||
const user = await authMiddleware(request)
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'Authentication required', code: 'UNAUTHORIZED' },
|
||||
},
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
await connectDB()
|
||||
|
||||
// Parse and validate request body
|
||||
const body = await request.json()
|
||||
const validatedData = HireDeveloperSchema.parse(body)
|
||||
|
||||
// Get user's current balance from database
|
||||
const userData = await UserModel.findOne({ email: user.email })
|
||||
if (!userData) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'User not found', code: 'USER_NOT_FOUND' },
|
||||
},
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
const currentBalance = userData.balance || 0
|
||||
|
||||
// Check if user has sufficient balance (minimum deposit required)
|
||||
const minimumDeposit = Math.min(validatedData.planPrice * 0.5, 10000) // 50% or max ₹10,000
|
||||
if (currentBalance < minimumDeposit) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: {
|
||||
message: `Insufficient balance. Minimum deposit required: ₹${minimumDeposit}, Available: ₹${currentBalance}`,
|
||||
code: 'INSUFFICIENT_BALANCE',
|
||||
},
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// Create developer request first
|
||||
const developerRequest = new DeveloperRequest({
|
||||
userId: userData._id,
|
||||
planId: validatedData.planId,
|
||||
planName: validatedData.planName,
|
||||
planPrice: validatedData.planPrice,
|
||||
requirements: validatedData.requirements,
|
||||
contactInfo: validatedData.contactInfo,
|
||||
status: 'pending',
|
||||
paymentStatus: 'pending', // Will be updated after billing
|
||||
transactionId: null, // Will be set after transaction is created
|
||||
})
|
||||
|
||||
// Save developer request first
|
||||
const savedRequest = await developerRequest.save()
|
||||
|
||||
// Process billing using the new comprehensive billing service
|
||||
try {
|
||||
const billingResult = await BillingService.processServiceDeployment({
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
siliconId: userData.siliconId,
|
||||
},
|
||||
service: {
|
||||
name: `Developer Hire - ${validatedData.planName}`,
|
||||
type: 'developer_hire',
|
||||
id: savedRequest._id.toString(),
|
||||
config: {
|
||||
planId: validatedData.planId,
|
||||
planName: validatedData.planName,
|
||||
planPrice: validatedData.planPrice,
|
||||
requirements: validatedData.requirements,
|
||||
contactInfo: validatedData.contactInfo,
|
||||
},
|
||||
},
|
||||
amount: minimumDeposit,
|
||||
currency: 'INR',
|
||||
cycle: 'onetime',
|
||||
deploymentSuccess: true, // Developer hire request is always successful
|
||||
deploymentResponse: { requestId: savedRequest._id.toString() },
|
||||
metadata: {
|
||||
userAgent: request.headers.get('user-agent'),
|
||||
ip: request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip'),
|
||||
depositAmount: minimumDeposit,
|
||||
fullPlanPrice: validatedData.planPrice,
|
||||
},
|
||||
})
|
||||
|
||||
// Update developer request with billing info
|
||||
await (DeveloperRequest as any).updateOne(
|
||||
{ _id: savedRequest._id },
|
||||
{
|
||||
$set: {
|
||||
paymentStatus: 'paid',
|
||||
transactionId: billingResult.transaction?._id,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
console.log('Developer hire billing processed:', {
|
||||
requestId: savedRequest._id,
|
||||
billingId: billingResult.billing.billing_id,
|
||||
transactionId: billingResult.transaction?.transactionId,
|
||||
})
|
||||
} catch (billingError) {
|
||||
console.error('Billing processing failed:', billingError)
|
||||
// Rollback developer request if billing fails
|
||||
await (DeveloperRequest as any).deleteOne({ _id: savedRequest._id })
|
||||
|
||||
if (billingError instanceof Error && billingError.message.includes('Insufficient balance')) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: {
|
||||
message: billingError.message,
|
||||
code: 'INSUFFICIENT_BALANCE',
|
||||
},
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
throw new Error('Failed to process payment')
|
||||
}
|
||||
|
||||
const responseData = {
|
||||
success: true,
|
||||
data: {
|
||||
requestId: savedRequest._id.toString(),
|
||||
transactionId: 'processed_via_billing',
|
||||
status: 'pending',
|
||||
message:
|
||||
'Your developer hire request has been submitted successfully! Our team will review your requirements and contact you within 24 hours.',
|
||||
estimatedResponse: '24 hours',
|
||||
},
|
||||
}
|
||||
|
||||
const validatedResponse = HireDeveloperResponseSchema.parse(responseData)
|
||||
return NextResponse.json(validatedResponse, { status: 200 })
|
||||
} catch (error) {
|
||||
console.error('Developer hire error:', error)
|
||||
|
||||
if (error instanceof z.ZodError) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: {
|
||||
message: 'Invalid request data',
|
||||
code: 'VALIDATION_ERROR',
|
||||
details: error.issues,
|
||||
},
|
||||
},
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'Failed to process developer hire request', code: 'INTERNAL_ERROR' },
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// GET endpoint to fetch user's developer requests
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const user = await authMiddleware(request)
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'Authentication required', code: 'UNAUTHORIZED' },
|
||||
},
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
await connectDB()
|
||||
|
||||
const userData = await UserModel.findOne({ email: user.email })
|
||||
if (!userData) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'User not found', code: 'USER_NOT_FOUND' },
|
||||
},
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
// Get user's developer requests
|
||||
const requests = await (DeveloperRequest as any)
|
||||
.find({ userId: userData._id })
|
||||
.sort({ createdAt: -1 })
|
||||
.populate('transactionId')
|
||||
.lean()
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: {
|
||||
requests,
|
||||
total: requests.length,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch developer requests:', error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: { message: 'Failed to fetch requests', code: 'INTERNAL_ERROR' },
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user