248 lines
7.6 KiB
TypeScript
248 lines
7.6 KiB
TypeScript
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 }
|
|
)
|
|
}
|
|
}
|