| import { Request, Response } from 'express'; |
| import { AppDataSource } from '../config/database'; |
| import { Agent, AgentStatus } from '../entities/Agent'; |
| import { User } from '../entities/User'; |
| import { IpfsService } from '../services/ipfsService'; |
|
|
| const agentRepository = AppDataSource.getRepository(Agent); |
| const ipfsService = new IpfsService(); |
|
|
| interface MulterFile { |
| fieldname: string; |
| originalname: string; |
| encoding: string; |
| mimetype: string; |
| size: number; |
| buffer: Buffer; |
| } |
|
|
| export class AgentController { |
| |
| |
| |
| async listAgents(req: Request, res: Response) { |
| try { |
| const { page = 1, limit = 20, search, sortBy = 'createdAt' } = req.query; |
|
|
| const query = agentRepository |
| .createQueryBuilder('agent') |
| .where('agent.status = :status', { status: AgentStatus.APPROVED }); |
|
|
| if (search) { |
| query.andWhere( |
| '(agent.name ILIKE :search OR agent.description ILIKE :search OR agent.category ILIKE :search)', |
| { search: `%${search}%` } |
| ); |
| } |
|
|
| |
| if (req.query.category) { |
| query.andWhere('agent.category = :category', { category: req.query.category }); |
| } |
|
|
| const sortOrder = req.query.sortOrder === 'asc' ? 'ASC' : 'DESC'; |
| query.orderBy(`agent.${sortBy}`, sortOrder); |
|
|
| const skip = (Number(page) - 1) * Number(limit); |
| query.skip(skip).take(Number(limit)); |
|
|
| const [agents, total] = await query.getManyAndCount(); |
|
|
| |
| const formattedAgents = agents.map(agent => ({ |
| ...agent, |
| avatar: agent.avatar, |
| reputation: agent.reputation || 0, |
| capabilities: agent.capabilities || [], |
| |
| endpoint: undefined, |
| })); |
|
|
| res.json({ |
| agents: formattedAgents, |
| pagination: { |
| page: Number(page), |
| limit: Number(limit), |
| total, |
| totalPages: Math.ceil(total / Number(limit)), |
| }, |
| }); |
| } catch (error) { |
| console.error('List agents error:', error); |
| res.status(500).json({ error: 'Failed to list agents' }); |
| } |
| } |
|
|
| |
| |
| |
| async getAgent(req: Request, res: Response) { |
| try { |
| const { id } = req.params; |
| const agent = await agentRepository.findOne({ |
| where: { id }, |
| }); |
|
|
| if (!agent) { |
| return res.status(404).json({ error: 'Agent not found' }); |
| } |
|
|
| |
| const userId = (req as any).user?.id; |
| if (agent.creatorId !== userId && !(req as any).user?.isAdmin) { |
| |
| const { endpoint, ...agentWithoutEndpoint } = agent; |
| res.json({ |
| ...agentWithoutEndpoint, |
| |
| avatar: agentWithoutEndpoint.avatar, |
| reputation: agent.reputation || 0, |
| capabilities: agent.capabilities || [], |
| }); |
| return; |
| } |
|
|
| |
| res.json({ |
| ...agent, |
| reputation: agent.reputation || 0, |
| capabilities: agent.capabilities || [], |
| }); |
| } catch (error) { |
| console.error('Get agent error:', error); |
| res.status(500).json({ error: 'Failed to get agent' }); |
| } |
| } |
|
|
| |
| |
| |
| async createAgent(req: Request, res: Response) { |
| try { |
| const userId = (req as any).user.id; |
| const isAdmin = (req as any).user.isAdmin; |
| |
| |
| const userRepository = AppDataSource.getRepository(User); |
| const user = await userRepository.findOne({ where: { id: userId } }); |
| |
| if (!user) { |
| return res.status(404).json({ error: 'User not found' }); |
| } |
| |
| if (!user.isCreator && !isAdmin) { |
| return res.status(403).json({ |
| error: 'You must be a creator to create agents', |
| message: 'Please become a creator first by calling POST /api/users/become-creator', |
| becomeCreatorEndpoint: '/api/users/become-creator', |
| }); |
| } |
| const avatarFile = (req as any).file as MulterFile | undefined; |
| const { |
| name, |
| description, |
| endpoint, |
| category, |
| capabilities, |
| price, |
| pointsPerTask, |
| isSubscription = false, |
| subscriptionDuration, |
| promptTemplate, |
| metadata, |
| } = req.body; |
|
|
| |
| let parsedCapabilities: string[] = []; |
| if (capabilities) { |
| if (typeof capabilities === 'string') { |
| try { |
| parsedCapabilities = JSON.parse(capabilities); |
| } catch { |
| parsedCapabilities = [capabilities]; |
| } |
| } else if (Array.isArray(capabilities)) { |
| parsedCapabilities = capabilities; |
| } |
| } |
|
|
| |
| if (!name || !description || !endpoint || pointsPerTask === undefined) { |
| return res.status(400).json({ |
| error: 'Missing required fields. Name, description, endpoint, and pointsPerTask are required.', |
| note: 'pointsPerTask is the number of points charged per task (1 point = $0.05)' |
| }); |
| } |
|
|
| |
| if (pointsPerTask < 0) { |
| return res.status(400).json({ error: 'pointsPerTask must be 0 or greater (0 = free agent)' }); |
| } |
|
|
| |
| if (!Array.isArray(parsedCapabilities)) { |
| return res.status(400).json({ error: 'Capabilities must be an array' }); |
| } |
|
|
| |
| let avatarUrl: string | undefined; |
| if (avatarFile) { |
| try { |
| const fileObj = new File( |
| [avatarFile.buffer], |
| avatarFile.originalname || `avatar-${Date.now()}.${avatarFile.mimetype.split('/')[1]}`, |
| { type: avatarFile.mimetype } |
| ); |
|
|
| const upload = await ipfsService.uploadFile(fileObj); |
| avatarUrl = ipfsService.getGatewayUrl(upload.cid); |
| } catch (error) { |
| console.error('Avatar upload error:', error); |
| return res.status(500).json({ error: 'Failed to upload avatar image' }); |
| } |
| } |
|
|
| |
| let ipfsHash: string | undefined; |
| try { |
| ipfsHash = await ipfsService.uploadMetadata({ |
| name, |
| description, |
| endpoint, |
| price, |
| creatorId: userId, |
| category, |
| capabilities: parsedCapabilities, |
| metadata, |
| }); |
| } catch (error) { |
| console.warn('IPFS upload failed, continuing without IPFS hash'); |
| } |
|
|
| |
| const finalPointsPerTask = pointsPerTask !== undefined ? pointsPerTask : (price ? price * 20 : 0); |
|
|
| const agent = agentRepository.create({ |
| name, |
| avatar: avatarUrl, |
| description, |
| endpoint, |
| category, |
| capabilities: parsedCapabilities, |
| price: price || pointsPerTask * 0.05, |
| pointsPerTask: finalPointsPerTask, |
| isSubscription: false, |
| subscriptionDuration: undefined, |
| promptTemplate, |
| metadata: metadata ? (typeof metadata === 'string' ? JSON.parse(metadata) : metadata) : undefined, |
| creatorId: userId, |
| ipfsHash, |
| status: AgentStatus.PENDING, |
| reputation: 0, |
| ratingCount: 0, |
| }); |
|
|
| const savedAgent = await agentRepository.save(agent); |
|
|
| res.status(201).json(savedAgent); |
| } catch (error: any) { |
| console.error('Create agent error:', error); |
| res.status(500).json({ error: error.message || 'Failed to create agent' }); |
| } |
| } |
|
|
| |
| |
| |
| async updateAgent(req: Request, res: Response) { |
| try { |
| const { id } = req.params; |
| const userId = (req as any).user.id; |
| const isAdmin = (req as any).user.isAdmin; |
|
|
| const agent = await agentRepository.findOne({ |
| where: { id }, |
| }); |
|
|
| if (!agent) { |
| return res.status(404).json({ error: 'Agent not found' }); |
| } |
|
|
| |
| if (agent.creatorId !== userId && !isAdmin) { |
| return res.status(403).json({ error: 'Unauthorized' }); |
| } |
|
|
| |
| if (agent.status === AgentStatus.APPROVED && agent.creatorId !== userId) { |
| return res.status(400).json({ |
| error: 'Approved agents cannot be updated. Create a new listing.', |
| }); |
| } |
|
|
| const avatarFile = (req as any).file as MulterFile | undefined; |
| const updateData = req.body; |
| delete updateData.status; |
| delete updateData.creatorId; |
| delete updateData.reputation; |
| delete updateData.ratingCount; |
| delete updateData.avatar; |
|
|
| |
| if (updateData.capabilities) { |
| if (typeof updateData.capabilities === 'string') { |
| try { |
| updateData.capabilities = JSON.parse(updateData.capabilities); |
| } catch { |
| updateData.capabilities = [updateData.capabilities]; |
| } |
| } |
| } |
|
|
| |
| if (updateData.capabilities && !Array.isArray(updateData.capabilities)) { |
| return res.status(400).json({ error: 'Capabilities must be an array' }); |
| } |
|
|
| |
| if (avatarFile) { |
| try { |
| const fileObj = new File( |
| [avatarFile.buffer], |
| avatarFile.originalname || `avatar-${Date.now()}.${avatarFile.mimetype.split('/')[1]}`, |
| { type: avatarFile.mimetype } |
| ); |
|
|
| const upload = await ipfsService.uploadFile(fileObj); |
| updateData.avatar = ipfsService.getGatewayUrl(upload.cid); |
| } catch (error) { |
| console.error('Avatar upload error:', error); |
| return res.status(500).json({ error: 'Failed to upload avatar image' }); |
| } |
| } |
|
|
| Object.assign(agent, updateData); |
|
|
| |
| if (updateData.metadata && typeof updateData.metadata === 'string') { |
| try { |
| updateData.metadata = JSON.parse(updateData.metadata); |
| } catch { |
| |
| } |
| } |
|
|
| |
| if (updateData.name || updateData.description || updateData.price || updateData.category || updateData.capabilities) { |
| try { |
| agent.ipfsHash = await ipfsService.uploadMetadata({ |
| name: agent.name, |
| description: agent.description, |
| endpoint: agent.endpoint, |
| price: agent.price, |
| creatorId: agent.creatorId, |
| category: agent.category, |
| capabilities: agent.capabilities, |
| metadata: agent.metadata, |
| }); |
| } catch (error) { |
| console.warn('IPFS update failed'); |
| } |
| } |
|
|
| const updatedAgent = await agentRepository.save(agent); |
|
|
| res.json(updatedAgent); |
| } catch (error) { |
| console.error('Update agent error:', error); |
| res.status(500).json({ error: 'Failed to update agent' }); |
| } |
| } |
|
|
| |
| |
| |
| async deleteAgent(req: Request, res: Response) { |
| try { |
| const { id } = req.params; |
| const userId = (req as any).user.id; |
| const isAdmin = (req as any).user.isAdmin; |
|
|
| const agent = await agentRepository.findOne({ |
| where: { id }, |
| }); |
|
|
| if (!agent) { |
| return res.status(404).json({ error: 'Agent not found' }); |
| } |
|
|
| if (agent.creatorId !== userId && !isAdmin) { |
| return res.status(403).json({ error: 'Unauthorized' }); |
| } |
|
|
| await agentRepository.remove(agent); |
|
|
| res.json({ message: 'Agent deleted' }); |
| } catch (error) { |
| console.error('Delete agent error:', error); |
| res.status(500).json({ error: 'Failed to delete agent' }); |
| } |
| } |
|
|
| |
| |
| |
| async getMyAgents(req: Request, res: Response) { |
| try { |
| const userId = (req as any).user.id; |
| const agents = await agentRepository.find({ |
| where: { creatorId: userId }, |
| order: { createdAt: 'DESC' }, |
| }); |
|
|
| res.json(agents); |
| } catch (error) { |
| console.error('Get my agents error:', error); |
| res.status(500).json({ error: 'Failed to get your agents' }); |
| } |
| } |
| } |
|
|