|
6 | 6 | */ |
7 | 7 |
|
8 | 8 | import type { Express, Request, Response } from 'express'; |
| 9 | +import type { Message } from './types.js'; |
9 | 10 | import { |
10 | 11 | mockAgents, |
11 | 12 | mockMessages, |
@@ -649,6 +650,133 @@ export function registerMockRoutes(app: Express, verbose: boolean): void { |
649 | 650 | }); |
650 | 651 | }); |
651 | 652 |
|
| 653 | + // ===== Reactions ===== |
| 654 | + |
| 655 | + app.post('/api/messages/:id/reactions', (req: Request, res: Response) => { |
| 656 | + const { id } = req.params; |
| 657 | + const { emoji } = req.body || {}; |
| 658 | + log(`POST /api/messages/${id}/reactions - ${emoji}`); |
| 659 | + |
| 660 | + const message = mockMessages.find(m => m.id === id); |
| 661 | + if (!message) { |
| 662 | + res.status(404).json({ ok: false, error: { code: 'message_not_found', message: 'Message not found' } }); |
| 663 | + return; |
| 664 | + } |
| 665 | + |
| 666 | + const agentName = req.body.from || mockUser.displayName; |
| 667 | + if (!message.reactions) message.reactions = []; |
| 668 | + const existing = message.reactions.find(r => r.emoji === emoji); |
| 669 | + if (existing) { |
| 670 | + if (!existing.agents.includes(agentName)) { |
| 671 | + existing.agents.push(agentName); |
| 672 | + existing.count++; |
| 673 | + } |
| 674 | + } else { |
| 675 | + message.reactions.push({ emoji, count: 1, agents: [agentName] }); |
| 676 | + } |
| 677 | + |
| 678 | + res.status(201).json({ |
| 679 | + ok: true, |
| 680 | + data: { id: `reaction-${Date.now()}`, message_id: id, emoji, agent_name: agentName, created_at: new Date().toISOString() }, |
| 681 | + }); |
| 682 | + }); |
| 683 | + |
| 684 | + app.delete('/api/messages/:id/reactions/:emoji', (req: Request, res: Response) => { |
| 685 | + const { id, emoji } = req.params; |
| 686 | + log(`DELETE /api/messages/${id}/reactions/${emoji}`); |
| 687 | + |
| 688 | + const message = mockMessages.find(m => m.id === id); |
| 689 | + if (!message) { |
| 690 | + res.status(404).json({ ok: false, error: { code: 'message_not_found', message: 'Message not found' } }); |
| 691 | + return; |
| 692 | + } |
| 693 | + |
| 694 | + if (message.reactions) { |
| 695 | + const existing = message.reactions.find(r => r.emoji === emoji); |
| 696 | + if (existing) { |
| 697 | + const agentToRemove = (req.body && req.body.from) || mockUser.displayName; |
| 698 | + existing.agents = existing.agents.filter(a => a !== agentToRemove); |
| 699 | + existing.count = existing.agents.length; |
| 700 | + if (existing.count === 0) { |
| 701 | + message.reactions = message.reactions.filter(r => r.emoji !== emoji); |
| 702 | + } |
| 703 | + } |
| 704 | + } |
| 705 | + |
| 706 | + res.status(204).end(); |
| 707 | + }); |
| 708 | + |
| 709 | + app.get('/api/messages/:id/reactions', (req: Request, res: Response) => { |
| 710 | + const { id } = req.params; |
| 711 | + log(`GET /api/messages/${id}/reactions`); |
| 712 | + |
| 713 | + const message = mockMessages.find(m => m.id === id); |
| 714 | + if (!message) { |
| 715 | + res.status(404).json({ ok: false, error: { code: 'message_not_found', message: 'Message not found' } }); |
| 716 | + return; |
| 717 | + } |
| 718 | + |
| 719 | + res.json({ ok: true, data: message.reactions || [] }); |
| 720 | + }); |
| 721 | + |
| 722 | + // ===== Thread Replies ===== |
| 723 | + |
| 724 | + app.get('/api/messages/:id/replies', (req: Request, res: Response) => { |
| 725 | + const { id } = req.params; |
| 726 | + log(`GET /api/messages/${id}/replies`); |
| 727 | + |
| 728 | + const parent = mockMessages.find(m => m.id === id); |
| 729 | + if (!parent) { |
| 730 | + res.status(404).json({ ok: false, error: { code: 'message_not_found', message: 'Message not found' } }); |
| 731 | + return; |
| 732 | + } |
| 733 | + |
| 734 | + const replies = mockMessages |
| 735 | + .filter(m => m.thread === id) |
| 736 | + .sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()); |
| 737 | + |
| 738 | + res.json({ |
| 739 | + ok: true, |
| 740 | + data: { |
| 741 | + parent: { ...parent, reply_count: replies.length }, |
| 742 | + replies, |
| 743 | + }, |
| 744 | + }); |
| 745 | + }); |
| 746 | + |
| 747 | + app.post('/api/messages/:id/replies', (req: Request, res: Response) => { |
| 748 | + const { id } = req.params; |
| 749 | + const { text } = req.body || {}; |
| 750 | + log(`POST /api/messages/${id}/replies`); |
| 751 | + |
| 752 | + const parent = mockMessages.find(m => m.id === id); |
| 753 | + if (!parent) { |
| 754 | + res.status(404).json({ ok: false, error: { code: 'message_not_found', message: 'Message not found' } }); |
| 755 | + return; |
| 756 | + } |
| 757 | + |
| 758 | + const replyFrom = req.body.from || mockUser.displayName; |
| 759 | + const reply: Message = { |
| 760 | + id: `msg-reply-${Date.now()}`, |
| 761 | + from: replyFrom, |
| 762 | + to: parent.from === replyFrom ? parent.to : parent.from, |
| 763 | + content: text, |
| 764 | + timestamp: new Date().toISOString(), |
| 765 | + thread: id as string, |
| 766 | + }; |
| 767 | + |
| 768 | + mockMessages.push(reply); |
| 769 | + |
| 770 | + // Update parent reply count |
| 771 | + if (parent.replyCount !== undefined) { |
| 772 | + parent.replyCount++; |
| 773 | + } else { |
| 774 | + parent.replyCount = mockMessages.filter(m => m.thread === id).length; |
| 775 | + } |
| 776 | + |
| 777 | + res.status(201).json({ ok: true, data: reply }); |
| 778 | + }); |
| 779 | + |
652 | 780 | // ===== Relay Message ===== |
653 | 781 |
|
654 | 782 | app.post('/api/relay/send', (req: Request, res: Response) => { |
@@ -1178,5 +1306,24 @@ export function registerMockRoutes(app: Express, verbose: boolean): void { |
1178 | 1306 | }); |
1179 | 1307 | }); |
1180 | 1308 |
|
| 1309 | + // ===== Relaycast compatibility (v1 API) ===== |
| 1310 | + |
| 1311 | + app.get('/v1/workspace', (req: Request, res: Response) => { |
| 1312 | + const auth = req.headers.authorization; |
| 1313 | + log(`GET /v1/workspace - auth: ${auth ? 'present' : 'missing'}`); |
| 1314 | + if (!auth?.startsWith('Bearer rk_live_')) { |
| 1315 | + res.status(401).json({ ok: false, error: { code: 'unauthorized', message: 'Invalid API key' } }); |
| 1316 | + return; |
| 1317 | + } |
| 1318 | + res.json({ |
| 1319 | + ok: true, |
| 1320 | + data: { |
| 1321 | + id: mockWorkspaces[0].id, |
| 1322 | + name: mockWorkspaces[0].name, |
| 1323 | + status: 'active', |
| 1324 | + }, |
| 1325 | + }); |
| 1326 | + }); |
| 1327 | + |
1181 | 1328 | console.log('[mock] Mock API routes registered'); |
1182 | 1329 | } |
0 commit comments