feat: add Layer 2 - WhatsApp Core logic, API Gateway models/auth, Frontend core

WhatsApp Core:
- SessionManager with Baileys integration for multi-account support
- Express server with REST API and Socket.IO for real-time events
- Session lifecycle management (create, disconnect, delete)
- Message sending with support for text, image, document, audio, video

API Gateway:
- Database models: User, WhatsAppAccount, Contact, Conversation, Message
- JWT authentication with access/refresh tokens
- Auth endpoints: login, refresh, register, me
- Pydantic schemas for request/response validation

Frontend:
- React 18 app structure with routing
- Zustand auth store with persistence
- API client with automatic token handling
- Base CSS and TypeScript declarations

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude AI
2026-01-29 09:55:10 +00:00
parent 31d68bc118
commit 7042aa2061
19 changed files with 827 additions and 0 deletions

View File

@@ -0,0 +1,99 @@
import { Router, Request, Response } from 'express';
import { SessionManager } from '../sessions/SessionManager';
export function createRouter(sessionManager: SessionManager): Router {
const router = Router();
// Health check
router.get('/health', (req: Request, res: Response) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// Create new session
router.post('/sessions', async (req: Request, res: Response) => {
try {
const { accountId, name } = req.body;
if (!accountId || !name) {
return res.status(400).json({ error: 'accountId and name required' });
}
const session = await sessionManager.createSession(accountId, name);
res.json(session);
} catch (error) {
res.status(500).json({ error: (error as Error).message });
}
});
// Get session info
router.get('/sessions/:accountId', (req: Request, res: Response) => {
const session = sessionManager.getSession(req.params.accountId);
if (!session) {
return res.status(404).json({ error: 'Session not found' });
}
res.json(session);
});
// Get all sessions
router.get('/sessions', (req: Request, res: Response) => {
const sessions = sessionManager.getAllSessions();
res.json(sessions);
});
// Disconnect session
router.post('/sessions/:accountId/disconnect', async (req: Request, res: Response) => {
try {
await sessionManager.disconnectSession(req.params.accountId);
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: (error as Error).message });
}
});
// Delete session
router.delete('/sessions/:accountId', async (req: Request, res: Response) => {
try {
await sessionManager.deleteSession(req.params.accountId);
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: (error as Error).message });
}
});
// Send message
router.post('/sessions/:accountId/messages', async (req: Request, res: Response) => {
try {
const { to, type, content } = req.body;
if (!to || !content) {
return res.status(400).json({ error: 'to and content required' });
}
let messageContent: any;
switch (type) {
case 'image':
messageContent = { image: { url: content.url }, caption: content.caption };
break;
case 'document':
messageContent = { document: { url: content.url }, fileName: content.filename };
break;
case 'audio':
messageContent = { audio: { url: content.url } };
break;
case 'video':
messageContent = { video: { url: content.url }, caption: content.caption };
break;
default:
messageContent = { text: content.text || content };
}
const result = await sessionManager.sendMessage(
req.params.accountId,
to,
messageContent
);
res.json({ success: true, messageId: result?.key.id });
} catch (error) {
res.status(500).json({ error: (error as Error).message });
}
});
return router;
}

View File

@@ -0,0 +1,74 @@
import express from 'express';
import { createServer } from 'http';
import { Server as SocketIOServer } from 'socket.io';
import { SessionManager } from './sessions/SessionManager';
import { createRouter } from './api/routes';
import pino from 'pino';
const logger = pino({ level: process.env.LOG_LEVEL || 'info' });
const PORT = parseInt(process.env.WS_PORT || '3001', 10);
const API_GATEWAY_URL = process.env.API_GATEWAY_URL || 'http://localhost:8000';
async function main() {
const app = express();
const httpServer = createServer(app);
const io = new SocketIOServer(httpServer, {
cors: {
origin: '*',
methods: ['GET', 'POST'],
},
path: '/ws',
});
app.use(express.json());
const sessionManager = new SessionManager('./sessions');
const router = createRouter(sessionManager);
app.use('/api', router);
// Forward events to API Gateway and connected clients
sessionManager.on('session_event', async (event) => {
logger.info({ event }, 'Session event');
// Emit to Socket.IO clients
io.emit(event.type, event);
// Forward to API Gateway
try {
await fetch(`${API_GATEWAY_URL}/api/internal/whatsapp/event`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(event),
});
} catch (error) {
logger.error({ error }, 'Failed to forward event to API Gateway');
}
});
io.on('connection', (socket) => {
logger.info({ socketId: socket.id }, 'Client connected');
socket.on('subscribe', (accountId: string) => {
socket.join(`account:${accountId}`);
logger.info({ socketId: socket.id, accountId }, 'Client subscribed');
});
socket.on('unsubscribe', (accountId: string) => {
socket.leave(`account:${accountId}`);
});
socket.on('disconnect', () => {
logger.info({ socketId: socket.id }, 'Client disconnected');
});
});
httpServer.listen(PORT, () => {
logger.info({ port: PORT }, 'WhatsApp Core server started');
});
}
main().catch((error) => {
logger.error({ error }, 'Failed to start server');
process.exit(1);
});