feat(saas): full SquareMCP SaaS platform v1
- JWT auth with bcrypt password hashing, cookie sessions, forgot/reset password - Per-user encrypted credential storage (Redis + AES-256-GCM) for all 9 platforms - Usage tracking with monthly limits per plan (free/starter/growth/enterprise) - Invoice generation and retrieval (admin + user views) - Admin panel with customer listing (role-based access) - Web app UI at app.squaremcp.com — login, dashboard, connections, usage, invoices - Unified auth middleware: API key, OAuth Bearer, and JWT cookie support - Facebook Graph API fixes: published_posts endpoint, photo/video post support - TikTok sandbox compliance: SELF_ONLY privacy for unaudited apps - URL verification files for TikTok app review
This commit is contained in:
386
src/index.ts
386
src/index.ts
@@ -1,6 +1,7 @@
|
||||
import 'dotenv/config';
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
||||
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
||||
@@ -14,7 +15,7 @@ import { tools, handleToolCall } from './tools.js';
|
||||
import { getManifest, getOpenApiSpec, getOpenApiSpecMail, getOpenApiSpecSocial } from './manifest.js';
|
||||
import { routeWhatsAppWebhook, registerWhatsAppNumber, type RoutedWebhookEvent } from './multitenancy/webhook-router.js';
|
||||
import { storeCredential, type Platform } from './multitenancy/credential-store.js';
|
||||
import { meterMiddleware, type Customer } from './billing/middleware.js';
|
||||
import { meterMiddleware, resolveCustomerByApiKey, resolveCustomerById, type Customer } from './billing/middleware.js';
|
||||
import {
|
||||
registerClient,
|
||||
getClient,
|
||||
@@ -23,9 +24,13 @@ import {
|
||||
validateAccessToken,
|
||||
getAuthorizeHtml,
|
||||
} from './oauth.js';
|
||||
import { initDatabase } from './db.js';
|
||||
import { initDatabase, getPool } from './db.js';
|
||||
import { hashPassword, verifyPassword, signJWT, verifyJWT, findCustomerByEmail, createCustomer, setResetToken, findCustomerByResetToken, clearResetToken, updatePassword } from './auth.js';
|
||||
import { recordUsage, getMonthlyUsage, getUsageBreakdown, checkLimit } from './billing/usage.js';
|
||||
import { getCustomerInvoices, getInvoiceByNumber, markInvoiceSent, markInvoicePaid, generateMonthlyInvoice } from './billing/invoices.js';
|
||||
|
||||
const app = express();
|
||||
app.use(cookieParser());
|
||||
app.use(cors({
|
||||
origin: '*',
|
||||
methods: ['GET', 'POST', 'DELETE', 'OPTIONS'],
|
||||
@@ -152,14 +157,14 @@ async function appendPilotRequestToVault(requestId: string, body: PilotRequestBo
|
||||
const content = formatPilotRequestMarkdown(requestId, body, req);
|
||||
const dailyNotePath = `Daily Notes/${getEasternDateString()}.md`;
|
||||
|
||||
await handleToolCall('obsidian_append_to_note', {
|
||||
await callTool(req, 'obsidian_append_to_note', {
|
||||
path: 'SquareMCP/Pilot Requests.md',
|
||||
header: 'Pilot Requests',
|
||||
content,
|
||||
create_if_missing: true,
|
||||
});
|
||||
|
||||
await handleToolCall('obsidian_append_to_note', {
|
||||
await callTool(req, 'obsidian_append_to_note', {
|
||||
path: dailyNotePath,
|
||||
header: 'SquareMCP Pilot Requests',
|
||||
content,
|
||||
@@ -302,19 +307,48 @@ async function requireAuth(req: express.Request, res: express.Response, next: ex
|
||||
// No API key configured = open access
|
||||
if (!API_KEY) return next();
|
||||
|
||||
// 1. Check x-api-key header or query param (backward compatibility)
|
||||
// 1. Check x-api-key header or query param (backward compatibility — global key)
|
||||
const apiKeyProvided = (req.headers['x-api-key'] as string | undefined) || (req.query.key as string | undefined);
|
||||
if (apiKeyProvided === API_KEY) return next();
|
||||
|
||||
// 2. Check OAuth Bearer token
|
||||
// 2. Check customer API key (per-user SaaS auth)
|
||||
if (apiKeyProvided) {
|
||||
const customer = await resolveCustomerByApiKey(apiKeyProvided);
|
||||
if (customer && customer.active) {
|
||||
(req as express.Request & { customer?: Customer }).customer = customer;
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Check OAuth Bearer token
|
||||
const bearerToken = extractBearerToken(req);
|
||||
if (bearerToken && await validateAccessToken(bearerToken)) return next();
|
||||
|
||||
// 4. Check JWT session cookie (web app auth)
|
||||
const jwtCookie = req.cookies?.session;
|
||||
if (jwtCookie) {
|
||||
try {
|
||||
const payload = verifyJWT(jwtCookie);
|
||||
const customer = await resolveCustomerById(payload.sub);
|
||||
if (customer && customer.active) {
|
||||
(req as express.Request & { customer?: Customer; jwtUser?: { id: string; email: string; plan: string } }).customer = customer;
|
||||
(req as express.Request & { jwtUser?: { id: string; email: string; plan: string } }).jwtUser = {
|
||||
id: payload.sub,
|
||||
email: payload.email,
|
||||
plan: payload.plan,
|
||||
};
|
||||
return next();
|
||||
}
|
||||
} catch {
|
||||
// invalid JWT, fall through to 401
|
||||
}
|
||||
}
|
||||
|
||||
res.setHeader(
|
||||
'WWW-Authenticate',
|
||||
`Bearer realm="hermes", resource_metadata="${PROTECTED_RESOURCE_METADATA_URL}"`
|
||||
);
|
||||
res.status(401).json({ error: 'Unauthorized — provide x-api-key header, ?key= query param, or Bearer token' });
|
||||
res.status(401).json({ error: 'Unauthorized — provide x-api-key header, ?key= query param, Bearer token, or session cookie' });
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
@@ -717,7 +751,7 @@ app.post('/hermes-mcp/:linkSegment/:toolName', requireAuth, async (req, res) =>
|
||||
const args = (req.body ?? {}) as Record<string, unknown>;
|
||||
console.log(`[chatgpt-mcp] ${toolName}`, JSON.stringify(args).substring(0, 200));
|
||||
try {
|
||||
const result = await handleToolCall(toolName, args);
|
||||
const result = await callTool(req, toolName, args);
|
||||
const text = result.content[0].text;
|
||||
if (text.startsWith('Error:')) {
|
||||
res.status(400).json({ error: text.slice(7).trim() });
|
||||
@@ -734,7 +768,7 @@ app.get('/hermes-mcp/:linkSegment/:toolName', requireAuth, async (req, res) => {
|
||||
const args = req.query as Record<string, unknown>;
|
||||
console.log(`[chatgpt-mcp] GET ${toolName}`, JSON.stringify(args).substring(0, 200));
|
||||
try {
|
||||
const result = await handleToolCall(toolName, args);
|
||||
const result = await callTool(req, toolName, args);
|
||||
const text = result.content[0].text;
|
||||
if (text.startsWith('Error:')) {
|
||||
res.status(400).json({ error: text.slice(7).trim() });
|
||||
@@ -799,7 +833,7 @@ app.get('/api/obsidian/search', requireAuth, async (req, res) => {
|
||||
const tagsRaw = req.query.tags as string | undefined;
|
||||
const tags = tagsRaw ? tagsRaw.split(',').map((t) => t.trim()).filter(Boolean) : undefined;
|
||||
try {
|
||||
const result = await handleToolCall('obsidian_search_notes', { query, limit, path_filter, tags });
|
||||
const result = await callTool(req, 'obsidian_search_notes', { query, limit, path_filter, tags });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -810,7 +844,7 @@ app.get('/api/obsidian/note', requireAuth, async (req, res) => {
|
||||
const path = req.query.path as string | undefined;
|
||||
if (!path) { res.status(400).json({ error: 'path is required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('obsidian_read_note', { path });
|
||||
const result = await callTool(req, 'obsidian_read_note', { path });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
const msg = (err as Error).message;
|
||||
@@ -822,7 +856,7 @@ app.post('/api/obsidian/note/append', requireAuth, async (req, res) => {
|
||||
const { path, content, header, create_if_missing } = req.body as Record<string, unknown>;
|
||||
if (!path || !content) { res.status(400).json({ error: 'path and content are required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('obsidian_append_to_note', { path, content, header, create_if_missing });
|
||||
const result = await callTool(req, 'obsidian_append_to_note', { path, content, header, create_if_missing });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -833,16 +867,16 @@ app.put('/api/obsidian/note', requireAuth, async (req, res) => {
|
||||
const { path, content } = req.body as Record<string, unknown>;
|
||||
if (!path || !content) { res.status(400).json({ error: 'path and content are required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('obsidian_update_note', { path, content });
|
||||
const result = await callTool(req, 'obsidian_update_note', { path, content });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/obsidian/sync', requireAuth, async (_req, res) => {
|
||||
app.get('/api/obsidian/sync', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const result = await handleToolCall('obsidian_sync_status', {});
|
||||
const result = await callTool(req, 'obsidian_sync_status', {});
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -854,7 +888,7 @@ app.post('/api/whatsapp/send', requireAuth, async (req, res) => {
|
||||
const { to, message, account } = req.body as Record<string, unknown>;
|
||||
if (!to || !message) { res.status(400).json({ error: 'to and message are required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('whatsapp_send_message', { to, message, account });
|
||||
const result = await callTool(req, 'whatsapp_send_message', { to, message, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -865,7 +899,7 @@ app.post('/api/whatsapp/template', requireAuth, async (req, res) => {
|
||||
const { to, template_name, language, components, account } = req.body as Record<string, unknown>;
|
||||
if (!to || !template_name) { res.status(400).json({ error: 'to and template_name are required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('whatsapp_send_template', { to, template_name, language, components, account });
|
||||
const result = await callTool(req, 'whatsapp_send_template', { to, template_name, language, components, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -875,7 +909,7 @@ app.post('/api/whatsapp/template', requireAuth, async (req, res) => {
|
||||
app.get('/api/whatsapp/templates', requireAuth, async (req, res) => {
|
||||
const account = req.query.account as string | undefined;
|
||||
try {
|
||||
const result = await handleToolCall('whatsapp_list_templates', { account });
|
||||
const result = await callTool(req, 'whatsapp_list_templates', { account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -916,6 +950,93 @@ app.post('/webhook/whatsapp', express.json(), async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ── Auth endpoints ──────────────────────────────────────────────
|
||||
|
||||
app.post('/api/auth/signup', express.json(), async (req, res) => {
|
||||
const { email, password } = req.body as Record<string, string>;
|
||||
if (!email || !password) {
|
||||
res.status(400).json({ error: 'Email and password required' });
|
||||
return;
|
||||
}
|
||||
if (password.length < 8) {
|
||||
res.status(400).json({ error: 'Password must be at least 8 characters' });
|
||||
return;
|
||||
}
|
||||
|
||||
const existing = await findCustomerByEmail(email);
|
||||
if (existing) {
|
||||
res.status(409).json({ error: 'Email already registered' });
|
||||
return;
|
||||
}
|
||||
|
||||
const id = crypto.randomUUID();
|
||||
const apiKey = crypto.randomUUID().replace(/-/g, '');
|
||||
const passwordHash = await hashPassword(password);
|
||||
|
||||
try {
|
||||
// Check if this is the first user — make them admin
|
||||
const [countRows] = await getPool().query<any[]>('SELECT COUNT(*) as c FROM customers');
|
||||
const isFirstUser = countRows[0]?.c === 0;
|
||||
await createCustomer(id, email, passwordHash, apiKey);
|
||||
if (isFirstUser) {
|
||||
await getPool().query("UPDATE customers SET role = 'admin', plan = 'enterprise' WHERE id = ?", [id]);
|
||||
}
|
||||
const token = signJWT({ sub: id, email, plan: isFirstUser ? 'enterprise' : 'free' });
|
||||
res.cookie('session', token, {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: 'strict',
|
||||
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
|
||||
});
|
||||
res.status(201).json({ id, email, plan: 'free', api_key: apiKey });
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: 'Failed to create account' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/auth/login', express.json(), async (req, res) => {
|
||||
const { email, password } = req.body as Record<string, string>;
|
||||
if (!email || !password) {
|
||||
res.status(400).json({ error: 'Email and password required' });
|
||||
return;
|
||||
}
|
||||
|
||||
const customer = await findCustomerByEmail(email);
|
||||
if (!customer || !customer.password_hash) {
|
||||
res.status(401).json({ error: 'Invalid credentials' });
|
||||
return;
|
||||
}
|
||||
|
||||
const valid = await verifyPassword(password, customer.password_hash);
|
||||
if (!valid) {
|
||||
res.status(401).json({ error: 'Invalid credentials' });
|
||||
return;
|
||||
}
|
||||
|
||||
const token = signJWT({ sub: customer.id, email: customer.email, plan: customer.plan });
|
||||
res.cookie('session', token, {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: 'strict',
|
||||
maxAge: 7 * 24 * 60 * 60 * 1000,
|
||||
});
|
||||
res.json({ id: customer.id, email: customer.email, plan: customer.plan, api_key: customer.api_key });
|
||||
});
|
||||
|
||||
app.post('/api/auth/logout', (_req, res) => {
|
||||
res.clearCookie('session');
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
app.get('/api/auth/me', requireAuth, async (req, res) => {
|
||||
const jwtUser = (req as express.Request & { jwtUser?: { id: string; email: string; plan: string } }).jwtUser;
|
||||
if (jwtUser) {
|
||||
res.json(jwtUser);
|
||||
return;
|
||||
}
|
||||
res.status(401).json({ error: 'Not authenticated' });
|
||||
});
|
||||
|
||||
// ── Customer onboarding endpoints ───────────────────────────────
|
||||
|
||||
// Connect WhatsApp — called after customer enters their Meta credentials
|
||||
@@ -1013,11 +1134,174 @@ app.get('/api/connections', meterMiddleware, async (req, res) => {
|
||||
res.json({ customerId: customer.id, connections: status });
|
||||
});
|
||||
|
||||
// ── Usage & Limits ──────────────────────────────────────────────
|
||||
|
||||
app.get('/api/usage', meterMiddleware, async (req, res) => {
|
||||
const customer = (req as unknown as { customer: Customer }).customer;
|
||||
const used = await getMonthlyUsage(customer.id);
|
||||
const breakdown = await getUsageBreakdown(customer.id);
|
||||
const limitCheck = await checkLimit(customer.id, customer.plan);
|
||||
res.json({
|
||||
plan: customer.plan,
|
||||
monthlyLimit: limitCheck.limit,
|
||||
used,
|
||||
remaining: limitCheck.limit === -1 ? -1 : Math.max(0, limitCheck.limit - used),
|
||||
breakdown,
|
||||
});
|
||||
});
|
||||
|
||||
// ── Invoices ────────────────────────────────────────────────────
|
||||
|
||||
app.get('/api/invoices', meterMiddleware, async (req, res) => {
|
||||
const customer = (req as unknown as { customer: Customer }).customer;
|
||||
const invoices = await getCustomerInvoices(customer.id);
|
||||
res.json({ invoices });
|
||||
});
|
||||
|
||||
app.get('/api/invoices/:number', meterMiddleware, async (req, res) => {
|
||||
const invoice = await getInvoiceByNumber(req.params.number);
|
||||
if (!invoice) {
|
||||
res.status(404).json({ error: 'Invoice not found' });
|
||||
return;
|
||||
}
|
||||
res.json(invoice);
|
||||
});
|
||||
|
||||
// ── Password Reset ──────────────────────────────────────────────
|
||||
|
||||
app.post('/api/auth/forgot-password', express.json(), async (req, res) => {
|
||||
const { email } = req.body as Record<string, string>;
|
||||
if (!email) {
|
||||
res.status(400).json({ error: 'Email required' });
|
||||
return;
|
||||
}
|
||||
|
||||
const token = crypto.randomUUID().replace(/-/g, '');
|
||||
const success = await setResetToken(email, token);
|
||||
|
||||
if (!success) {
|
||||
// Don't reveal if email exists
|
||||
res.json({ message: 'If an account exists, a reset link has been sent.' });
|
||||
return;
|
||||
}
|
||||
|
||||
// In production, send email here. For now, return the token in dev mode.
|
||||
const resetUrl = `https://app.squaremcp.com/reset-password?token=${token}`;
|
||||
res.json({
|
||||
message: 'Password reset link generated.',
|
||||
resetUrl,
|
||||
token,
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/api/auth/reset-password', express.json(), async (req, res) => {
|
||||
const { token, password } = req.body as Record<string, string>;
|
||||
if (!token || !password) {
|
||||
res.status(400).json({ error: 'Token and password required' });
|
||||
return;
|
||||
}
|
||||
if (password.length < 8) {
|
||||
res.status(400).json({ error: 'Password must be at least 8 characters' });
|
||||
return;
|
||||
}
|
||||
|
||||
const customer = await findCustomerByResetToken(token);
|
||||
if (!customer) {
|
||||
res.status(400).json({ error: 'Invalid or expired reset token' });
|
||||
return;
|
||||
}
|
||||
|
||||
const passwordHash = await hashPassword(password);
|
||||
await updatePassword(customer.id, passwordHash);
|
||||
await clearResetToken(customer.id);
|
||||
|
||||
res.json({ message: 'Password updated successfully.' });
|
||||
});
|
||||
|
||||
// ── Admin Endpoints ─────────────────────────────────────────────
|
||||
|
||||
function callTool(req: express.Request, name: string, args: Record<string, unknown>) {
|
||||
const customer = (req as express.Request & { customer?: Customer }).customer;
|
||||
return handleToolCall(name, args, customer);
|
||||
}
|
||||
|
||||
async function requireAdmin(req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
// Global API key = superadmin access
|
||||
const apiKeyProvided = (req.headers['x-api-key'] as string | undefined) || (req.query.key as string | undefined);
|
||||
if (apiKeyProvided === API_KEY) return next();
|
||||
|
||||
// Check JWT first
|
||||
const jwtCookie = req.cookies?.session;
|
||||
let customerId: string | null = null;
|
||||
|
||||
if (jwtCookie) {
|
||||
try {
|
||||
const payload = verifyJWT(jwtCookie);
|
||||
customerId = payload.sub;
|
||||
} catch {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
// Check customer API key
|
||||
if (!customerId && apiKeyProvided) {
|
||||
const customer = await resolveCustomerByApiKey(apiKeyProvided);
|
||||
if (customer) customerId = customer.id;
|
||||
}
|
||||
|
||||
if (!customerId) {
|
||||
res.status(401).json({ error: 'Unauthorized' });
|
||||
return;
|
||||
}
|
||||
|
||||
const [rows] = await getPool().query<any[]>('SELECT role FROM customers WHERE id = ?', [customerId]);
|
||||
if (!rows.length || rows[0].role !== 'admin') {
|
||||
res.status(403).json({ error: 'Admin access required' });
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
app.get('/api/admin/customers', requireAdmin, async (req, res) => {
|
||||
const [rows] = await getPool().query<any[]>(
|
||||
'SELECT id, email, plan, active, role, created_at FROM customers ORDER BY created_at DESC'
|
||||
);
|
||||
res.json({ customers: rows });
|
||||
});
|
||||
|
||||
app.get('/api/admin/customers/:id/usage', requireAdmin, async (req, res) => {
|
||||
const customerId = req.params.id;
|
||||
const used = await getMonthlyUsage(customerId);
|
||||
const breakdown = await getUsageBreakdown(customerId);
|
||||
res.json({ customerId, used, breakdown });
|
||||
});
|
||||
|
||||
app.post('/api/admin/customers/:id/invoice', requireAdmin, async (req, res) => {
|
||||
const customerId = req.params.id;
|
||||
const invoice = await generateMonthlyInvoice(customerId);
|
||||
if (!invoice) {
|
||||
res.status(400).json({ error: 'No usage to invoice' });
|
||||
return;
|
||||
}
|
||||
res.json(invoice);
|
||||
});
|
||||
|
||||
app.post('/api/admin/invoices/:number/send', requireAdmin, async (req, res) => {
|
||||
await markInvoiceSent(req.params.number);
|
||||
res.json({ sent: true });
|
||||
});
|
||||
|
||||
app.post('/api/admin/invoices/:number/pay', requireAdmin, async (req, res) => {
|
||||
await markInvoicePaid(req.params.number);
|
||||
res.json({ paid: true });
|
||||
});
|
||||
|
||||
// ── LinkedIn REST endpoints ─────────────────────────────────────
|
||||
app.get('/api/linkedin/profile', requireAuth, async (req, res) => {
|
||||
const account = req.query.account as string | undefined;
|
||||
try {
|
||||
const result = await handleToolCall('linkedin_get_profile', { account });
|
||||
const result = await callTool(req, 'linkedin_get_profile', { account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1028,7 +1312,7 @@ app.post('/api/linkedin/post', requireAuth, async (req, res) => {
|
||||
const { text, visibility, account } = req.body as Record<string, unknown>;
|
||||
if (!text) { res.status(400).json({ error: 'text is required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('linkedin_create_post', { text, visibility, account });
|
||||
const result = await callTool(req, 'linkedin_create_post', { text, visibility, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1038,7 +1322,7 @@ app.post('/api/linkedin/post', requireAuth, async (req, res) => {
|
||||
app.post('/api/linkedin/video', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { video_url, text, visibility, account } = req.body as Record<string, unknown>;
|
||||
const result = await handleToolCall('linkedin_upload_video', { video_url, text, visibility, account });
|
||||
const result = await callTool(req, 'linkedin_upload_video', { video_url, text, visibility, account });
|
||||
res.json(result);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: String(err) });
|
||||
@@ -1048,7 +1332,7 @@ app.post('/api/linkedin/video', requireAuth, async (req, res) => {
|
||||
app.post('/api/linkedin/search-connections', requireAuth, async (req, res) => {
|
||||
const { keywords, account } = req.body as Record<string, unknown>;
|
||||
try {
|
||||
const result = await handleToolCall('linkedin_search_connections', { keywords, account });
|
||||
const result = await callTool(req, 'linkedin_search_connections', { keywords, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1059,7 +1343,7 @@ app.post('/api/linkedin/message', requireAuth, async (req, res) => {
|
||||
const { recipient_id, message, account } = req.body as Record<string, unknown>;
|
||||
if (!recipient_id || !message) { res.status(400).json({ error: 'recipient_id and message are required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('linkedin_send_message', { recipient_id, message, account });
|
||||
const result = await callTool(req, 'linkedin_send_message', { recipient_id, message, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1070,7 +1354,7 @@ app.post('/api/linkedin/message', requireAuth, async (req, res) => {
|
||||
app.get('/api/telegram/me', requireAuth, async (req, res) => {
|
||||
const account = req.query.account as string | undefined;
|
||||
try {
|
||||
const result = await handleToolCall('telegram_get_me', { account });
|
||||
const result = await callTool(req, 'telegram_get_me', { account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1081,7 +1365,7 @@ app.post('/api/telegram/message', requireAuth, async (req, res) => {
|
||||
const { chat_id, text, parse_mode, account } = req.body as Record<string, unknown>;
|
||||
if (!chat_id || !text) { res.status(400).json({ error: 'chat_id and text are required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('telegram_send_message', { chat_id, text, parse_mode, account });
|
||||
const result = await callTool(req, 'telegram_send_message', { chat_id, text, parse_mode, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1092,7 +1376,7 @@ app.post('/api/telegram/photo', requireAuth, async (req, res) => {
|
||||
const { chat_id, photo, caption, account } = req.body as Record<string, unknown>;
|
||||
if (!chat_id || !photo) { res.status(400).json({ error: 'chat_id and photo are required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('telegram_send_photo', { chat_id, photo, caption, account });
|
||||
const result = await callTool(req, 'telegram_send_photo', { chat_id, photo, caption, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1103,7 +1387,7 @@ app.get('/api/telegram/updates', requireAuth, async (req, res) => {
|
||||
const limit = req.query.limit ? parseInt(req.query.limit as string, 10) : undefined;
|
||||
const account = req.query.account as string | undefined;
|
||||
try {
|
||||
const result = await handleToolCall('telegram_get_updates', { limit, account });
|
||||
const result = await callTool(req, 'telegram_get_updates', { limit, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1115,7 +1399,7 @@ app.get('/api/telegram/chat', requireAuth, async (req, res) => {
|
||||
const account = req.query.account as string | undefined;
|
||||
if (!chat_id) { res.status(400).json({ error: 'chat_id is required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('telegram_get_chat', { chat_id, account });
|
||||
const result = await callTool(req, 'telegram_get_chat', { chat_id, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1126,7 +1410,7 @@ app.get('/api/telegram/chat', requireAuth, async (req, res) => {
|
||||
app.get('/api/discord/me', requireAuth, async (req, res) => {
|
||||
const account = req.query.account as string | undefined;
|
||||
try {
|
||||
const result = await handleToolCall('discord_get_me', { account });
|
||||
const result = await callTool(req, 'discord_get_me', { account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1136,7 +1420,7 @@ app.get('/api/discord/me', requireAuth, async (req, res) => {
|
||||
app.get('/api/discord/guilds', requireAuth, async (req, res) => {
|
||||
const account = req.query.account as string | undefined;
|
||||
try {
|
||||
const result = await handleToolCall('discord_get_guilds', { account });
|
||||
const result = await callTool(req, 'discord_get_guilds', { account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1148,7 +1432,7 @@ app.get('/api/discord/channels', requireAuth, async (req, res) => {
|
||||
const account = req.query.account as string | undefined;
|
||||
if (!guild_id) { res.status(400).json({ error: 'guild_id is required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('discord_get_channels', { guild_id, account });
|
||||
const result = await callTool(req, 'discord_get_channels', { guild_id, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1159,7 +1443,7 @@ app.post('/api/discord/message', requireAuth, async (req, res) => {
|
||||
const { channel_id, content, account } = req.body as Record<string, unknown>;
|
||||
if (!channel_id || !content) { res.status(400).json({ error: 'channel_id and content are required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('discord_send_message', { channel_id, content, account });
|
||||
const result = await callTool(req, 'discord_send_message', { channel_id, content, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1172,7 +1456,7 @@ app.get('/api/discord/messages', requireAuth, async (req, res) => {
|
||||
const account = req.query.account as string | undefined;
|
||||
if (!channel_id) { res.status(400).json({ error: 'channel_id is required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('discord_get_messages', { channel_id, limit, account });
|
||||
const result = await callTool(req, 'discord_get_messages', { channel_id, limit, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1183,7 +1467,7 @@ app.get('/api/discord/messages', requireAuth, async (req, res) => {
|
||||
app.get('/api/instagram/profile', requireAuth, async (req, res) => {
|
||||
const account = req.query.account as string | undefined;
|
||||
try {
|
||||
const result = await handleToolCall('instagram_get_profile', { account });
|
||||
const result = await callTool(req, 'instagram_get_profile', { account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1194,7 +1478,7 @@ app.get('/api/instagram/media', requireAuth, async (req, res) => {
|
||||
const limit = req.query.limit ? parseInt(req.query.limit as string, 10) : undefined;
|
||||
const account = req.query.account as string | undefined;
|
||||
try {
|
||||
const result = await handleToolCall('instagram_get_media', { limit, account });
|
||||
const result = await callTool(req, 'instagram_get_media', { limit, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1205,7 +1489,7 @@ app.post('/api/instagram/post', requireAuth, async (req, res) => {
|
||||
const { image_url, caption, account } = req.body as Record<string, unknown>;
|
||||
if (!image_url) { res.status(400).json({ error: 'image_url is required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('instagram_create_post', { image_url, caption, account });
|
||||
const result = await callTool(req, 'instagram_create_post', { image_url, caption, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1216,7 +1500,7 @@ app.post('/api/instagram/reel', requireAuth, async (req, res) => {
|
||||
const { video_url, caption, account } = req.body as Record<string, unknown>;
|
||||
if (!video_url) { res.status(400).json({ error: 'video_url is required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('instagram_create_reel', { video_url, caption, account });
|
||||
const result = await callTool(req, 'instagram_create_reel', { video_url, caption, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1230,7 +1514,7 @@ app.get('/api/twitter/search', requireAuth, async (req, res) => {
|
||||
const account = req.query.account as string | undefined;
|
||||
if (!query) { res.status(400).json({ error: 'query is required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('twitter_search_tweets', { query, max_results, account });
|
||||
const result = await callTool(req, 'twitter_search_tweets', { query, max_results, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1242,7 +1526,7 @@ app.get('/api/twitter/user', requireAuth, async (req, res) => {
|
||||
const account = req.query.account as string | undefined;
|
||||
if (!username) { res.status(400).json({ error: 'username is required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('twitter_get_user_profile', { username, account });
|
||||
const result = await callTool(req, 'twitter_get_user_profile', { username, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1255,7 +1539,7 @@ app.get('/api/twitter/tweets', requireAuth, async (req, res) => {
|
||||
const account = req.query.account as string | undefined;
|
||||
if (!username) { res.status(400).json({ error: 'username is required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('twitter_get_user_tweets', { username, max_results, account });
|
||||
const result = await callTool(req, 'twitter_get_user_tweets', { username, max_results, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1266,7 +1550,7 @@ app.post('/api/twitter/tweet', requireAuth, async (req, res) => {
|
||||
const { text, account } = req.body as Record<string, unknown>;
|
||||
if (!text) { res.status(400).json({ error: 'text is required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('twitter_create_tweet', { text, account });
|
||||
const result = await callTool(req, 'twitter_create_tweet', { text, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1277,7 +1561,7 @@ app.post('/api/twitter/video', requireAuth, async (req, res) => {
|
||||
const { video_url, text, account } = req.body as Record<string, unknown>;
|
||||
if (!video_url || !text) { res.status(400).json({ error: 'video_url and text are required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('twitter_upload_video', { video_url, text, account });
|
||||
const result = await callTool(req, 'twitter_upload_video', { video_url, text, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1288,7 +1572,7 @@ app.post('/api/twitter/video', requireAuth, async (req, res) => {
|
||||
app.get('/api/facebook/page', requireAuth, async (req, res) => {
|
||||
const account = req.query.account as string | undefined;
|
||||
try {
|
||||
const result = await handleToolCall('facebook_get_page', { account });
|
||||
const result = await callTool(req, 'facebook_get_page', { account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1299,7 +1583,7 @@ app.get('/api/facebook/posts', requireAuth, async (req, res) => {
|
||||
const limit = req.query.limit ? parseInt(req.query.limit as string, 10) : undefined;
|
||||
const account = req.query.account as string | undefined;
|
||||
try {
|
||||
const result = await handleToolCall('facebook_get_posts', { limit, account });
|
||||
const result = await callTool(req, 'facebook_get_posts', { limit, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1310,7 +1594,7 @@ app.post('/api/facebook/post', requireAuth, async (req, res) => {
|
||||
const { message, link, account } = req.body as Record<string, unknown>;
|
||||
if (!message) { res.status(400).json({ error: 'message is required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('facebook_create_post', { message, link, account });
|
||||
const result = await callTool(req, 'facebook_create_post', { message, link, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1321,7 +1605,7 @@ app.post('/api/facebook/photo', requireAuth, async (req, res) => {
|
||||
const { image_url, caption, account } = req.body as Record<string, unknown>;
|
||||
if (!image_url) { res.status(400).json({ error: 'image_url is required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('facebook_create_photo_post', { image_url, caption, account });
|
||||
const result = await callTool(req, 'facebook_create_photo_post', { image_url, caption, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1332,7 +1616,7 @@ app.post('/api/facebook/video', requireAuth, async (req, res) => {
|
||||
const { video_url, description, account } = req.body as Record<string, unknown>;
|
||||
if (!video_url) { res.status(400).json({ error: 'video_url is required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('facebook_create_video_post', { video_url, description, account });
|
||||
const result = await callTool(req, 'facebook_create_video_post', { video_url, description, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1343,7 +1627,7 @@ app.post('/api/facebook/video', requireAuth, async (req, res) => {
|
||||
app.get('/api/tiktok/profile', requireAuth, async (req, res) => {
|
||||
const account = req.query.account as string | undefined;
|
||||
try {
|
||||
const result = await handleToolCall('tiktok_get_profile', { account });
|
||||
const result = await callTool(req, 'tiktok_get_profile', { account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1353,7 +1637,7 @@ app.get('/api/tiktok/profile', requireAuth, async (req, res) => {
|
||||
app.get('/api/tiktok/creator-info', requireAuth, async (req, res) => {
|
||||
const account = req.query.account as string | undefined;
|
||||
try {
|
||||
const result = await handleToolCall('tiktok_get_creator_info', { account });
|
||||
const result = await callTool(req, 'tiktok_get_creator_info', { account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1364,7 +1648,7 @@ app.post('/api/tiktok/video', requireAuth, async (req, res) => {
|
||||
const { video_url, title, description, account } = req.body as Record<string, unknown>;
|
||||
if (!video_url) { res.status(400).json({ error: 'video_url is required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('tiktok_create_video', { video_url, title, description, account });
|
||||
const result = await callTool(req, 'tiktok_create_video', { video_url, title, description, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
@@ -1375,7 +1659,7 @@ app.post('/api/tiktok/video/status', requireAuth, async (req, res) => {
|
||||
const { publish_id, account } = req.body as Record<string, unknown>;
|
||||
if (!publish_id) { res.status(400).json({ error: 'publish_id is required' }); return; }
|
||||
try {
|
||||
const result = await handleToolCall('tiktok_get_video_status', { publish_id, account });
|
||||
const result = await callTool(req, 'tiktok_get_video_status', { publish_id, account });
|
||||
res.json(parseToolResult(result));
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: (err as Error).message });
|
||||
|
||||
Reference in New Issue
Block a user