nexusbert commited on
Commit
f5035b9
Β·
1 Parent(s): 9c67ac5
src/controllers/agentController.ts CHANGED
@@ -1,6 +1,7 @@
1
  import { Request, Response } from 'express';
2
  import { AppDataSource } from '../config/database';
3
  import { Agent, AgentStatus } from '../entities/Agent';
 
4
  import { IpfsService } from '../services/ipfsService';
5
 
6
  const agentRepository = AppDataSource.getRepository(Agent);
@@ -119,6 +120,23 @@ export class AgentController {
119
  async createAgent(req: Request, res: Response) {
120
  try {
121
  const userId = (req as any).user.id;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  const avatarFile = (req as any).file as MulterFile | undefined;
123
  const {
124
  name,
 
1
  import { Request, Response } from 'express';
2
  import { AppDataSource } from '../config/database';
3
  import { Agent, AgentStatus } from '../entities/Agent';
4
+ import { User } from '../entities/User';
5
  import { IpfsService } from '../services/ipfsService';
6
 
7
  const agentRepository = AppDataSource.getRepository(Agent);
 
120
  async createAgent(req: Request, res: Response) {
121
  try {
122
  const userId = (req as any).user.id;
123
+ const isAdmin = (req as any).user.isAdmin;
124
+
125
+ // Check if user is a creator or admin
126
+ const userRepository = AppDataSource.getRepository(User);
127
+ const user = await userRepository.findOne({ where: { id: userId } });
128
+
129
+ if (!user) {
130
+ return res.status(404).json({ error: 'User not found' });
131
+ }
132
+
133
+ if (!user.isCreator && !isAdmin) {
134
+ return res.status(403).json({
135
+ error: 'You must be a creator to create agents',
136
+ message: 'Please become a creator first by calling POST /api/users/become-creator',
137
+ becomeCreatorEndpoint: '/api/users/become-creator',
138
+ });
139
+ }
140
  const avatarFile = (req as any).file as MulterFile | undefined;
141
  const {
142
  name,
src/controllers/walletController.ts CHANGED
@@ -61,6 +61,14 @@ export class WalletController {
61
 
62
  const paymentReference = `wallet_${userId}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
63
 
 
 
 
 
 
 
 
 
64
  const paymentData = await getPaystackService().initializeTransaction({
65
  email: user.email,
66
  amount: Math.round(amount * 100),
@@ -71,7 +79,7 @@ export class WalletController {
71
  amountInNaira: amount,
72
  amountInDollars: amountInDollars,
73
  },
74
- callback_url: process.env.PAYSTACK_CALLBACK_URL || `${process.env.FRONTEND_URL || 'http://localhost:3000'}/payment/callback`,
75
  });
76
 
77
  // Return payment info for frontend
 
61
 
62
  const paymentReference = `wallet_${userId}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
63
 
64
+ // Paystack callback should point to backend endpoint, which then redirects to frontend
65
+ // For Hugging Face Spaces: https://nexusbert-zurri.hf.space/api/wallet/callback
66
+ // For local: http://localhost:7860/api/wallet/callback
67
+ const backendUrl = process.env.PAYSTACK_CALLBACK_URL
68
+ || (process.env.HF_SPACE_ID || process.env.SPACE_ID
69
+ ? `https://nexusbert-zurri.hf.space/api/wallet/callback`
70
+ : `${process.env.API_BASE_URL || process.env.BACKEND_URL || 'http://localhost:7860'}/api/wallet/callback`);
71
+
72
  const paymentData = await getPaystackService().initializeTransaction({
73
  email: user.email,
74
  amount: Math.round(amount * 100),
 
79
  amountInNaira: amount,
80
  amountInDollars: amountInDollars,
81
  },
82
+ callback_url: backendUrl,
83
  });
84
 
85
  // Return payment info for frontend
src/docs/swagger.ts CHANGED
@@ -198,6 +198,51 @@ export const swaggerSpec = swaggerJSDoc({
198
  },
199
  },
200
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  },
202
  },
203
  security: [{ bearerAuth: [] }],
 
198
  },
199
  },
200
  },
201
+ User: {
202
+ type: 'object',
203
+ properties: {
204
+ id: {
205
+ type: 'string',
206
+ format: 'uuid',
207
+ description: 'User ID',
208
+ },
209
+ email: {
210
+ type: 'string',
211
+ format: 'email',
212
+ description: 'User email address',
213
+ },
214
+ name: {
215
+ type: 'string',
216
+ nullable: true,
217
+ description: 'User display name',
218
+ },
219
+ isAdmin: {
220
+ type: 'boolean',
221
+ default: false,
222
+ description: 'Whether user has admin privileges',
223
+ },
224
+ isCreator: {
225
+ type: 'boolean',
226
+ default: false,
227
+ description: 'Whether user can create and manage agents',
228
+ },
229
+ isActive: {
230
+ type: 'boolean',
231
+ default: true,
232
+ description: 'Whether user account is active',
233
+ },
234
+ createdAt: {
235
+ type: 'string',
236
+ format: 'date-time',
237
+ description: 'Account creation timestamp',
238
+ },
239
+ updatedAt: {
240
+ type: 'string',
241
+ format: 'date-time',
242
+ description: 'Last update timestamp',
243
+ },
244
+ },
245
+ },
246
  },
247
  },
248
  security: [{ bearerAuth: [] }],
src/routes/agentRoutes.ts CHANGED
@@ -107,6 +107,28 @@ router.get('/', optionalAuth, agentController.listAgents.bind(agentController));
107
  // Specific routes must come BEFORE /:id to avoid route conflicts
108
  // These routes are matched first to prevent conflicts with /:id
109
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  /**
111
  * @swagger
112
  * /agents/my/list:
 
107
  // Specific routes must come BEFORE /:id to avoid route conflicts
108
  // These routes are matched first to prevent conflicts with /:id
109
 
110
+ /**
111
+ * @swagger
112
+ * /agents/new:
113
+ * get:
114
+ * summary: Get agent creation form info (frontend route)
115
+ * tags: [Agents]
116
+ * description: This endpoint exists to prevent /new from being caught by /:id route
117
+ * security:
118
+ * - bearerAuth: []
119
+ * responses:
120
+ * 200:
121
+ * description: Agent creation info
122
+ */
123
+ router.get('/new', authenticate, (req, res) => {
124
+ res.json({
125
+ message: 'Use POST /api/agents to create an agent',
126
+ requiresCreator: true,
127
+ endpoint: '/api/agents',
128
+ method: 'POST',
129
+ });
130
+ });
131
+
132
  /**
133
  * @swagger
134
  * /agents/my/list:
src/routes/authRoutes.ts CHANGED
@@ -134,15 +134,16 @@ router.post('/register', authLimiter, async (req: Request, res: Response) => {
134
  { expiresIn: '7d' }
135
  );
136
 
137
- res.status(201).json({
138
- token,
139
- user: {
140
- id: savedUser.id,
141
- email: savedUser.email,
142
- name: savedUser.name,
143
- isAdmin: savedUser.isAdmin,
144
- },
145
- });
 
146
  } catch (error) {
147
  console.error('Register error:', error);
148
  res.status(500).json({ error: 'Failed to register user' });
@@ -186,17 +187,7 @@ router.post('/register', authLimiter, async (req: Request, res: Response) => {
186
  * type: string
187
  * description: JWT authentication token
188
  * user:
189
- * type: object
190
- * properties:
191
- * id:
192
- * type: string
193
- * format: uuid
194
- * email:
195
- * type: string
196
- * name:
197
- * type: string
198
- * isAdmin:
199
- * type: boolean
200
  * 401:
201
  * description: Invalid credentials
202
  * 403:
@@ -272,6 +263,7 @@ router.post('/login', authLimiter, async (req: Request, res: Response) => {
272
  email: user.email,
273
  name: user.name,
274
  isAdmin: user.isAdmin,
 
275
  },
276
  });
277
  } catch (error) {
@@ -336,7 +328,7 @@ router.get('/me', authenticate, async (req: Request, res: Response) => {
336
  const userId = (req as any).user.id;
337
  const user = await userRepository.findOne({
338
  where: { id: userId },
339
- select: ['id', 'email', 'name', 'isAdmin', 'createdAt'],
340
  });
341
 
342
  if (!user) {
@@ -355,6 +347,7 @@ router.get('/me', authenticate, async (req: Request, res: Response) => {
355
  email: user.email,
356
  name: user.name,
357
  isAdmin: user.isAdmin,
 
358
  createdAt: user.createdAt,
359
  wallet: {
360
  balance: Number(wallet.balance),
 
134
  { expiresIn: '7d' }
135
  );
136
 
137
+ res.status(201).json({
138
+ token,
139
+ user: {
140
+ id: savedUser.id,
141
+ email: savedUser.email,
142
+ name: savedUser.name,
143
+ isAdmin: savedUser.isAdmin,
144
+ isCreator: savedUser.isCreator,
145
+ },
146
+ });
147
  } catch (error) {
148
  console.error('Register error:', error);
149
  res.status(500).json({ error: 'Failed to register user' });
 
187
  * type: string
188
  * description: JWT authentication token
189
  * user:
190
+ * $ref: '#/components/schemas/User'
 
 
 
 
 
 
 
 
 
 
191
  * 401:
192
  * description: Invalid credentials
193
  * 403:
 
263
  email: user.email,
264
  name: user.name,
265
  isAdmin: user.isAdmin,
266
+ isCreator: user.isCreator,
267
  },
268
  });
269
  } catch (error) {
 
328
  const userId = (req as any).user.id;
329
  const user = await userRepository.findOne({
330
  where: { id: userId },
331
+ select: ['id', 'email', 'name', 'isAdmin', 'isCreator', 'createdAt'],
332
  });
333
 
334
  if (!user) {
 
347
  email: user.email,
348
  name: user.name,
349
  isAdmin: user.isAdmin,
350
+ isCreator: user.isCreator,
351
  createdAt: user.createdAt,
352
  wallet: {
353
  balance: Number(wallet.balance),
src/routes/userRoutes.ts CHANGED
@@ -1,10 +1,101 @@
1
- import { Router } from 'express';
2
  import { authenticate } from '../middlewares/auth';
3
  import { UserController } from '../controllers/userController';
 
 
4
 
5
  const router = Router();
6
  const userController = new UserController();
 
7
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  router.get('/me/history', authenticate, userController.getMyHistory.bind(userController));
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  export default router;
 
1
+ import { Router, Request, Response } from 'express';
2
  import { authenticate } from '../middlewares/auth';
3
  import { UserController } from '../controllers/userController';
4
+ import { AppDataSource } from '../config/database';
5
+ import { User } from '../entities/User';
6
 
7
  const router = Router();
8
  const userController = new UserController();
9
+ const userRepository = AppDataSource.getRepository(User);
10
 
11
+ /**
12
+ * @swagger
13
+ * /users/me/history:
14
+ * get:
15
+ * summary: Get user's chat history across all agents
16
+ * tags: [Users]
17
+ * security:
18
+ * - bearerAuth: []
19
+ * responses:
20
+ * 200:
21
+ * description: Chat history
22
+ */
23
  router.get('/me/history', authenticate, userController.getMyHistory.bind(userController));
24
 
25
+ /**
26
+ * @swagger
27
+ * /users/become-creator:
28
+ * post:
29
+ * summary: Become a creator (enable agent creation)
30
+ * tags: [Users]
31
+ * description: |
32
+ * Allows a regular user to become a creator and start creating agents.
33
+ * This sets the `isCreator` field to `true` in the database, enabling the user to:
34
+ * - Create new agent listings
35
+ * - Manage their own agents (edit, delete, delist, relist)
36
+ * - View creator dashboard and earnings
37
+ * security:
38
+ * - bearerAuth: []
39
+ * responses:
40
+ * 200:
41
+ * description: Successfully became a creator
42
+ * content:
43
+ * application/json:
44
+ * schema:
45
+ * type: object
46
+ * properties:
47
+ * message:
48
+ * type: string
49
+ * example: "You are now a creator! You can create and manage agents."
50
+ * user:
51
+ * $ref: '#/components/schemas/User'
52
+ * 400:
53
+ * description: User is already a creator
54
+ * content:
55
+ * application/json:
56
+ * schema:
57
+ * type: object
58
+ * properties:
59
+ * error:
60
+ * type: string
61
+ * example: "You are already a creator"
62
+ * 401:
63
+ * description: Unauthorized - authentication required
64
+ * 404:
65
+ * description: User not found
66
+ * 500:
67
+ * description: Server error
68
+ */
69
+ router.post('/become-creator', authenticate, async (req: Request, res: Response) => {
70
+ try {
71
+ const userId = (req as any).user.id;
72
+ const user = await userRepository.findOne({ where: { id: userId } });
73
+
74
+ if (!user) {
75
+ return res.status(404).json({ error: 'User not found' });
76
+ }
77
+
78
+ if (user.isCreator) {
79
+ return res.status(400).json({ error: 'You are already a creator' });
80
+ }
81
+
82
+ user.isCreator = true;
83
+ await userRepository.save(user);
84
+
85
+ res.json({
86
+ message: 'You are now a creator! You can create and manage agents.',
87
+ user: {
88
+ id: user.id,
89
+ email: user.email,
90
+ name: user.name,
91
+ isCreator: user.isCreator,
92
+ isAdmin: user.isAdmin,
93
+ },
94
+ });
95
+ } catch (error) {
96
+ console.error('Become creator error:', error);
97
+ res.status(500).json({ error: 'Failed to become creator' });
98
+ }
99
+ });
100
+
101
  export default router;