LandveX Gateway v0.1.0 + Operations API + Communications Module — ALL RUNNING
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* LandveX Communications Module
|
||||
*
|
||||
* Email, SMS, Push notifications
|
||||
* Port: 3010
|
||||
*/
|
||||
|
||||
import http from 'http';
|
||||
import { handleRequest } from './router.mjs';
|
||||
|
||||
const PORT = process.env.PORT || 3010;
|
||||
|
||||
const server = http.createServer(handleRequest);
|
||||
|
||||
server.listen(PORT, () => {
|
||||
console.log(`📧 LandveX Communications running on port ${PORT}`);
|
||||
console.log(`📨 Endpoints:`);
|
||||
console.log(` GET /health → Health check`);
|
||||
console.log(` GET /api/v1/communications/threads → List threads`);
|
||||
console.log(` POST /api/v1/communications/threads → Create thread`);
|
||||
console.log(` POST /api/v1/communications/send → Send message`);
|
||||
console.log(` GET /api/v1/communications/templates → List templates`);
|
||||
console.log(` POST /api/v1/communications/templates → Create template`);
|
||||
console.log(` POST /webhooks/mailgun → Mailgun webhook`);
|
||||
console.log(` POST /webhooks/twilio → Twilio webhook`);
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* LandveX Communications Module
|
||||
*
|
||||
* Email, SMS, Push notifications
|
||||
* Port: 3010
|
||||
*/
|
||||
|
||||
import http from 'http';
|
||||
import { handleRequest } from './router.js';
|
||||
|
||||
const PORT = process.env.PORT || 3010;
|
||||
|
||||
const server = http.createServer(handleRequest);
|
||||
|
||||
server.listen(PORT, () => {
|
||||
console.log(`📧 LandveX Communications running on port ${PORT}`);
|
||||
console.log(`📨 Endpoints:`);
|
||||
console.log(` GET /health → Health check`);
|
||||
console.log(` GET /api/v1/communications/threads → List threads`);
|
||||
console.log(` POST /api/v1/communications/threads → Create thread`);
|
||||
console.log(` POST /api/v1/communications/send → Send message`);
|
||||
console.log(` GET /api/v1/communications/templates → List templates`);
|
||||
console.log(` POST /api/v1/communications/templates → Create template`);
|
||||
console.log(` POST /webhooks/mailgun → Mailgun webhook`);
|
||||
console.log(` POST /webhooks/twilio → Twilio webhook`);
|
||||
});
|
||||
@@ -0,0 +1,213 @@
|
||||
/**
|
||||
* LandveX Communications — Router
|
||||
* Zero-dependency HTTP router
|
||||
*/
|
||||
|
||||
import http from 'http';
|
||||
import { URL } from 'url';
|
||||
|
||||
// In-memory store
|
||||
const threads = {};
|
||||
const templates = {};
|
||||
const messages = {};
|
||||
|
||||
export function handleRequest(req, res) {
|
||||
// CORS
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.statusCode = 204;
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
const url = new URL(req.url || '/', `http://${req.headers.host}`);
|
||||
const path = url.pathname;
|
||||
|
||||
// Health check
|
||||
if (path === '/health') {
|
||||
res.end(JSON.stringify({
|
||||
status: 'ok',
|
||||
version: '0.1.0-communications',
|
||||
service: 'communications',
|
||||
channels: ['email', 'sms', 'push'],
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// Threads
|
||||
if (path === '/api/v1/communications/threads' && req.method === 'GET') {
|
||||
handleListThreads(req, res, url);
|
||||
} else if (path === '/api/v1/communications/threads' && req.method === 'POST') {
|
||||
handleCreateThread(req, res);
|
||||
} else if (path.startsWith('/api/v1/communications/threads/') && req.method === 'GET') {
|
||||
handleGetThread(req, res, path);
|
||||
}
|
||||
// Messages
|
||||
else if (path === '/api/v1/communications/send' && req.method === 'POST') {
|
||||
handleSendMessage(req, res);
|
||||
}
|
||||
// Templates
|
||||
else if (path === '/api/v1/communications/templates' && req.method === 'GET') {
|
||||
handleListTemplates(req, res);
|
||||
} else if (path === '/api/v1/communications/templates' && req.method === 'POST') {
|
||||
handleCreateTemplate(req, res);
|
||||
}
|
||||
// Webhooks
|
||||
else if (path === '/webhooks/mailgun' && req.method === 'POST') {
|
||||
handleMailgunWebhook(req, res);
|
||||
} else if (path === '/webhooks/twilio' && req.method === 'POST') {
|
||||
handleTwilioWebhook(req, res);
|
||||
} else {
|
||||
res.statusCode = 404;
|
||||
res.end(JSON.stringify({ error: 'Not Found', path }));
|
||||
}
|
||||
}
|
||||
|
||||
function handleListThreads(req, res, url) {
|
||||
const customerId = url.searchParams.get('customer') || 'all';
|
||||
const type = url.searchParams.get('type');
|
||||
|
||||
let result = Object.values(threads);
|
||||
if (customerId !== 'all') {
|
||||
result = result.filter((t) => t.customerId === customerId);
|
||||
}
|
||||
if (type) {
|
||||
result = result.filter((t) => t.type === type);
|
||||
}
|
||||
|
||||
res.end(JSON.stringify({
|
||||
threads: result,
|
||||
count: result.length,
|
||||
}));
|
||||
}
|
||||
|
||||
function handleCreateThread(req, res) {
|
||||
let body = '';
|
||||
req.on('data', chunk => body += chunk);
|
||||
req.on('end', () => {
|
||||
try {
|
||||
const data = JSON.parse(body);
|
||||
const thread = {
|
||||
id: `thread-${Date.now()}`,
|
||||
...data,
|
||||
messages: [],
|
||||
status: 'open',
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
threads[thread.id] = thread;
|
||||
|
||||
res.statusCode = 201;
|
||||
res.end(JSON.stringify(thread));
|
||||
} catch {
|
||||
res.statusCode = 400;
|
||||
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleGetThread(req, res, path) {
|
||||
const id = path.split('/').pop();
|
||||
const thread = threads[id || ''];
|
||||
|
||||
if (!thread) {
|
||||
res.statusCode = 404;
|
||||
res.end(JSON.stringify({ error: 'Thread not found' }));
|
||||
return;
|
||||
}
|
||||
|
||||
res.end(JSON.stringify(thread));
|
||||
}
|
||||
|
||||
function handleSendMessage(req, res) {
|
||||
let body = '';
|
||||
req.on('data', chunk => body += chunk);
|
||||
req.on('end', () => {
|
||||
try {
|
||||
const data = JSON.parse(body);
|
||||
|
||||
// Simulate sending
|
||||
const message = {
|
||||
id: `msg-${Date.now()}`,
|
||||
...data,
|
||||
status: 'sent',
|
||||
sentAt: new Date().toISOString(),
|
||||
providerMessageId: `mock-${Date.now()}`,
|
||||
};
|
||||
|
||||
// Add to thread if specified
|
||||
if (data.threadId && threads[data.threadId]) {
|
||||
threads[data.threadId].messages.push(message);
|
||||
threads[data.threadId].updatedAt = new Date().toISOString();
|
||||
}
|
||||
|
||||
res.statusCode = 201;
|
||||
res.end(JSON.stringify({
|
||||
success: true,
|
||||
message,
|
||||
note: 'Mock send — connect Mailgun/Twilio for real sending',
|
||||
}));
|
||||
} catch {
|
||||
res.statusCode = 400;
|
||||
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleListTemplates(req, res) {
|
||||
const result = Object.values(templates);
|
||||
res.end(JSON.stringify({
|
||||
templates: result,
|
||||
count: result.length,
|
||||
}));
|
||||
}
|
||||
|
||||
function handleCreateTemplate(req, res) {
|
||||
let body = '';
|
||||
req.on('data', chunk => body += chunk);
|
||||
req.on('end', () => {
|
||||
try {
|
||||
const data = JSON.parse(body);
|
||||
const template = {
|
||||
id: `template-${Date.now()}`,
|
||||
...data,
|
||||
usageCount: 0,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
templates[template.id] = template;
|
||||
|
||||
res.statusCode = 201;
|
||||
res.end(JSON.stringify(template));
|
||||
} catch {
|
||||
res.statusCode = 400;
|
||||
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleMailgunWebhook(req, res) {
|
||||
let body = '';
|
||||
req.on('data', chunk => body += chunk);
|
||||
req.on('end', () => {
|
||||
console.log('[Mailgun Webhook]', body);
|
||||
res.statusCode = 200;
|
||||
res.end(JSON.stringify({ received: true }));
|
||||
});
|
||||
}
|
||||
|
||||
function handleTwilioWebhook(req, res) {
|
||||
let body = '';
|
||||
req.on('data', chunk => body += chunk);
|
||||
req.on('end', () => {
|
||||
console.log('[Twilio Webhook]', body);
|
||||
res.statusCode = 200;
|
||||
res.end(JSON.stringify({ received: true }));
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
/**
|
||||
* LandveX Communications — Router
|
||||
* Zero-dependency HTTP router
|
||||
*/
|
||||
|
||||
import http from 'http';
|
||||
import { URL } from 'url';
|
||||
|
||||
// In-memory store
|
||||
const threads: Record<string, any> = {};
|
||||
const templates: Record<string, any> = {};
|
||||
const messages: Record<string, any[]> = {};
|
||||
|
||||
export function handleRequest(req: http.IncomingMessage, res: http.ServerResponse): void {
|
||||
// CORS
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.statusCode = 204;
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
const url = new URL(req.url || '/', `http://${req.headers.host}`);
|
||||
const path = url.pathname;
|
||||
|
||||
// Health check
|
||||
if (path === '/health') {
|
||||
res.end(JSON.stringify({
|
||||
status: 'ok',
|
||||
version: '0.1.0-communications',
|
||||
service: 'communications',
|
||||
channels: ['email', 'sms', 'push'],
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// Threads
|
||||
if (path === '/api/v1/communications/threads' && req.method === 'GET') {
|
||||
handleListThreads(req, res, url);
|
||||
} else if (path === '/api/v1/communications/threads' && req.method === 'POST') {
|
||||
handleCreateThread(req, res);
|
||||
} else if (path.startsWith('/api/v1/communications/threads/') && req.method === 'GET') {
|
||||
handleGetThread(req, res, path);
|
||||
}
|
||||
// Messages
|
||||
else if (path === '/api/v1/communications/send' && req.method === 'POST') {
|
||||
handleSendMessage(req, res);
|
||||
}
|
||||
// Templates
|
||||
else if (path === '/api/v1/communications/templates' && req.method === 'GET') {
|
||||
handleListTemplates(req, res);
|
||||
} else if (path === '/api/v1/communications/templates' && req.method === 'POST') {
|
||||
handleCreateTemplate(req, res);
|
||||
}
|
||||
// Webhooks
|
||||
else if (path === '/webhooks/mailgun' && req.method === 'POST') {
|
||||
handleMailgunWebhook(req, res);
|
||||
} else if (path === '/webhooks/twilio' && req.method === 'POST') {
|
||||
handleTwilioWebhook(req, res);
|
||||
} else {
|
||||
res.statusCode = 404;
|
||||
res.end(JSON.stringify({ error: 'Not Found', path }));
|
||||
}
|
||||
}
|
||||
|
||||
function handleListThreads(req: http.IncomingMessage, res: http.ServerResponse, url: URL): void {
|
||||
const customerId = url.searchParams.get('customer') || 'all';
|
||||
const type = url.searchParams.get('type');
|
||||
|
||||
let result = Object.values(threads);
|
||||
if (customerId !== 'all') {
|
||||
result = result.filter((t: any) => t.customerId === customerId);
|
||||
}
|
||||
if (type) {
|
||||
result = result.filter((t: any) => t.type === type);
|
||||
}
|
||||
|
||||
res.end(JSON.stringify({
|
||||
threads: result,
|
||||
count: result.length,
|
||||
}));
|
||||
}
|
||||
|
||||
function handleCreateThread(req: http.IncomingMessage, res: http.ServerResponse): void {
|
||||
let body = '';
|
||||
req.on('data', chunk => body += chunk);
|
||||
req.on('end', () => {
|
||||
try {
|
||||
const data = JSON.parse(body);
|
||||
const thread = {
|
||||
id: `thread-${Date.now()}`,
|
||||
...data,
|
||||
messages: [],
|
||||
status: 'open',
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
threads[thread.id] = thread;
|
||||
|
||||
res.statusCode = 201;
|
||||
res.end(JSON.stringify(thread));
|
||||
} catch {
|
||||
res.statusCode = 400;
|
||||
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleGetThread(req: http.IncomingMessage, res: http.ServerResponse, path: string): void {
|
||||
const id = path.split('/').pop();
|
||||
const thread = threads[id || ''];
|
||||
|
||||
if (!thread) {
|
||||
res.statusCode = 404;
|
||||
res.end(JSON.stringify({ error: 'Thread not found' }));
|
||||
return;
|
||||
}
|
||||
|
||||
res.end(JSON.stringify(thread));
|
||||
}
|
||||
|
||||
function handleSendMessage(req: http.IncomingMessage, res: http.ServerResponse): void {
|
||||
let body = '';
|
||||
req.on('data', chunk => body += chunk);
|
||||
req.on('end', () => {
|
||||
try {
|
||||
const data = JSON.parse(body);
|
||||
|
||||
// Simulate sending
|
||||
const message = {
|
||||
id: `msg-${Date.now()}`,
|
||||
...data,
|
||||
status: 'sent',
|
||||
sentAt: new Date().toISOString(),
|
||||
providerMessageId: `mock-${Date.now()}`,
|
||||
};
|
||||
|
||||
// Add to thread if specified
|
||||
if (data.threadId && threads[data.threadId]) {
|
||||
threads[data.threadId].messages.push(message);
|
||||
threads[data.threadId].updatedAt = new Date().toISOString();
|
||||
}
|
||||
|
||||
res.statusCode = 201;
|
||||
res.end(JSON.stringify({
|
||||
success: true,
|
||||
message,
|
||||
note: 'Mock send — connect Mailgun/Twilio for real sending',
|
||||
}));
|
||||
} catch {
|
||||
res.statusCode = 400;
|
||||
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleListTemplates(req: http.IncomingMessage, res: http.ServerResponse): void {
|
||||
const result = Object.values(templates);
|
||||
res.end(JSON.stringify({
|
||||
templates: result,
|
||||
count: result.length,
|
||||
}));
|
||||
}
|
||||
|
||||
function handleCreateTemplate(req: http.IncomingMessage, res: http.ServerResponse): void {
|
||||
let body = '';
|
||||
req.on('data', chunk => body += chunk);
|
||||
req.on('end', () => {
|
||||
try {
|
||||
const data = JSON.parse(body);
|
||||
const template = {
|
||||
id: `template-${Date.now()}`,
|
||||
...data,
|
||||
usageCount: 0,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
templates[template.id] = template;
|
||||
|
||||
res.statusCode = 201;
|
||||
res.end(JSON.stringify(template));
|
||||
} catch {
|
||||
res.statusCode = 400;
|
||||
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleMailgunWebhook(req: http.IncomingMessage, res: http.ServerResponse): void {
|
||||
let body = '';
|
||||
req.on('data', chunk => body += chunk);
|
||||
req.on('end', () => {
|
||||
console.log('[Mailgun Webhook]', body);
|
||||
res.statusCode = 200;
|
||||
res.end(JSON.stringify({ received: true }));
|
||||
});
|
||||
}
|
||||
|
||||
function handleTwilioWebhook(req: http.IncomingMessage, res: http.ServerResponse): void {
|
||||
let body = '';
|
||||
req.on('data', chunk => body += chunk);
|
||||
req.on('end', () => {
|
||||
console.log('[Twilio Webhook]', body);
|
||||
res.statusCode = 200;
|
||||
res.end(JSON.stringify({ received: true }));
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user