zurri / src /controllers /chatController.ts
nexusbert's picture
fixed handlers
4068be5
import { Request, Response } from 'express';
import { AppDataSource } from '../config/database';
import { Agent, AgentStatus } from '../entities/Agent';
import { Subscription, SubscriptionStatus } from '../entities/Subscription';
import { Transaction, TransactionType, TransactionStatus } from '../entities/Transaction';
import { ChatProtocolService } from '../services/chatProtocol';
import { WalletService } from '../services/walletService';
const agentRepository = AppDataSource.getRepository(Agent);
const subscriptionRepository = AppDataSource.getRepository(Subscription);
const chatService = new ChatProtocolService();
const walletService = new WalletService();
export class ChatController {
/**
* Send chat message to agent (with optional file uploads)
*/
async sendMessage(req: Request, res: Response) {
try {
const { id: agentId } = req.params;
const { message, conversationId, metadata } = req.body;
const userId = (req as any).user?.id;
// Extract files from multer - support both 'file' (singular) and 'files' (plural)
interface MulterFile {
fieldname: string;
originalname: string;
encoding: string;
mimetype: string;
size: number;
buffer: Buffer;
}
const multerFiles = (req as any).files;
const files = Array.isArray(multerFiles)
? multerFiles as Array<MulterFile>
: multerFiles
? [multerFiles as MulterFile]
: undefined;
// Message is optional if files are provided
if (!message && (!files || files.length === 0)) {
return res.status(400).json({
error: 'Message or files are required'
});
}
const user = (req as any).user;
const isAdmin = user?.isAdmin || false;
// Get agent - admins can test pending/rejected agents, regular users only approved
const agent = await agentRepository.findOne({
where: {
id: agentId,
...(isAdmin ? {} : { status: AgentStatus.APPROVED })
},
});
if (!agent) {
return res.status(404).json({ error: 'Agent not found' });
}
// Admins don't pay for unapproved agents (pending/rejected) - they're testing quality
const isAdminTestingUnapproved = isAdmin && agent.status !== AgentStatus.APPROVED;
const shouldCharge = userId && !isAdminTestingUnapproved;
// Check wallet balance and charge points (first 2 tasks free)
if (shouldCharge) {
const chargeResult = await walletService.chargeForTask(userId, agentId, agent);
if (!chargeResult.success) {
const balance = await walletService.getBalance(userId);
const pointsRequired = Number(agent.pointsPerTask) || 0;
const freeTasksRemaining = await walletService.getFreeTasksRemaining(userId);
return res.status(402).json({
error: 'Insufficient wallet balance',
details: {
balance,
required: pointsRequired,
freeTasksRemaining,
message: freeTasksRemaining > 0
? `You have ${freeTasksRemaining} free task(s) remaining. After that, you need ${pointsRequired} points ($${(pointsRequired * 0.05).toFixed(2)}) per task.`
: `You need ${pointsRequired} points ($${(pointsRequired * 0.05).toFixed(2)}) to use this agent. Please fund your wallet.`,
},
});
}
} else if (isAdminTestingUnapproved) {
// Create a free transaction for admin testing (for tracking purposes)
const wallet = await walletService.getOrCreateWallet(userId);
const transactionRepository = AppDataSource.getRepository(Transaction);
const transaction = transactionRepository.create({
walletId: wallet.id,
userId,
agentId,
type: TransactionType.ADMIN_TEST,
status: TransactionStatus.COMPLETED,
amount: 0,
balanceAfter: Number(wallet.balance),
description: `Admin test of ${agent.name} (${agent.status}) - no charge`,
metadata: {
agentId,
agentName: agent.name,
agentStatus: agent.status,
isAdminTest: true
},
});
await transactionRepository.save(transaction);
}
// Convert multer files to chat protocol format
const chatFiles = files?.map(file => ({
fieldname: file.fieldname,
originalname: file.originalname,
mimetype: file.mimetype,
size: file.size,
buffer: file.buffer,
}));
// Send message via chat protocol
const response = await chatService.sendMessage(agent, {
message: message || '',
conversationId,
userId,
metadata: metadata ? (typeof metadata === 'string' ? JSON.parse(metadata) : metadata) : undefined,
files: chatFiles,
});
res.json(response);
} catch (error: any) {
console.error('Chat error:', error);
res.status(500).json({
error: error.message || 'Failed to send message to agent',
});
}
}
/**
* Get conversation history
*/
async getHistory(req: Request, res: Response) {
try {
const { id: agentId } = req.params;
const { conversationId, limit } = req.query;
const user = (req as any).user;
const userId = user?.id;
const isAdmin = user?.isAdmin || false;
if (!userId) {
return res.status(401).json({ error: 'Authentication required' });
}
// Admins can view history for any agent, regular users only approved agents
const agent = await agentRepository.findOne({
where: {
id: agentId,
...(isAdmin ? {} : { status: AgentStatus.APPROVED })
},
});
if (!agent) {
return res.status(404).json({ error: 'Agent not found' });
}
const messages = await chatService.getConversationHistory(
agentId,
userId,
conversationId as string,
limit ? Number(limit) : 50
);
res.json({ messages });
} catch (error) {
console.error('Get history error:', error);
res.status(500).json({ error: 'Failed to get conversation history' });
}
}
/**
* Check if user has access to agent
*/
private async checkUserAccess(
userId: string,
agentId: string
): Promise<boolean> {
// Check for active subscription
const subscription = await subscriptionRepository.findOne({
where: {
userId,
agentId,
status: SubscriptionStatus.ACTIVE,
isPaymentVerified: true,
},
});
if (!subscription) {
return false;
}
// Check if subscription is expired
if (subscription.expiresAt && subscription.expiresAt < new Date()) {
subscription.status = SubscriptionStatus.EXPIRED;
await subscriptionRepository.save(subscription);
return false;
}
return true;
}
}