@builderbot/manager 1.0.0 → 1.3.14-alpha.148
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.ts +1 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/bot-manager.d.ts.map +1 -1
- package/dist/flow-registry.d.ts +1 -1
- package/dist/flow-registry.d.ts.map +1 -1
- package/dist/index.cjs +605 -661
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/persistence.d.ts.map +1 -1
- package/dist/rate-limiter.d.ts.map +1 -1
- package/dist/schemas.d.ts.map +1 -1
- package/dist/swagger.d.ts.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var require$$1$5 = require('path');
|
|
4
3
|
var require$$9 = require('console');
|
|
5
4
|
var require$$0$9 = require('fs');
|
|
6
5
|
var require$$11 = require('node:events');
|
|
@@ -13,6 +12,7 @@ var require$$17 = require('assert');
|
|
|
13
12
|
var require$$18 = require('tty');
|
|
14
13
|
var require$$1$6 = require('util');
|
|
15
14
|
var require$$0$a = require('os');
|
|
15
|
+
var require$$1$5 = require('path');
|
|
16
16
|
var require$$2$3 = require('events');
|
|
17
17
|
var require$$0$b = require('child_process');
|
|
18
18
|
var require$$24 = require('net');
|
|
@@ -39540,15 +39540,15 @@ class BotManager {
|
|
|
39540
39540
|
enabled: config.autoReconnect?.enabled ?? true,
|
|
39541
39541
|
maxRetries: config.autoReconnect?.maxRetries ?? 5,
|
|
39542
39542
|
initialDelay: config.autoReconnect?.initialDelay ?? 1000,
|
|
39543
|
-
maxDelay: config.autoReconnect?.maxDelay ?? 30000
|
|
39544
|
-
}
|
|
39543
|
+
maxDelay: config.autoReconnect?.maxDelay ?? 30000,
|
|
39544
|
+
},
|
|
39545
39545
|
};
|
|
39546
39546
|
}
|
|
39547
39547
|
/**
|
|
39548
39548
|
* Create a new bot instance for a tenant
|
|
39549
39549
|
*/
|
|
39550
39550
|
async createBot(tenantConfig) {
|
|
39551
|
-
const { tenantId, name, flows, port, providerOptions = {}, databaseOptions = {}, providerClass, databaseClass } = tenantConfig;
|
|
39551
|
+
const { tenantId, name, flows, port, providerOptions = {}, databaseOptions = {}, providerClass, databaseClass, } = tenantConfig;
|
|
39552
39552
|
if (this.bots.has(tenantId)) {
|
|
39553
39553
|
throw new Error(`Bot with tenantId "${tenantId}" already exists`);
|
|
39554
39554
|
}
|
|
@@ -39566,13 +39566,13 @@ class BotManager {
|
|
|
39566
39566
|
const adapterProvider = createProvider_1(ProviderToUse, {
|
|
39567
39567
|
...this.config.defaultProviderOptions,
|
|
39568
39568
|
...providerOptions,
|
|
39569
|
-
name: sessionPath // This creates isolated session folder per tenant
|
|
39569
|
+
name: sessionPath, // This creates isolated session folder per tenant
|
|
39570
39570
|
});
|
|
39571
39571
|
// Use tenant-specific or default database class
|
|
39572
39572
|
const DatabaseToUse = databaseClass || this.config.defaultDatabaseClass || MemoryDB_1;
|
|
39573
39573
|
const adapterDB = new DatabaseToUse({
|
|
39574
39574
|
...this.config.defaultDatabaseOptions,
|
|
39575
|
-
...databaseOptions
|
|
39575
|
+
...databaseOptions,
|
|
39576
39576
|
});
|
|
39577
39577
|
const { handleCtx, httpServer } = await createBot_1({
|
|
39578
39578
|
flow: adapterFlow,
|
|
@@ -39591,7 +39591,7 @@ class BotManager {
|
|
|
39591
39591
|
createdAt: new Date(),
|
|
39592
39592
|
status: 'initializing',
|
|
39593
39593
|
providerType: ProviderToUse.name,
|
|
39594
|
-
databaseType: DatabaseToUse.name
|
|
39594
|
+
databaseType: DatabaseToUse.name,
|
|
39595
39595
|
};
|
|
39596
39596
|
this.bots.set(tenantId, botInstance);
|
|
39597
39597
|
// Initialize reconnect state
|
|
@@ -39599,7 +39599,7 @@ class BotManager {
|
|
|
39599
39599
|
attempts: 0,
|
|
39600
39600
|
lastAttempt: new Date(),
|
|
39601
39601
|
nextDelay: this.config.autoReconnect.initialDelay,
|
|
39602
|
-
isReconnecting: false
|
|
39602
|
+
isReconnecting: false,
|
|
39603
39603
|
});
|
|
39604
39604
|
// Setup provider event listeners for real status tracking
|
|
39605
39605
|
this.setupProviderEvents(tenantId, adapterProvider);
|
|
@@ -39612,7 +39612,7 @@ class BotManager {
|
|
|
39612
39612
|
port,
|
|
39613
39613
|
sessionPath,
|
|
39614
39614
|
providerType: botInstance.providerType,
|
|
39615
|
-
databaseType: botInstance.databaseType
|
|
39615
|
+
databaseType: botInstance.databaseType,
|
|
39616
39616
|
});
|
|
39617
39617
|
return botInstance;
|
|
39618
39618
|
}
|
|
@@ -39668,7 +39668,7 @@ class BotManager {
|
|
|
39668
39668
|
const { maxRetries, maxDelay } = this.config.autoReconnect;
|
|
39669
39669
|
if (reconnectState.attempts >= maxRetries) {
|
|
39670
39670
|
this.emit('bot:error', tenantId, {
|
|
39671
|
-
error: new Error(`Max reconnection attempts (${maxRetries}) reached`)
|
|
39671
|
+
error: new Error(`Max reconnection attempts (${maxRetries}) reached`),
|
|
39672
39672
|
});
|
|
39673
39673
|
return;
|
|
39674
39674
|
}
|
|
@@ -39678,7 +39678,7 @@ class BotManager {
|
|
|
39678
39678
|
this.emit('bot:reconnecting', tenantId, {
|
|
39679
39679
|
attempt: reconnectState.attempts,
|
|
39680
39680
|
maxRetries,
|
|
39681
|
-
nextDelay: reconnectState.nextDelay
|
|
39681
|
+
nextDelay: reconnectState.nextDelay,
|
|
39682
39682
|
});
|
|
39683
39683
|
// Wait before reconnecting
|
|
39684
39684
|
await this.delay(reconnectState.nextDelay);
|
|
@@ -39696,7 +39696,7 @@ class BotManager {
|
|
|
39696
39696
|
reconnectState.nextDelay = Math.min(reconnectState.nextDelay * 2, maxDelay);
|
|
39697
39697
|
this.emit('bot:error', tenantId, {
|
|
39698
39698
|
error,
|
|
39699
|
-
reconnectAttempt: reconnectState.attempts
|
|
39699
|
+
reconnectAttempt: reconnectState.attempts,
|
|
39700
39700
|
});
|
|
39701
39701
|
// Try again
|
|
39702
39702
|
this.attemptReconnect(tenantId);
|
|
@@ -39706,7 +39706,7 @@ class BotManager {
|
|
|
39706
39706
|
* Helper delay function
|
|
39707
39707
|
*/
|
|
39708
39708
|
delay(ms) {
|
|
39709
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
39709
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
39710
39710
|
}
|
|
39711
39711
|
/**
|
|
39712
39712
|
* Get a bot instance by tenant ID
|
|
@@ -39766,7 +39766,7 @@ class BotManager {
|
|
|
39766
39766
|
*/
|
|
39767
39767
|
async shutdown() {
|
|
39768
39768
|
const tenantIds = this.listBots();
|
|
39769
|
-
await Promise.all(tenantIds.map(tenantId => this.removeBot(tenantId)));
|
|
39769
|
+
await Promise.all(tenantIds.map((tenantId) => this.removeBot(tenantId)));
|
|
39770
39770
|
}
|
|
39771
39771
|
/**
|
|
39772
39772
|
* Register an event handler
|
|
@@ -39792,7 +39792,7 @@ class BotManager {
|
|
|
39792
39792
|
emit(event, tenantId, data) {
|
|
39793
39793
|
const handlers = this.eventHandlers.get(event);
|
|
39794
39794
|
if (handlers) {
|
|
39795
|
-
handlers.forEach(handler => {
|
|
39795
|
+
handlers.forEach((handler) => {
|
|
39796
39796
|
try {
|
|
39797
39797
|
handler(tenantId, data);
|
|
39798
39798
|
}
|
|
@@ -39836,7 +39836,7 @@ class BotManager {
|
|
|
39836
39836
|
attempts: 0,
|
|
39837
39837
|
lastAttempt: new Date(),
|
|
39838
39838
|
nextDelay: this.config.autoReconnect.initialDelay,
|
|
39839
|
-
isReconnecting: false
|
|
39839
|
+
isReconnecting: false,
|
|
39840
39840
|
});
|
|
39841
39841
|
try {
|
|
39842
39842
|
await this.removeBot(tenantId);
|
|
@@ -39861,7 +39861,7 @@ class BotManager {
|
|
|
39861
39861
|
tenantId,
|
|
39862
39862
|
name: existingBot?.name || tenantId,
|
|
39863
39863
|
flows: [],
|
|
39864
|
-
port: existingBot?.port
|
|
39864
|
+
port: existingBot?.port,
|
|
39865
39865
|
};
|
|
39866
39866
|
// Remove existing bot
|
|
39867
39867
|
await this.removeBot(tenantId);
|
|
@@ -39876,7 +39876,7 @@ class BotManager {
|
|
|
39876
39876
|
* Get summary info of all bots (useful for dashboards)
|
|
39877
39877
|
*/
|
|
39878
39878
|
getBotsInfo() {
|
|
39879
|
-
return this.getAllBots().map(bot => ({
|
|
39879
|
+
return this.getAllBots().map((bot) => ({
|
|
39880
39880
|
tenantId: bot.tenantId,
|
|
39881
39881
|
name: bot.name,
|
|
39882
39882
|
status: bot.status,
|
|
@@ -39885,7 +39885,7 @@ class BotManager {
|
|
|
39885
39885
|
uptime: Date.now() - bot.createdAt.getTime(),
|
|
39886
39886
|
providerType: bot.providerType,
|
|
39887
39887
|
databaseType: bot.databaseType,
|
|
39888
|
-
reconnectState: this.reconnectStates.get(bot.tenantId)
|
|
39888
|
+
reconnectState: this.reconnectStates.get(bot.tenantId),
|
|
39889
39889
|
}));
|
|
39890
39890
|
}
|
|
39891
39891
|
/**
|
|
@@ -39898,7 +39898,7 @@ class BotManager {
|
|
|
39898
39898
|
connected: 0,
|
|
39899
39899
|
disconnected: 0,
|
|
39900
39900
|
error: 0,
|
|
39901
|
-
initializing: 0
|
|
39901
|
+
initializing: 0,
|
|
39902
39902
|
};
|
|
39903
39903
|
for (const bot of bots) {
|
|
39904
39904
|
statusCounts[bot.status]++;
|
|
@@ -39914,7 +39914,7 @@ class BotManager {
|
|
|
39914
39914
|
status,
|
|
39915
39915
|
bots: statusCounts,
|
|
39916
39916
|
memory: process.memoryUsage(),
|
|
39917
|
-
uptime: process.uptime()
|
|
39917
|
+
uptime: process.uptime(),
|
|
39918
39918
|
};
|
|
39919
39919
|
}
|
|
39920
39920
|
/**
|
|
@@ -39936,6 +39936,324 @@ class BotManager {
|
|
|
39936
39936
|
}
|
|
39937
39937
|
}
|
|
39938
39938
|
|
|
39939
|
+
/**
|
|
39940
|
+
* FlowRegistry manages flow definitions that can be used when creating bots
|
|
39941
|
+
*/
|
|
39942
|
+
class FlowRegistry {
|
|
39943
|
+
constructor() {
|
|
39944
|
+
this.flows = new Map();
|
|
39945
|
+
}
|
|
39946
|
+
/**
|
|
39947
|
+
* Register a programmatic flow (created with addKeyword)
|
|
39948
|
+
*/
|
|
39949
|
+
register(id, name, flow) {
|
|
39950
|
+
const definition = {
|
|
39951
|
+
id,
|
|
39952
|
+
name,
|
|
39953
|
+
flow,
|
|
39954
|
+
dynamic: false,
|
|
39955
|
+
createdAt: new Date(),
|
|
39956
|
+
updatedAt: new Date(),
|
|
39957
|
+
};
|
|
39958
|
+
this.flows.set(id, definition);
|
|
39959
|
+
return definition;
|
|
39960
|
+
}
|
|
39961
|
+
/**
|
|
39962
|
+
* Register a dynamic flow from JSON configuration
|
|
39963
|
+
*/
|
|
39964
|
+
registerDynamic(config) {
|
|
39965
|
+
const { id, name, keyword, steps } = config;
|
|
39966
|
+
const flow = this.buildFlowFromSteps(keyword, steps);
|
|
39967
|
+
const definition = {
|
|
39968
|
+
id,
|
|
39969
|
+
name,
|
|
39970
|
+
flow,
|
|
39971
|
+
dynamic: true,
|
|
39972
|
+
config,
|
|
39973
|
+
createdAt: new Date(),
|
|
39974
|
+
updatedAt: new Date(),
|
|
39975
|
+
};
|
|
39976
|
+
this.flows.set(id, definition);
|
|
39977
|
+
return definition;
|
|
39978
|
+
}
|
|
39979
|
+
/**
|
|
39980
|
+
* Update a dynamic flow
|
|
39981
|
+
*/
|
|
39982
|
+
update(id, updates) {
|
|
39983
|
+
const existing = this.flows.get(id);
|
|
39984
|
+
if (!existing || !existing.dynamic || !existing.config) {
|
|
39985
|
+
return null;
|
|
39986
|
+
}
|
|
39987
|
+
// Merge updates with existing config
|
|
39988
|
+
const newConfig = {
|
|
39989
|
+
...existing.config,
|
|
39990
|
+
...(updates.name && { name: updates.name }),
|
|
39991
|
+
...(updates.keyword && { keyword: updates.keyword }),
|
|
39992
|
+
...(updates.steps && { steps: updates.steps }),
|
|
39993
|
+
};
|
|
39994
|
+
// Rebuild flow
|
|
39995
|
+
const flow = this.buildFlowFromSteps(newConfig.keyword, newConfig.steps);
|
|
39996
|
+
const definition = {
|
|
39997
|
+
id,
|
|
39998
|
+
name: newConfig.name,
|
|
39999
|
+
flow,
|
|
40000
|
+
dynamic: true,
|
|
40001
|
+
config: newConfig,
|
|
40002
|
+
createdAt: existing.createdAt,
|
|
40003
|
+
updatedAt: new Date(),
|
|
40004
|
+
};
|
|
40005
|
+
this.flows.set(id, definition);
|
|
40006
|
+
return definition;
|
|
40007
|
+
}
|
|
40008
|
+
/**
|
|
40009
|
+
* Remove a flow from registry
|
|
40010
|
+
*/
|
|
40011
|
+
remove(id) {
|
|
40012
|
+
return this.flows.delete(id);
|
|
40013
|
+
}
|
|
40014
|
+
/**
|
|
40015
|
+
* Get a flow by ID
|
|
40016
|
+
*/
|
|
40017
|
+
get(id) {
|
|
40018
|
+
return this.flows.get(id);
|
|
40019
|
+
}
|
|
40020
|
+
/**
|
|
40021
|
+
* Get all registered flows
|
|
40022
|
+
*/
|
|
40023
|
+
getAll() {
|
|
40024
|
+
return Array.from(this.flows.values());
|
|
40025
|
+
}
|
|
40026
|
+
/**
|
|
40027
|
+
* Check if a flow exists
|
|
40028
|
+
*/
|
|
40029
|
+
has(id) {
|
|
40030
|
+
return this.flows.has(id);
|
|
40031
|
+
}
|
|
40032
|
+
/**
|
|
40033
|
+
* Get all flow IDs
|
|
40034
|
+
*/
|
|
40035
|
+
getIds() {
|
|
40036
|
+
return Array.from(this.flows.keys());
|
|
40037
|
+
}
|
|
40038
|
+
/**
|
|
40039
|
+
* Get count of registered flows
|
|
40040
|
+
*/
|
|
40041
|
+
count() {
|
|
40042
|
+
return this.flows.size;
|
|
40043
|
+
}
|
|
40044
|
+
/**
|
|
40045
|
+
* Clear all flows
|
|
40046
|
+
*/
|
|
40047
|
+
clear() {
|
|
40048
|
+
this.flows.clear();
|
|
40049
|
+
}
|
|
40050
|
+
/**
|
|
40051
|
+
* Get flows by type (dynamic or programmatic)
|
|
40052
|
+
*/
|
|
40053
|
+
getByType(dynamic) {
|
|
40054
|
+
return this.getAll().filter((f) => f.dynamic === dynamic);
|
|
40055
|
+
}
|
|
40056
|
+
/**
|
|
40057
|
+
* Resolve multiple flow IDs to Flow objects
|
|
40058
|
+
*/
|
|
40059
|
+
resolveFlows(flowIds) {
|
|
40060
|
+
const flows = [];
|
|
40061
|
+
const missing = [];
|
|
40062
|
+
for (const id of flowIds) {
|
|
40063
|
+
const definition = this.flows.get(id);
|
|
40064
|
+
if (definition) {
|
|
40065
|
+
flows.push(definition.flow);
|
|
40066
|
+
}
|
|
40067
|
+
else {
|
|
40068
|
+
missing.push(id);
|
|
40069
|
+
}
|
|
40070
|
+
}
|
|
40071
|
+
return { flows, missing };
|
|
40072
|
+
}
|
|
40073
|
+
/**
|
|
40074
|
+
* Build a flow from steps configuration
|
|
40075
|
+
*/
|
|
40076
|
+
buildFlowFromSteps(keyword, steps) {
|
|
40077
|
+
const keywords = Array.isArray(keyword)
|
|
40078
|
+
? keyword
|
|
40079
|
+
: keyword;
|
|
40080
|
+
let flow = addKeyword_1(keywords);
|
|
40081
|
+
for (const step of steps) {
|
|
40082
|
+
const options = {};
|
|
40083
|
+
if (step.delay)
|
|
40084
|
+
options.delay = step.delay;
|
|
40085
|
+
if (step.media)
|
|
40086
|
+
options.media = step.media;
|
|
40087
|
+
if (step.capture)
|
|
40088
|
+
options.capture = step.capture;
|
|
40089
|
+
flow = flow.addAnswer(step.answer, Object.keys(options).length > 0 ? options : undefined);
|
|
40090
|
+
}
|
|
40091
|
+
return flow;
|
|
40092
|
+
}
|
|
40093
|
+
/**
|
|
40094
|
+
* Export all dynamic flows as serializable configs
|
|
40095
|
+
*/
|
|
40096
|
+
exportDynamicFlows() {
|
|
40097
|
+
return this.getByType(true)
|
|
40098
|
+
.filter((f) => f.config)
|
|
40099
|
+
.map((f) => f.config);
|
|
40100
|
+
}
|
|
40101
|
+
/**
|
|
40102
|
+
* Import dynamic flows from configs
|
|
40103
|
+
*/
|
|
40104
|
+
importDynamicFlows(configs) {
|
|
40105
|
+
const failed = [];
|
|
40106
|
+
let imported = 0;
|
|
40107
|
+
for (const config of configs) {
|
|
40108
|
+
try {
|
|
40109
|
+
if (!this.has(config.id)) {
|
|
40110
|
+
this.registerDynamic(config);
|
|
40111
|
+
imported++;
|
|
40112
|
+
}
|
|
40113
|
+
}
|
|
40114
|
+
catch {
|
|
40115
|
+
failed.push(config.id);
|
|
40116
|
+
}
|
|
40117
|
+
}
|
|
40118
|
+
return { imported, failed };
|
|
40119
|
+
}
|
|
40120
|
+
}
|
|
40121
|
+
|
|
40122
|
+
/**
|
|
40123
|
+
* Simple in-memory rate limiter
|
|
40124
|
+
*/
|
|
40125
|
+
class RateLimiter {
|
|
40126
|
+
constructor(config = {}) {
|
|
40127
|
+
this.store = new Map();
|
|
40128
|
+
this.cleanupInterval = null;
|
|
40129
|
+
this.config = {
|
|
40130
|
+
maxRequests: config.maxRequests ?? 100,
|
|
40131
|
+
windowMs: config.windowMs ?? 60000, // 1 minute
|
|
40132
|
+
message: config.message ?? 'Too many requests, please try again later',
|
|
40133
|
+
skipPaths: config.skipPaths ?? ['/docs', '/api/health'],
|
|
40134
|
+
keyExtractor: config.keyExtractor ?? this.defaultKeyExtractor,
|
|
40135
|
+
};
|
|
40136
|
+
// Cleanup expired entries every minute
|
|
40137
|
+
this.cleanupInterval = setInterval(() => this.cleanup(), 60000);
|
|
40138
|
+
}
|
|
40139
|
+
/**
|
|
40140
|
+
* Default key extractor - uses IP address
|
|
40141
|
+
*/
|
|
40142
|
+
defaultKeyExtractor(req) {
|
|
40143
|
+
const forwarded = req.headers['x-forwarded-for'];
|
|
40144
|
+
if (forwarded) {
|
|
40145
|
+
const ips = Array.isArray(forwarded) ? forwarded[0] : forwarded.split(',')[0];
|
|
40146
|
+
return ips.trim();
|
|
40147
|
+
}
|
|
40148
|
+
return req.socket?.remoteAddress || 'unknown';
|
|
40149
|
+
}
|
|
40150
|
+
/**
|
|
40151
|
+
* Check if request should be rate limited
|
|
40152
|
+
*/
|
|
40153
|
+
isRateLimited(req) {
|
|
40154
|
+
const url = req.url || '/';
|
|
40155
|
+
// Skip certain paths
|
|
40156
|
+
if (this.config.skipPaths.some((path) => url.startsWith(path))) {
|
|
40157
|
+
return { limited: false, remaining: this.config.maxRequests, resetTime: 0 };
|
|
40158
|
+
}
|
|
40159
|
+
const key = this.config.keyExtractor(req);
|
|
40160
|
+
const now = Date.now();
|
|
40161
|
+
let entry = this.store.get(key);
|
|
40162
|
+
// If no entry or window expired, create new entry
|
|
40163
|
+
if (!entry || now > entry.resetTime) {
|
|
40164
|
+
entry = {
|
|
40165
|
+
count: 1,
|
|
40166
|
+
resetTime: now + this.config.windowMs,
|
|
40167
|
+
};
|
|
40168
|
+
this.store.set(key, entry);
|
|
40169
|
+
return {
|
|
40170
|
+
limited: false,
|
|
40171
|
+
remaining: this.config.maxRequests - 1,
|
|
40172
|
+
resetTime: entry.resetTime,
|
|
40173
|
+
};
|
|
40174
|
+
}
|
|
40175
|
+
// Increment count
|
|
40176
|
+
entry.count++;
|
|
40177
|
+
// Check if over limit
|
|
40178
|
+
if (entry.count > this.config.maxRequests) {
|
|
40179
|
+
return {
|
|
40180
|
+
limited: true,
|
|
40181
|
+
remaining: 0,
|
|
40182
|
+
resetTime: entry.resetTime,
|
|
40183
|
+
};
|
|
40184
|
+
}
|
|
40185
|
+
return {
|
|
40186
|
+
limited: false,
|
|
40187
|
+
remaining: this.config.maxRequests - entry.count,
|
|
40188
|
+
resetTime: entry.resetTime,
|
|
40189
|
+
};
|
|
40190
|
+
}
|
|
40191
|
+
/**
|
|
40192
|
+
* Create middleware function
|
|
40193
|
+
*/
|
|
40194
|
+
middleware() {
|
|
40195
|
+
return (req, res, next) => {
|
|
40196
|
+
const result = this.isRateLimited(req);
|
|
40197
|
+
// Set rate limit headers
|
|
40198
|
+
res.setHeader('X-RateLimit-Limit', this.config.maxRequests.toString());
|
|
40199
|
+
res.setHeader('X-RateLimit-Remaining', result.remaining.toString());
|
|
40200
|
+
res.setHeader('X-RateLimit-Reset', result.resetTime.toString());
|
|
40201
|
+
if (result.limited) {
|
|
40202
|
+
res.setHeader('Retry-After', Math.ceil((result.resetTime - Date.now()) / 1000).toString());
|
|
40203
|
+
res.writeHead(429, { 'Content-Type': 'application/json' });
|
|
40204
|
+
res.end(JSON.stringify({
|
|
40205
|
+
error: this.config.message,
|
|
40206
|
+
retryAfter: Math.ceil((result.resetTime - Date.now()) / 1000),
|
|
40207
|
+
}));
|
|
40208
|
+
return;
|
|
40209
|
+
}
|
|
40210
|
+
next();
|
|
40211
|
+
};
|
|
40212
|
+
}
|
|
40213
|
+
/**
|
|
40214
|
+
* Reset rate limit for a specific key
|
|
40215
|
+
*/
|
|
40216
|
+
reset(key) {
|
|
40217
|
+
this.store.delete(key);
|
|
40218
|
+
}
|
|
40219
|
+
/**
|
|
40220
|
+
* Clear all rate limit data
|
|
40221
|
+
*/
|
|
40222
|
+
clear() {
|
|
40223
|
+
this.store.clear();
|
|
40224
|
+
}
|
|
40225
|
+
/**
|
|
40226
|
+
* Cleanup expired entries
|
|
40227
|
+
*/
|
|
40228
|
+
cleanup() {
|
|
40229
|
+
const now = Date.now();
|
|
40230
|
+
for (const [key, entry] of this.store.entries()) {
|
|
40231
|
+
if (now > entry.resetTime) {
|
|
40232
|
+
this.store.delete(key);
|
|
40233
|
+
}
|
|
40234
|
+
}
|
|
40235
|
+
}
|
|
40236
|
+
/**
|
|
40237
|
+
* Stop the cleanup interval
|
|
40238
|
+
*/
|
|
40239
|
+
destroy() {
|
|
40240
|
+
if (this.cleanupInterval) {
|
|
40241
|
+
clearInterval(this.cleanupInterval);
|
|
40242
|
+
this.cleanupInterval = null;
|
|
40243
|
+
}
|
|
40244
|
+
this.store.clear();
|
|
40245
|
+
}
|
|
40246
|
+
/**
|
|
40247
|
+
* Get current stats
|
|
40248
|
+
*/
|
|
40249
|
+
getStats() {
|
|
40250
|
+
return {
|
|
40251
|
+
activeKeys: this.store.size,
|
|
40252
|
+
config: this.config,
|
|
40253
|
+
};
|
|
40254
|
+
}
|
|
40255
|
+
}
|
|
40256
|
+
|
|
39939
40257
|
/**
|
|
39940
40258
|
* Reserved tenant IDs that cannot be used
|
|
39941
40259
|
*/
|
|
@@ -39954,31 +40272,21 @@ const tenantIdSchema = zod.z
|
|
|
39954
40272
|
*/
|
|
39955
40273
|
const createBotSchema = zod.z.object({
|
|
39956
40274
|
tenantId: tenantIdSchema,
|
|
39957
|
-
name: zod.z
|
|
39958
|
-
|
|
39959
|
-
.min(1, 'name cannot be empty')
|
|
39960
|
-
.max(100, 'name must be 100 characters or less')
|
|
39961
|
-
.optional(),
|
|
39962
|
-
flowIds: zod.z
|
|
39963
|
-
.array(zod.z.string().min(1, 'flowId cannot be empty'))
|
|
39964
|
-
.min(1, 'At least one flowId is required'),
|
|
40275
|
+
name: zod.z.string().min(1, 'name cannot be empty').max(100, 'name must be 100 characters or less').optional(),
|
|
40276
|
+
flowIds: zod.z.array(zod.z.string().min(1, 'flowId cannot be empty')).min(1, 'At least one flowId is required'),
|
|
39965
40277
|
port: zod.z
|
|
39966
40278
|
.number()
|
|
39967
40279
|
.int('port must be an integer')
|
|
39968
40280
|
.min(1024, 'port must be 1024 or higher')
|
|
39969
40281
|
.max(65535, 'port must be 65535 or lower')
|
|
39970
40282
|
.optional(),
|
|
39971
|
-
providerOptions: zod.z.record(zod.z.any()).optional()
|
|
40283
|
+
providerOptions: zod.z.record(zod.z.any()).optional(),
|
|
39972
40284
|
});
|
|
39973
40285
|
/**
|
|
39974
40286
|
* Schema for updating a bot
|
|
39975
40287
|
*/
|
|
39976
40288
|
const updateBotSchema = zod.z.object({
|
|
39977
|
-
name: zod.z
|
|
39978
|
-
.string()
|
|
39979
|
-
.min(1, 'name cannot be empty')
|
|
39980
|
-
.max(100, 'name must be 100 characters or less')
|
|
39981
|
-
.optional()
|
|
40289
|
+
name: zod.z.string().min(1, 'name cannot be empty').max(100, 'name must be 100 characters or less').optional(),
|
|
39982
40290
|
});
|
|
39983
40291
|
/**
|
|
39984
40292
|
* Schema for sending a message
|
|
@@ -39989,33 +40297,21 @@ const sendMessageSchema = zod.z.object({
|
|
|
39989
40297
|
.min(10, 'number must be at least 10 characters')
|
|
39990
40298
|
.max(20, 'number must be 20 characters or less')
|
|
39991
40299
|
.regex(/^[0-9+]+$/, 'number can only contain digits and + symbol'),
|
|
39992
|
-
message: zod.z
|
|
39993
|
-
|
|
39994
|
-
.min(1, 'message cannot be empty')
|
|
39995
|
-
.max(4096, 'message must be 4096 characters or less'),
|
|
39996
|
-
media: zod.z
|
|
39997
|
-
.string()
|
|
39998
|
-
.url('media must be a valid URL')
|
|
39999
|
-
.optional()
|
|
40300
|
+
message: zod.z.string().min(1, 'message cannot be empty').max(4096, 'message must be 4096 characters or less'),
|
|
40301
|
+
media: zod.z.string().url('media must be a valid URL').optional(),
|
|
40000
40302
|
});
|
|
40001
40303
|
/**
|
|
40002
40304
|
* Schema for restarting a bot
|
|
40003
40305
|
*/
|
|
40004
40306
|
const restartBotSchema = zod.z.object({
|
|
40005
|
-
flowIds: zod.z
|
|
40006
|
-
.array(zod.z.string().min(1))
|
|
40007
|
-
.min(1, 'At least one flowId is required'),
|
|
40307
|
+
flowIds: zod.z.array(zod.z.string().min(1)).min(1, 'At least one flowId is required'),
|
|
40008
40308
|
port: zod.z
|
|
40009
40309
|
.number()
|
|
40010
40310
|
.int('port must be an integer')
|
|
40011
40311
|
.min(1024, 'port must be 1024 or higher')
|
|
40012
40312
|
.max(65535, 'port must be 65535 or lower')
|
|
40013
40313
|
.optional(),
|
|
40014
|
-
name: zod.z
|
|
40015
|
-
.string()
|
|
40016
|
-
.min(1)
|
|
40017
|
-
.max(100)
|
|
40018
|
-
.optional()
|
|
40314
|
+
name: zod.z.string().min(1).max(100).optional(),
|
|
40019
40315
|
});
|
|
40020
40316
|
/**
|
|
40021
40317
|
* Reserved flow IDs
|
|
@@ -40032,7 +40328,7 @@ const flowStepSchema = zod.z.object({
|
|
|
40032
40328
|
/** Optional media URL to attach */
|
|
40033
40329
|
media: zod.z.string().url().optional(),
|
|
40034
40330
|
/** Whether to capture user response */
|
|
40035
|
-
capture: zod.z.boolean().optional()
|
|
40331
|
+
capture: zod.z.boolean().optional(),
|
|
40036
40332
|
});
|
|
40037
40333
|
/**
|
|
40038
40334
|
* Schema for creating a dynamic flow
|
|
@@ -40048,23 +40344,17 @@ const createFlowSchema = zod.z.object({
|
|
|
40048
40344
|
/** Display name for the flow */
|
|
40049
40345
|
name: zod.z.string().min(1).max(100),
|
|
40050
40346
|
/** Keywords that trigger this flow */
|
|
40051
|
-
keyword: zod.z.union([
|
|
40052
|
-
zod.z.string().min(1),
|
|
40053
|
-
zod.z.array(zod.z.string().min(1)).min(1)
|
|
40054
|
-
]),
|
|
40347
|
+
keyword: zod.z.union([zod.z.string().min(1), zod.z.array(zod.z.string().min(1)).min(1)]),
|
|
40055
40348
|
/** Steps/answers in the flow */
|
|
40056
|
-
steps: zod.z.array(flowStepSchema).min(1, 'At least one step is required')
|
|
40349
|
+
steps: zod.z.array(flowStepSchema).min(1, 'At least one step is required'),
|
|
40057
40350
|
});
|
|
40058
40351
|
/**
|
|
40059
40352
|
* Schema for updating a flow
|
|
40060
40353
|
*/
|
|
40061
40354
|
const updateFlowSchema = zod.z.object({
|
|
40062
40355
|
name: zod.z.string().min(1).max(100).optional(),
|
|
40063
|
-
keyword: zod.z.union([
|
|
40064
|
-
|
|
40065
|
-
zod.z.array(zod.z.string().min(1)).min(1)
|
|
40066
|
-
]).optional(),
|
|
40067
|
-
steps: zod.z.array(flowStepSchema).min(1).optional()
|
|
40356
|
+
keyword: zod.z.union([zod.z.string().min(1), zod.z.array(zod.z.string().min(1)).min(1)]).optional(),
|
|
40357
|
+
steps: zod.z.array(flowStepSchema).min(1).optional(),
|
|
40068
40358
|
});
|
|
40069
40359
|
/**
|
|
40070
40360
|
* Validate data against a Zod schema
|
|
@@ -40074,17 +40364,17 @@ function validate(schema, data) {
|
|
|
40074
40364
|
if (result.success) {
|
|
40075
40365
|
return {
|
|
40076
40366
|
success: true,
|
|
40077
|
-
data: result.data
|
|
40367
|
+
data: result.data,
|
|
40078
40368
|
};
|
|
40079
40369
|
}
|
|
40080
40370
|
const errors = result.error.errors.map((err) => ({
|
|
40081
40371
|
field: err.path.join('.') || 'root',
|
|
40082
|
-
message: err.message
|
|
40372
|
+
message: err.message,
|
|
40083
40373
|
}));
|
|
40084
40374
|
return {
|
|
40085
40375
|
success: false,
|
|
40086
40376
|
error: errors.map((e) => `${e.field}: ${e.message}`).join(', '),
|
|
40087
|
-
errors
|
|
40377
|
+
errors,
|
|
40088
40378
|
};
|
|
40089
40379
|
}
|
|
40090
40380
|
|
|
@@ -40098,19 +40388,19 @@ const openApiSpec = {
|
|
|
40098
40388
|
description: 'Multi-tenant WhatsApp Bot Manager REST API. Manage bots, flows, and send messages.',
|
|
40099
40389
|
version: '1.0.0',
|
|
40100
40390
|
contact: {
|
|
40101
|
-
name: 'BotManager'
|
|
40102
|
-
}
|
|
40391
|
+
name: 'BotManager',
|
|
40392
|
+
},
|
|
40103
40393
|
},
|
|
40104
40394
|
servers: [
|
|
40105
40395
|
{
|
|
40106
40396
|
url: '/api',
|
|
40107
|
-
description: 'API Server'
|
|
40108
|
-
}
|
|
40397
|
+
description: 'API Server',
|
|
40398
|
+
},
|
|
40109
40399
|
],
|
|
40110
40400
|
tags: [
|
|
40111
40401
|
{ name: 'Health', description: 'Health check endpoints' },
|
|
40112
40402
|
{ name: 'Flows', description: 'Flow management' },
|
|
40113
|
-
{ name: 'Bots', description: 'Bot management' }
|
|
40403
|
+
{ name: 'Bots', description: 'Bot management' },
|
|
40114
40404
|
],
|
|
40115
40405
|
paths: {
|
|
40116
40406
|
'/health': {
|
|
@@ -40128,14 +40418,14 @@ const openApiSpec = {
|
|
|
40128
40418
|
properties: {
|
|
40129
40419
|
status: { type: 'string', example: 'ok' },
|
|
40130
40420
|
timestamp: { type: 'string', format: 'date-time' },
|
|
40131
|
-
botsCount: { type: 'integer', example: 2 }
|
|
40132
|
-
}
|
|
40133
|
-
}
|
|
40134
|
-
}
|
|
40135
|
-
}
|
|
40136
|
-
}
|
|
40137
|
-
}
|
|
40138
|
-
}
|
|
40421
|
+
botsCount: { type: 'integer', example: 2 },
|
|
40422
|
+
},
|
|
40423
|
+
},
|
|
40424
|
+
},
|
|
40425
|
+
},
|
|
40426
|
+
},
|
|
40427
|
+
},
|
|
40428
|
+
},
|
|
40139
40429
|
},
|
|
40140
40430
|
'/flows': {
|
|
40141
40431
|
get: {
|
|
@@ -40153,14 +40443,14 @@ const openApiSpec = {
|
|
|
40153
40443
|
count: { type: 'integer' },
|
|
40154
40444
|
flows: {
|
|
40155
40445
|
type: 'array',
|
|
40156
|
-
items: { $ref: '#/components/schemas/FlowInfo' }
|
|
40157
|
-
}
|
|
40158
|
-
}
|
|
40159
|
-
}
|
|
40160
|
-
}
|
|
40161
|
-
}
|
|
40162
|
-
}
|
|
40163
|
-
}
|
|
40446
|
+
items: { $ref: '#/components/schemas/FlowInfo' },
|
|
40447
|
+
},
|
|
40448
|
+
},
|
|
40449
|
+
},
|
|
40450
|
+
},
|
|
40451
|
+
},
|
|
40452
|
+
},
|
|
40453
|
+
},
|
|
40164
40454
|
},
|
|
40165
40455
|
post: {
|
|
40166
40456
|
tags: ['Flows'],
|
|
@@ -40180,9 +40470,9 @@ const openApiSpec = {
|
|
|
40180
40470
|
keyword: ['hola', 'hello', 'hi'],
|
|
40181
40471
|
steps: [
|
|
40182
40472
|
{ answer: '👋 ¡Hola! Bienvenido', delay: 500 },
|
|
40183
|
-
{ answer: '¿En qué puedo ayudarte?', capture: true }
|
|
40184
|
-
]
|
|
40185
|
-
}
|
|
40473
|
+
{ answer: '¿En qué puedo ayudarte?', capture: true },
|
|
40474
|
+
],
|
|
40475
|
+
},
|
|
40186
40476
|
},
|
|
40187
40477
|
support: {
|
|
40188
40478
|
summary: 'Support flow',
|
|
@@ -40192,54 +40482,50 @@ const openApiSpec = {
|
|
|
40192
40482
|
keyword: 'ayuda',
|
|
40193
40483
|
steps: [
|
|
40194
40484
|
{ answer: '🆘 Soporte técnico' },
|
|
40195
|
-
{ answer: 'Por favor describe tu problema:', capture: true }
|
|
40196
|
-
]
|
|
40197
|
-
}
|
|
40198
|
-
}
|
|
40199
|
-
}
|
|
40200
|
-
}
|
|
40201
|
-
}
|
|
40485
|
+
{ answer: 'Por favor describe tu problema:', capture: true },
|
|
40486
|
+
],
|
|
40487
|
+
},
|
|
40488
|
+
},
|
|
40489
|
+
},
|
|
40490
|
+
},
|
|
40491
|
+
},
|
|
40202
40492
|
},
|
|
40203
40493
|
responses: {
|
|
40204
40494
|
'201': {
|
|
40205
40495
|
description: 'Flow created successfully',
|
|
40206
40496
|
content: {
|
|
40207
40497
|
'application/json': {
|
|
40208
|
-
schema: { $ref: '#/components/schemas/FlowResponse' }
|
|
40209
|
-
}
|
|
40210
|
-
}
|
|
40498
|
+
schema: { $ref: '#/components/schemas/FlowResponse' },
|
|
40499
|
+
},
|
|
40500
|
+
},
|
|
40211
40501
|
},
|
|
40212
40502
|
'400': { $ref: '#/components/responses/ValidationError' },
|
|
40213
|
-
'409': { $ref: '#/components/responses/Conflict' }
|
|
40214
|
-
}
|
|
40215
|
-
}
|
|
40503
|
+
'409': { $ref: '#/components/responses/Conflict' },
|
|
40504
|
+
},
|
|
40505
|
+
},
|
|
40216
40506
|
},
|
|
40217
40507
|
'/flows/{flowId}': {
|
|
40218
40508
|
get: {
|
|
40219
40509
|
tags: ['Flows'],
|
|
40220
40510
|
summary: 'Get flow by ID',
|
|
40221
|
-
parameters: [
|
|
40222
|
-
{ $ref: '#/components/parameters/flowId' }
|
|
40223
|
-
],
|
|
40511
|
+
parameters: [{ $ref: '#/components/parameters/flowId' }],
|
|
40224
40512
|
responses: {
|
|
40225
40513
|
'200': {
|
|
40226
40514
|
description: 'Flow details',
|
|
40227
40515
|
content: {
|
|
40228
40516
|
'application/json': {
|
|
40229
|
-
schema: { $ref: '#/components/schemas/FlowInfo' }
|
|
40230
|
-
}
|
|
40231
|
-
}
|
|
40517
|
+
schema: { $ref: '#/components/schemas/FlowInfo' },
|
|
40518
|
+
},
|
|
40519
|
+
},
|
|
40232
40520
|
},
|
|
40233
|
-
'404': { $ref: '#/components/responses/NotFound' }
|
|
40234
|
-
}
|
|
40521
|
+
'404': { $ref: '#/components/responses/NotFound' },
|
|
40522
|
+
},
|
|
40235
40523
|
},
|
|
40236
40524
|
put: {
|
|
40237
40525
|
tags: ['Flows'],
|
|
40238
40526
|
summary: 'Update a dynamic flow',
|
|
40239
40527
|
description: 'Update an existing dynamic flow. Programmatic flows cannot be updated.',
|
|
40240
|
-
parameters: [
|
|
40241
|
-
{ $ref: '#/components/parameters/flowId' }
|
|
40242
|
-
],
|
|
40528
|
+
parameters: [{ $ref: '#/components/parameters/flowId' }],
|
|
40243
40529
|
requestBody: {
|
|
40244
40530
|
required: true,
|
|
40245
40531
|
content: {
|
|
@@ -40247,33 +40533,29 @@ const openApiSpec = {
|
|
|
40247
40533
|
schema: { $ref: '#/components/schemas/UpdateFlow' },
|
|
40248
40534
|
example: {
|
|
40249
40535
|
name: 'Updated Flow Name',
|
|
40250
|
-
steps: [
|
|
40251
|
-
|
|
40252
|
-
|
|
40253
|
-
|
|
40254
|
-
}
|
|
40255
|
-
}
|
|
40536
|
+
steps: [{ answer: 'New message', delay: 1000 }],
|
|
40537
|
+
},
|
|
40538
|
+
},
|
|
40539
|
+
},
|
|
40256
40540
|
},
|
|
40257
40541
|
responses: {
|
|
40258
40542
|
'200': {
|
|
40259
40543
|
description: 'Flow updated successfully',
|
|
40260
40544
|
content: {
|
|
40261
40545
|
'application/json': {
|
|
40262
|
-
schema: { $ref: '#/components/schemas/FlowResponse' }
|
|
40263
|
-
}
|
|
40264
|
-
}
|
|
40546
|
+
schema: { $ref: '#/components/schemas/FlowResponse' },
|
|
40547
|
+
},
|
|
40548
|
+
},
|
|
40265
40549
|
},
|
|
40266
40550
|
'400': { $ref: '#/components/responses/ValidationError' },
|
|
40267
|
-
'404': { $ref: '#/components/responses/NotFound' }
|
|
40268
|
-
}
|
|
40551
|
+
'404': { $ref: '#/components/responses/NotFound' },
|
|
40552
|
+
},
|
|
40269
40553
|
},
|
|
40270
40554
|
delete: {
|
|
40271
40555
|
tags: ['Flows'],
|
|
40272
40556
|
summary: 'Delete a dynamic flow',
|
|
40273
40557
|
description: 'Delete a dynamic flow. Programmatic flows cannot be deleted.',
|
|
40274
|
-
parameters: [
|
|
40275
|
-
{ $ref: '#/components/parameters/flowId' }
|
|
40276
|
-
],
|
|
40558
|
+
parameters: [{ $ref: '#/components/parameters/flowId' }],
|
|
40277
40559
|
responses: {
|
|
40278
40560
|
'200': {
|
|
40279
40561
|
description: 'Flow deleted successfully',
|
|
@@ -40283,16 +40565,16 @@ const openApiSpec = {
|
|
|
40283
40565
|
type: 'object',
|
|
40284
40566
|
properties: {
|
|
40285
40567
|
message: { type: 'string' },
|
|
40286
|
-
flowId: { type: 'string' }
|
|
40287
|
-
}
|
|
40288
|
-
}
|
|
40289
|
-
}
|
|
40290
|
-
}
|
|
40568
|
+
flowId: { type: 'string' },
|
|
40569
|
+
},
|
|
40570
|
+
},
|
|
40571
|
+
},
|
|
40572
|
+
},
|
|
40291
40573
|
},
|
|
40292
40574
|
'400': { $ref: '#/components/responses/BadRequest' },
|
|
40293
|
-
'404': { $ref: '#/components/responses/NotFound' }
|
|
40294
|
-
}
|
|
40295
|
-
}
|
|
40575
|
+
'404': { $ref: '#/components/responses/NotFound' },
|
|
40576
|
+
},
|
|
40577
|
+
},
|
|
40296
40578
|
},
|
|
40297
40579
|
'/bots': {
|
|
40298
40580
|
get: {
|
|
@@ -40309,14 +40591,14 @@ const openApiSpec = {
|
|
|
40309
40591
|
count: { type: 'integer' },
|
|
40310
40592
|
bots: {
|
|
40311
40593
|
type: 'array',
|
|
40312
|
-
items: { $ref: '#/components/schemas/BotInfo' }
|
|
40313
|
-
}
|
|
40314
|
-
}
|
|
40315
|
-
}
|
|
40316
|
-
}
|
|
40317
|
-
}
|
|
40318
|
-
}
|
|
40319
|
-
}
|
|
40594
|
+
items: { $ref: '#/components/schemas/BotInfo' },
|
|
40595
|
+
},
|
|
40596
|
+
},
|
|
40597
|
+
},
|
|
40598
|
+
},
|
|
40599
|
+
},
|
|
40600
|
+
},
|
|
40601
|
+
},
|
|
40320
40602
|
},
|
|
40321
40603
|
post: {
|
|
40322
40604
|
tags: ['Bots'],
|
|
@@ -40334,33 +40616,33 @@ const openApiSpec = {
|
|
|
40334
40616
|
tenantId: 'my-bot',
|
|
40335
40617
|
name: 'My WhatsApp Bot',
|
|
40336
40618
|
flowIds: ['greeting', 'support'],
|
|
40337
|
-
port: 3008
|
|
40338
|
-
}
|
|
40619
|
+
port: 3008,
|
|
40620
|
+
},
|
|
40339
40621
|
},
|
|
40340
40622
|
minimalBot: {
|
|
40341
40623
|
summary: 'Minimal bot',
|
|
40342
40624
|
value: {
|
|
40343
40625
|
tenantId: 'bot-2',
|
|
40344
|
-
flowIds: ['greeting']
|
|
40345
|
-
}
|
|
40346
|
-
}
|
|
40347
|
-
}
|
|
40348
|
-
}
|
|
40349
|
-
}
|
|
40626
|
+
flowIds: ['greeting'],
|
|
40627
|
+
},
|
|
40628
|
+
},
|
|
40629
|
+
},
|
|
40630
|
+
},
|
|
40631
|
+
},
|
|
40350
40632
|
},
|
|
40351
40633
|
responses: {
|
|
40352
40634
|
'201': {
|
|
40353
40635
|
description: 'Bot created successfully',
|
|
40354
40636
|
content: {
|
|
40355
40637
|
'application/json': {
|
|
40356
|
-
schema: { $ref: '#/components/schemas/BotResponse' }
|
|
40357
|
-
}
|
|
40358
|
-
}
|
|
40638
|
+
schema: { $ref: '#/components/schemas/BotResponse' },
|
|
40639
|
+
},
|
|
40640
|
+
},
|
|
40359
40641
|
},
|
|
40360
40642
|
'400': { $ref: '#/components/responses/ValidationError' },
|
|
40361
|
-
'409': { $ref: '#/components/responses/Conflict' }
|
|
40362
|
-
}
|
|
40363
|
-
}
|
|
40643
|
+
'409': { $ref: '#/components/responses/Conflict' },
|
|
40644
|
+
},
|
|
40645
|
+
},
|
|
40364
40646
|
},
|
|
40365
40647
|
'/bots/active': {
|
|
40366
40648
|
get: {
|
|
@@ -40377,70 +40659,64 @@ const openApiSpec = {
|
|
|
40377
40659
|
count: { type: 'integer' },
|
|
40378
40660
|
bots: {
|
|
40379
40661
|
type: 'array',
|
|
40380
|
-
items: { $ref: '#/components/schemas/BotInfo' }
|
|
40381
|
-
}
|
|
40382
|
-
}
|
|
40383
|
-
}
|
|
40384
|
-
}
|
|
40385
|
-
}
|
|
40386
|
-
}
|
|
40387
|
-
}
|
|
40388
|
-
}
|
|
40662
|
+
items: { $ref: '#/components/schemas/BotInfo' },
|
|
40663
|
+
},
|
|
40664
|
+
},
|
|
40665
|
+
},
|
|
40666
|
+
},
|
|
40667
|
+
},
|
|
40668
|
+
},
|
|
40669
|
+
},
|
|
40670
|
+
},
|
|
40389
40671
|
},
|
|
40390
40672
|
'/bots/{tenantId}': {
|
|
40391
40673
|
get: {
|
|
40392
40674
|
tags: ['Bots'],
|
|
40393
40675
|
summary: 'Get bot by tenant ID',
|
|
40394
|
-
parameters: [
|
|
40395
|
-
{ $ref: '#/components/parameters/tenantId' }
|
|
40396
|
-
],
|
|
40676
|
+
parameters: [{ $ref: '#/components/parameters/tenantId' }],
|
|
40397
40677
|
responses: {
|
|
40398
40678
|
'200': {
|
|
40399
40679
|
description: 'Bot details',
|
|
40400
40680
|
content: {
|
|
40401
40681
|
'application/json': {
|
|
40402
|
-
schema: { $ref: '#/components/schemas/BotInfo' }
|
|
40403
|
-
}
|
|
40404
|
-
}
|
|
40682
|
+
schema: { $ref: '#/components/schemas/BotInfo' },
|
|
40683
|
+
},
|
|
40684
|
+
},
|
|
40405
40685
|
},
|
|
40406
|
-
'404': { $ref: '#/components/responses/NotFound' }
|
|
40407
|
-
}
|
|
40686
|
+
'404': { $ref: '#/components/responses/NotFound' },
|
|
40687
|
+
},
|
|
40408
40688
|
},
|
|
40409
40689
|
put: {
|
|
40410
40690
|
tags: ['Bots'],
|
|
40411
40691
|
summary: 'Update bot',
|
|
40412
|
-
parameters: [
|
|
40413
|
-
{ $ref: '#/components/parameters/tenantId' }
|
|
40414
|
-
],
|
|
40692
|
+
parameters: [{ $ref: '#/components/parameters/tenantId' }],
|
|
40415
40693
|
requestBody: {
|
|
40416
40694
|
required: true,
|
|
40417
40695
|
content: {
|
|
40418
40696
|
'application/json': {
|
|
40419
40697
|
schema: { $ref: '#/components/schemas/UpdateBot' },
|
|
40420
40698
|
example: {
|
|
40421
|
-
name: 'Updated Bot Name'
|
|
40422
|
-
}
|
|
40423
|
-
}
|
|
40424
|
-
}
|
|
40699
|
+
name: 'Updated Bot Name',
|
|
40700
|
+
},
|
|
40701
|
+
},
|
|
40702
|
+
},
|
|
40425
40703
|
},
|
|
40426
40704
|
responses: {
|
|
40427
40705
|
'200': {
|
|
40428
40706
|
description: 'Bot updated',
|
|
40429
40707
|
content: {
|
|
40430
40708
|
'application/json': {
|
|
40431
|
-
schema: { $ref: '#/components/schemas/BotResponse' }
|
|
40432
|
-
}
|
|
40433
|
-
}
|
|
40709
|
+
schema: { $ref: '#/components/schemas/BotResponse' },
|
|
40710
|
+
},
|
|
40711
|
+
},
|
|
40434
40712
|
},
|
|
40435
|
-
'404': { $ref: '#/components/responses/NotFound' }
|
|
40436
|
-
}
|
|
40713
|
+
'404': { $ref: '#/components/responses/NotFound' },
|
|
40714
|
+
},
|
|
40437
40715
|
},
|
|
40438
40716
|
delete: {
|
|
40439
40717
|
tags: ['Bots'],
|
|
40440
40718
|
summary: 'Delete bot',
|
|
40441
|
-
parameters: [
|
|
40442
|
-
{ $ref: '#/components/parameters/tenantId' }
|
|
40443
|
-
],
|
|
40719
|
+
parameters: [{ $ref: '#/components/parameters/tenantId' }],
|
|
40444
40720
|
responses: {
|
|
40445
40721
|
'200': {
|
|
40446
40722
|
description: 'Bot deleted successfully',
|
|
@@ -40450,24 +40726,22 @@ const openApiSpec = {
|
|
|
40450
40726
|
type: 'object',
|
|
40451
40727
|
properties: {
|
|
40452
40728
|
message: { type: 'string' },
|
|
40453
|
-
tenantId: { type: 'string' }
|
|
40454
|
-
}
|
|
40455
|
-
}
|
|
40456
|
-
}
|
|
40457
|
-
}
|
|
40729
|
+
tenantId: { type: 'string' },
|
|
40730
|
+
},
|
|
40731
|
+
},
|
|
40732
|
+
},
|
|
40733
|
+
},
|
|
40458
40734
|
},
|
|
40459
|
-
'404': { $ref: '#/components/responses/NotFound' }
|
|
40460
|
-
}
|
|
40461
|
-
}
|
|
40735
|
+
'404': { $ref: '#/components/responses/NotFound' },
|
|
40736
|
+
},
|
|
40737
|
+
},
|
|
40462
40738
|
},
|
|
40463
40739
|
'/bots/{tenantId}/qr': {
|
|
40464
40740
|
get: {
|
|
40465
40741
|
tags: ['Bots'],
|
|
40466
40742
|
summary: 'Get QR code for bot',
|
|
40467
40743
|
description: 'Get the QR code to connect WhatsApp. Returns null if already connected.',
|
|
40468
|
-
parameters: [
|
|
40469
|
-
{ $ref: '#/components/parameters/tenantId' }
|
|
40470
|
-
],
|
|
40744
|
+
parameters: [{ $ref: '#/components/parameters/tenantId' }],
|
|
40471
40745
|
responses: {
|
|
40472
40746
|
'200': {
|
|
40473
40747
|
description: 'QR code data',
|
|
@@ -40476,10 +40750,13 @@ const openApiSpec = {
|
|
|
40476
40750
|
schema: {
|
|
40477
40751
|
type: 'object',
|
|
40478
40752
|
properties: {
|
|
40479
|
-
status: {
|
|
40753
|
+
status: {
|
|
40754
|
+
type: 'string',
|
|
40755
|
+
enum: ['initializing', 'connected', 'disconnected', 'error'],
|
|
40756
|
+
},
|
|
40480
40757
|
qr: { type: 'string', nullable: true },
|
|
40481
|
-
message: { type: 'string' }
|
|
40482
|
-
}
|
|
40758
|
+
message: { type: 'string' },
|
|
40759
|
+
},
|
|
40483
40760
|
},
|
|
40484
40761
|
examples: {
|
|
40485
40762
|
pending: {
|
|
@@ -40487,31 +40764,29 @@ const openApiSpec = {
|
|
|
40487
40764
|
value: {
|
|
40488
40765
|
status: 'initializing',
|
|
40489
40766
|
qr: '2@abc123...',
|
|
40490
|
-
message: 'Scan QR to connect'
|
|
40491
|
-
}
|
|
40767
|
+
message: 'Scan QR to connect',
|
|
40768
|
+
},
|
|
40492
40769
|
},
|
|
40493
40770
|
connected: {
|
|
40494
40771
|
summary: 'Already connected',
|
|
40495
40772
|
value: {
|
|
40496
40773
|
status: 'connected',
|
|
40497
|
-
qr: null
|
|
40498
|
-
}
|
|
40499
|
-
}
|
|
40500
|
-
}
|
|
40501
|
-
}
|
|
40502
|
-
}
|
|
40774
|
+
qr: null,
|
|
40775
|
+
},
|
|
40776
|
+
},
|
|
40777
|
+
},
|
|
40778
|
+
},
|
|
40779
|
+
},
|
|
40503
40780
|
},
|
|
40504
|
-
'404': { $ref: '#/components/responses/NotFound' }
|
|
40505
|
-
}
|
|
40506
|
-
}
|
|
40781
|
+
'404': { $ref: '#/components/responses/NotFound' },
|
|
40782
|
+
},
|
|
40783
|
+
},
|
|
40507
40784
|
},
|
|
40508
40785
|
'/bots/{tenantId}/restart': {
|
|
40509
40786
|
post: {
|
|
40510
40787
|
tags: ['Bots'],
|
|
40511
40788
|
summary: 'Restart bot',
|
|
40512
|
-
parameters: [
|
|
40513
|
-
{ $ref: '#/components/parameters/tenantId' }
|
|
40514
|
-
],
|
|
40789
|
+
parameters: [{ $ref: '#/components/parameters/tenantId' }],
|
|
40515
40790
|
requestBody: {
|
|
40516
40791
|
required: true,
|
|
40517
40792
|
content: {
|
|
@@ -40519,10 +40794,10 @@ const openApiSpec = {
|
|
|
40519
40794
|
schema: { $ref: '#/components/schemas/RestartBot' },
|
|
40520
40795
|
example: {
|
|
40521
40796
|
flowIds: ['greeting', 'support'],
|
|
40522
|
-
port: 3009
|
|
40523
|
-
}
|
|
40524
|
-
}
|
|
40525
|
-
}
|
|
40797
|
+
port: 3009,
|
|
40798
|
+
},
|
|
40799
|
+
},
|
|
40800
|
+
},
|
|
40526
40801
|
},
|
|
40527
40802
|
responses: {
|
|
40528
40803
|
'200': {
|
|
@@ -40533,25 +40808,23 @@ const openApiSpec = {
|
|
|
40533
40808
|
type: 'object',
|
|
40534
40809
|
properties: {
|
|
40535
40810
|
message: { type: 'string' },
|
|
40536
|
-
tenantId: { type: 'string' }
|
|
40537
|
-
}
|
|
40538
|
-
}
|
|
40539
|
-
}
|
|
40540
|
-
}
|
|
40811
|
+
tenantId: { type: 'string' },
|
|
40812
|
+
},
|
|
40813
|
+
},
|
|
40814
|
+
},
|
|
40815
|
+
},
|
|
40541
40816
|
},
|
|
40542
40817
|
'400': { $ref: '#/components/responses/ValidationError' },
|
|
40543
|
-
'404': { $ref: '#/components/responses/NotFound' }
|
|
40544
|
-
}
|
|
40545
|
-
}
|
|
40818
|
+
'404': { $ref: '#/components/responses/NotFound' },
|
|
40819
|
+
},
|
|
40820
|
+
},
|
|
40546
40821
|
},
|
|
40547
40822
|
'/bots/{tenantId}/stop': {
|
|
40548
40823
|
post: {
|
|
40549
40824
|
tags: ['Bots'],
|
|
40550
40825
|
summary: 'Stop bot',
|
|
40551
40826
|
description: 'Stop and remove the bot',
|
|
40552
|
-
parameters: [
|
|
40553
|
-
{ $ref: '#/components/parameters/tenantId' }
|
|
40554
|
-
],
|
|
40827
|
+
parameters: [{ $ref: '#/components/parameters/tenantId' }],
|
|
40555
40828
|
responses: {
|
|
40556
40829
|
'200': {
|
|
40557
40830
|
description: 'Bot stopped',
|
|
@@ -40561,16 +40834,16 @@ const openApiSpec = {
|
|
|
40561
40834
|
type: 'object',
|
|
40562
40835
|
properties: {
|
|
40563
40836
|
success: { type: 'boolean' },
|
|
40564
|
-
message: { type: 'string' }
|
|
40565
|
-
}
|
|
40566
|
-
}
|
|
40567
|
-
}
|
|
40568
|
-
}
|
|
40837
|
+
message: { type: 'string' },
|
|
40838
|
+
},
|
|
40839
|
+
},
|
|
40840
|
+
},
|
|
40841
|
+
},
|
|
40569
40842
|
},
|
|
40570
|
-
'404': { $ref: '#/components/responses/NotFound' }
|
|
40571
|
-
}
|
|
40572
|
-
}
|
|
40573
|
-
}
|
|
40843
|
+
'404': { $ref: '#/components/responses/NotFound' },
|
|
40844
|
+
},
|
|
40845
|
+
},
|
|
40846
|
+
},
|
|
40574
40847
|
},
|
|
40575
40848
|
components: {
|
|
40576
40849
|
parameters: {
|
|
@@ -40580,7 +40853,7 @@ const openApiSpec = {
|
|
|
40580
40853
|
required: true,
|
|
40581
40854
|
description: 'Bot tenant identifier',
|
|
40582
40855
|
schema: { type: 'string' },
|
|
40583
|
-
example: 'my-bot'
|
|
40856
|
+
example: 'my-bot',
|
|
40584
40857
|
},
|
|
40585
40858
|
flowId: {
|
|
40586
40859
|
name: 'flowId',
|
|
@@ -40588,8 +40861,8 @@ const openApiSpec = {
|
|
|
40588
40861
|
required: true,
|
|
40589
40862
|
description: 'Flow identifier',
|
|
40590
40863
|
schema: { type: 'string' },
|
|
40591
|
-
example: 'greeting'
|
|
40592
|
-
}
|
|
40864
|
+
example: 'greeting',
|
|
40865
|
+
},
|
|
40593
40866
|
},
|
|
40594
40867
|
schemas: {
|
|
40595
40868
|
FlowStep: {
|
|
@@ -40599,8 +40872,8 @@ const openApiSpec = {
|
|
|
40599
40872
|
answer: { type: 'string', description: 'Message to send', maxLength: 4096 },
|
|
40600
40873
|
delay: { type: 'integer', description: 'Delay in ms before sending', minimum: 0, maximum: 30000 },
|
|
40601
40874
|
media: { type: 'string', format: 'uri', description: 'Media URL to attach' },
|
|
40602
|
-
capture: { type: 'boolean', description: 'Whether to capture user response' }
|
|
40603
|
-
}
|
|
40875
|
+
capture: { type: 'boolean', description: 'Whether to capture user response' },
|
|
40876
|
+
},
|
|
40604
40877
|
},
|
|
40605
40878
|
CreateFlow: {
|
|
40606
40879
|
type: 'object',
|
|
@@ -40609,34 +40882,28 @@ const openApiSpec = {
|
|
|
40609
40882
|
id: { type: 'string', pattern: '^[a-zA-Z0-9-_]+$', maxLength: 50 },
|
|
40610
40883
|
name: { type: 'string', maxLength: 100 },
|
|
40611
40884
|
keyword: {
|
|
40612
|
-
oneOf: [
|
|
40613
|
-
{ type: 'string' },
|
|
40614
|
-
{ type: 'array', items: { type: 'string' }, minItems: 1 }
|
|
40615
|
-
]
|
|
40885
|
+
oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }],
|
|
40616
40886
|
},
|
|
40617
40887
|
steps: {
|
|
40618
40888
|
type: 'array',
|
|
40619
40889
|
items: { $ref: '#/components/schemas/FlowStep' },
|
|
40620
|
-
minItems: 1
|
|
40621
|
-
}
|
|
40622
|
-
}
|
|
40890
|
+
minItems: 1,
|
|
40891
|
+
},
|
|
40892
|
+
},
|
|
40623
40893
|
},
|
|
40624
40894
|
UpdateFlow: {
|
|
40625
40895
|
type: 'object',
|
|
40626
40896
|
properties: {
|
|
40627
40897
|
name: { type: 'string', maxLength: 100 },
|
|
40628
40898
|
keyword: {
|
|
40629
|
-
oneOf: [
|
|
40630
|
-
{ type: 'string' },
|
|
40631
|
-
{ type: 'array', items: { type: 'string' }, minItems: 1 }
|
|
40632
|
-
]
|
|
40899
|
+
oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' }, minItems: 1 }],
|
|
40633
40900
|
},
|
|
40634
40901
|
steps: {
|
|
40635
40902
|
type: 'array',
|
|
40636
40903
|
items: { $ref: '#/components/schemas/FlowStep' },
|
|
40637
|
-
minItems: 1
|
|
40638
|
-
}
|
|
40639
|
-
}
|
|
40904
|
+
minItems: 1,
|
|
40905
|
+
},
|
|
40906
|
+
},
|
|
40640
40907
|
},
|
|
40641
40908
|
FlowInfo: {
|
|
40642
40909
|
type: 'object',
|
|
@@ -40644,8 +40911,8 @@ const openApiSpec = {
|
|
|
40644
40911
|
id: { type: 'string' },
|
|
40645
40912
|
name: { type: 'string' },
|
|
40646
40913
|
dynamic: { type: 'boolean' },
|
|
40647
|
-
config: { $ref: '#/components/schemas/CreateFlow' }
|
|
40648
|
-
}
|
|
40914
|
+
config: { $ref: '#/components/schemas/CreateFlow' },
|
|
40915
|
+
},
|
|
40649
40916
|
},
|
|
40650
40917
|
FlowResponse: {
|
|
40651
40918
|
type: 'object',
|
|
@@ -40654,8 +40921,8 @@ const openApiSpec = {
|
|
|
40654
40921
|
id: { type: 'string' },
|
|
40655
40922
|
name: { type: 'string' },
|
|
40656
40923
|
dynamic: { type: 'boolean' },
|
|
40657
|
-
config: { $ref: '#/components/schemas/CreateFlow' }
|
|
40658
|
-
}
|
|
40924
|
+
config: { $ref: '#/components/schemas/CreateFlow' },
|
|
40925
|
+
},
|
|
40659
40926
|
},
|
|
40660
40927
|
CreateBot: {
|
|
40661
40928
|
type: 'object',
|
|
@@ -40663,16 +40930,21 @@ const openApiSpec = {
|
|
|
40663
40930
|
properties: {
|
|
40664
40931
|
tenantId: { type: 'string', pattern: '^[a-zA-Z0-9-_]+$', maxLength: 50 },
|
|
40665
40932
|
name: { type: 'string', maxLength: 100 },
|
|
40666
|
-
flowIds: {
|
|
40933
|
+
flowIds: {
|
|
40934
|
+
type: 'array',
|
|
40935
|
+
items: { type: 'string' },
|
|
40936
|
+
minItems: 1,
|
|
40937
|
+
description: 'At least one flowId is required',
|
|
40938
|
+
},
|
|
40667
40939
|
port: { type: 'integer', minimum: 1024, maximum: 65535 },
|
|
40668
|
-
providerOptions: { type: 'object', additionalProperties: true }
|
|
40669
|
-
}
|
|
40940
|
+
providerOptions: { type: 'object', additionalProperties: true },
|
|
40941
|
+
},
|
|
40670
40942
|
},
|
|
40671
40943
|
UpdateBot: {
|
|
40672
40944
|
type: 'object',
|
|
40673
40945
|
properties: {
|
|
40674
|
-
name: { type: 'string', maxLength: 100 }
|
|
40675
|
-
}
|
|
40946
|
+
name: { type: 'string', maxLength: 100 },
|
|
40947
|
+
},
|
|
40676
40948
|
},
|
|
40677
40949
|
RestartBot: {
|
|
40678
40950
|
type: 'object',
|
|
@@ -40680,8 +40952,8 @@ const openApiSpec = {
|
|
|
40680
40952
|
properties: {
|
|
40681
40953
|
flowIds: { type: 'array', items: { type: 'string' }, minItems: 1 },
|
|
40682
40954
|
port: { type: 'integer', minimum: 1024, maximum: 65535 },
|
|
40683
|
-
name: { type: 'string', maxLength: 100 }
|
|
40684
|
-
}
|
|
40955
|
+
name: { type: 'string', maxLength: 100 },
|
|
40956
|
+
},
|
|
40685
40957
|
},
|
|
40686
40958
|
BotInfo: {
|
|
40687
40959
|
type: 'object',
|
|
@@ -40691,8 +40963,8 @@ const openApiSpec = {
|
|
|
40691
40963
|
status: { type: 'string', enum: ['initializing', 'connected', 'disconnected', 'error'] },
|
|
40692
40964
|
port: { type: 'integer' },
|
|
40693
40965
|
createdAt: { type: 'string', format: 'date-time' },
|
|
40694
|
-
uptime: { type: 'integer', description: 'Uptime in milliseconds' }
|
|
40695
|
-
}
|
|
40966
|
+
uptime: { type: 'integer', description: 'Uptime in milliseconds' },
|
|
40967
|
+
},
|
|
40696
40968
|
},
|
|
40697
40969
|
BotResponse: {
|
|
40698
40970
|
type: 'object',
|
|
@@ -40701,8 +40973,8 @@ const openApiSpec = {
|
|
|
40701
40973
|
tenantId: { type: 'string' },
|
|
40702
40974
|
name: { type: 'string' },
|
|
40703
40975
|
status: { type: 'string' },
|
|
40704
|
-
port: { type: 'integer' }
|
|
40705
|
-
}
|
|
40976
|
+
port: { type: 'integer' },
|
|
40977
|
+
},
|
|
40706
40978
|
},
|
|
40707
40979
|
Error: {
|
|
40708
40980
|
type: 'object',
|
|
@@ -40714,12 +40986,12 @@ const openApiSpec = {
|
|
|
40714
40986
|
type: 'object',
|
|
40715
40987
|
properties: {
|
|
40716
40988
|
field: { type: 'string' },
|
|
40717
|
-
message: { type: 'string' }
|
|
40718
|
-
}
|
|
40719
|
-
}
|
|
40720
|
-
}
|
|
40721
|
-
}
|
|
40722
|
-
}
|
|
40989
|
+
message: { type: 'string' },
|
|
40990
|
+
},
|
|
40991
|
+
},
|
|
40992
|
+
},
|
|
40993
|
+
},
|
|
40994
|
+
},
|
|
40723
40995
|
},
|
|
40724
40996
|
responses: {
|
|
40725
40997
|
NotFound: {
|
|
@@ -40727,9 +40999,9 @@ const openApiSpec = {
|
|
|
40727
40999
|
content: {
|
|
40728
41000
|
'application/json': {
|
|
40729
41001
|
schema: { $ref: '#/components/schemas/Error' },
|
|
40730
|
-
example: { error: 'Bot not found' }
|
|
40731
|
-
}
|
|
40732
|
-
}
|
|
41002
|
+
example: { error: 'Bot not found' },
|
|
41003
|
+
},
|
|
41004
|
+
},
|
|
40733
41005
|
},
|
|
40734
41006
|
ValidationError: {
|
|
40735
41007
|
description: 'Validation error',
|
|
@@ -40738,57 +41010,52 @@ const openApiSpec = {
|
|
|
40738
41010
|
schema: { $ref: '#/components/schemas/Error' },
|
|
40739
41011
|
example: {
|
|
40740
41012
|
error: 'Validation failed',
|
|
40741
|
-
details: [
|
|
40742
|
-
|
|
40743
|
-
|
|
40744
|
-
|
|
40745
|
-
}
|
|
40746
|
-
}
|
|
41013
|
+
details: [{ field: 'tenantId', message: 'tenantId is required' }],
|
|
41014
|
+
},
|
|
41015
|
+
},
|
|
41016
|
+
},
|
|
40747
41017
|
},
|
|
40748
41018
|
BadRequest: {
|
|
40749
41019
|
description: 'Bad request',
|
|
40750
41020
|
content: {
|
|
40751
41021
|
'application/json': {
|
|
40752
41022
|
schema: { $ref: '#/components/schemas/Error' },
|
|
40753
|
-
example: { error: 'Cannot delete programmatic flows' }
|
|
40754
|
-
}
|
|
40755
|
-
}
|
|
41023
|
+
example: { error: 'Cannot delete programmatic flows' },
|
|
41024
|
+
},
|
|
41025
|
+
},
|
|
40756
41026
|
},
|
|
40757
41027
|
Conflict: {
|
|
40758
41028
|
description: 'Resource already exists',
|
|
40759
41029
|
content: {
|
|
40760
41030
|
'application/json': {
|
|
40761
41031
|
schema: { $ref: '#/components/schemas/Error' },
|
|
40762
|
-
example: { error: 'Bot with this tenantId already exists' }
|
|
40763
|
-
}
|
|
40764
|
-
}
|
|
41032
|
+
example: { error: 'Bot with this tenantId already exists' },
|
|
41033
|
+
},
|
|
41034
|
+
},
|
|
40765
41035
|
},
|
|
40766
41036
|
Unauthorized: {
|
|
40767
41037
|
description: 'Unauthorized',
|
|
40768
41038
|
content: {
|
|
40769
41039
|
'application/json': {
|
|
40770
41040
|
schema: { $ref: '#/components/schemas/Error' },
|
|
40771
|
-
example: { error: 'Unauthorized' }
|
|
40772
|
-
}
|
|
40773
|
-
}
|
|
40774
|
-
}
|
|
41041
|
+
example: { error: 'Unauthorized' },
|
|
41042
|
+
},
|
|
41043
|
+
},
|
|
41044
|
+
},
|
|
40775
41045
|
},
|
|
40776
41046
|
securitySchemes: {
|
|
40777
41047
|
ApiKeyAuth: {
|
|
40778
41048
|
type: 'apiKey',
|
|
40779
41049
|
in: 'header',
|
|
40780
|
-
name: 'X-API-Key'
|
|
41050
|
+
name: 'X-API-Key',
|
|
40781
41051
|
},
|
|
40782
41052
|
BearerAuth: {
|
|
40783
41053
|
type: 'http',
|
|
40784
|
-
scheme: 'bearer'
|
|
40785
|
-
}
|
|
40786
|
-
}
|
|
41054
|
+
scheme: 'bearer',
|
|
41055
|
+
},
|
|
41056
|
+
},
|
|
40787
41057
|
},
|
|
40788
|
-
security: [
|
|
40789
|
-
{ ApiKeyAuth: [] },
|
|
40790
|
-
{ BearerAuth: [] }
|
|
40791
|
-
]
|
|
41058
|
+
security: [{ ApiKeyAuth: [] }, { BearerAuth: [] }],
|
|
40792
41059
|
};
|
|
40793
41060
|
/**
|
|
40794
41061
|
* Generate Swagger UI HTML
|
|
@@ -40832,324 +41099,6 @@ function generateSwaggerHtml(specUrl) {
|
|
|
40832
41099
|
</html>`;
|
|
40833
41100
|
}
|
|
40834
41101
|
|
|
40835
|
-
/**
|
|
40836
|
-
* FlowRegistry manages flow definitions that can be used when creating bots
|
|
40837
|
-
*/
|
|
40838
|
-
class FlowRegistry {
|
|
40839
|
-
constructor() {
|
|
40840
|
-
this.flows = new Map();
|
|
40841
|
-
}
|
|
40842
|
-
/**
|
|
40843
|
-
* Register a programmatic flow (created with addKeyword)
|
|
40844
|
-
*/
|
|
40845
|
-
register(id, name, flow) {
|
|
40846
|
-
const definition = {
|
|
40847
|
-
id,
|
|
40848
|
-
name,
|
|
40849
|
-
flow,
|
|
40850
|
-
dynamic: false,
|
|
40851
|
-
createdAt: new Date(),
|
|
40852
|
-
updatedAt: new Date()
|
|
40853
|
-
};
|
|
40854
|
-
this.flows.set(id, definition);
|
|
40855
|
-
return definition;
|
|
40856
|
-
}
|
|
40857
|
-
/**
|
|
40858
|
-
* Register a dynamic flow from JSON configuration
|
|
40859
|
-
*/
|
|
40860
|
-
registerDynamic(config) {
|
|
40861
|
-
const { id, name, keyword, steps } = config;
|
|
40862
|
-
const flow = this.buildFlowFromSteps(keyword, steps);
|
|
40863
|
-
const definition = {
|
|
40864
|
-
id,
|
|
40865
|
-
name,
|
|
40866
|
-
flow,
|
|
40867
|
-
dynamic: true,
|
|
40868
|
-
config,
|
|
40869
|
-
createdAt: new Date(),
|
|
40870
|
-
updatedAt: new Date()
|
|
40871
|
-
};
|
|
40872
|
-
this.flows.set(id, definition);
|
|
40873
|
-
return definition;
|
|
40874
|
-
}
|
|
40875
|
-
/**
|
|
40876
|
-
* Update a dynamic flow
|
|
40877
|
-
*/
|
|
40878
|
-
update(id, updates) {
|
|
40879
|
-
const existing = this.flows.get(id);
|
|
40880
|
-
if (!existing || !existing.dynamic || !existing.config) {
|
|
40881
|
-
return null;
|
|
40882
|
-
}
|
|
40883
|
-
// Merge updates with existing config
|
|
40884
|
-
const newConfig = {
|
|
40885
|
-
...existing.config,
|
|
40886
|
-
...(updates.name && { name: updates.name }),
|
|
40887
|
-
...(updates.keyword && { keyword: updates.keyword }),
|
|
40888
|
-
...(updates.steps && { steps: updates.steps })
|
|
40889
|
-
};
|
|
40890
|
-
// Rebuild flow
|
|
40891
|
-
const flow = this.buildFlowFromSteps(newConfig.keyword, newConfig.steps);
|
|
40892
|
-
const definition = {
|
|
40893
|
-
id,
|
|
40894
|
-
name: newConfig.name,
|
|
40895
|
-
flow,
|
|
40896
|
-
dynamic: true,
|
|
40897
|
-
config: newConfig,
|
|
40898
|
-
createdAt: existing.createdAt,
|
|
40899
|
-
updatedAt: new Date()
|
|
40900
|
-
};
|
|
40901
|
-
this.flows.set(id, definition);
|
|
40902
|
-
return definition;
|
|
40903
|
-
}
|
|
40904
|
-
/**
|
|
40905
|
-
* Remove a flow from registry
|
|
40906
|
-
*/
|
|
40907
|
-
remove(id) {
|
|
40908
|
-
return this.flows.delete(id);
|
|
40909
|
-
}
|
|
40910
|
-
/**
|
|
40911
|
-
* Get a flow by ID
|
|
40912
|
-
*/
|
|
40913
|
-
get(id) {
|
|
40914
|
-
return this.flows.get(id);
|
|
40915
|
-
}
|
|
40916
|
-
/**
|
|
40917
|
-
* Get all registered flows
|
|
40918
|
-
*/
|
|
40919
|
-
getAll() {
|
|
40920
|
-
return Array.from(this.flows.values());
|
|
40921
|
-
}
|
|
40922
|
-
/**
|
|
40923
|
-
* Check if a flow exists
|
|
40924
|
-
*/
|
|
40925
|
-
has(id) {
|
|
40926
|
-
return this.flows.has(id);
|
|
40927
|
-
}
|
|
40928
|
-
/**
|
|
40929
|
-
* Get all flow IDs
|
|
40930
|
-
*/
|
|
40931
|
-
getIds() {
|
|
40932
|
-
return Array.from(this.flows.keys());
|
|
40933
|
-
}
|
|
40934
|
-
/**
|
|
40935
|
-
* Get count of registered flows
|
|
40936
|
-
*/
|
|
40937
|
-
count() {
|
|
40938
|
-
return this.flows.size;
|
|
40939
|
-
}
|
|
40940
|
-
/**
|
|
40941
|
-
* Clear all flows
|
|
40942
|
-
*/
|
|
40943
|
-
clear() {
|
|
40944
|
-
this.flows.clear();
|
|
40945
|
-
}
|
|
40946
|
-
/**
|
|
40947
|
-
* Get flows by type (dynamic or programmatic)
|
|
40948
|
-
*/
|
|
40949
|
-
getByType(dynamic) {
|
|
40950
|
-
return this.getAll().filter(f => f.dynamic === dynamic);
|
|
40951
|
-
}
|
|
40952
|
-
/**
|
|
40953
|
-
* Resolve multiple flow IDs to Flow objects
|
|
40954
|
-
*/
|
|
40955
|
-
resolveFlows(flowIds) {
|
|
40956
|
-
const flows = [];
|
|
40957
|
-
const missing = [];
|
|
40958
|
-
for (const id of flowIds) {
|
|
40959
|
-
const definition = this.flows.get(id);
|
|
40960
|
-
if (definition) {
|
|
40961
|
-
flows.push(definition.flow);
|
|
40962
|
-
}
|
|
40963
|
-
else {
|
|
40964
|
-
missing.push(id);
|
|
40965
|
-
}
|
|
40966
|
-
}
|
|
40967
|
-
return { flows, missing };
|
|
40968
|
-
}
|
|
40969
|
-
/**
|
|
40970
|
-
* Build a flow from steps configuration
|
|
40971
|
-
*/
|
|
40972
|
-
buildFlowFromSteps(keyword, steps) {
|
|
40973
|
-
const keywords = Array.isArray(keyword)
|
|
40974
|
-
? keyword
|
|
40975
|
-
: keyword;
|
|
40976
|
-
let flow = addKeyword_1(keywords);
|
|
40977
|
-
for (const step of steps) {
|
|
40978
|
-
const options = {};
|
|
40979
|
-
if (step.delay)
|
|
40980
|
-
options.delay = step.delay;
|
|
40981
|
-
if (step.media)
|
|
40982
|
-
options.media = step.media;
|
|
40983
|
-
if (step.capture)
|
|
40984
|
-
options.capture = step.capture;
|
|
40985
|
-
flow = flow.addAnswer(step.answer, Object.keys(options).length > 0 ? options : undefined);
|
|
40986
|
-
}
|
|
40987
|
-
return flow;
|
|
40988
|
-
}
|
|
40989
|
-
/**
|
|
40990
|
-
* Export all dynamic flows as serializable configs
|
|
40991
|
-
*/
|
|
40992
|
-
exportDynamicFlows() {
|
|
40993
|
-
return this.getByType(true)
|
|
40994
|
-
.filter(f => f.config)
|
|
40995
|
-
.map(f => f.config);
|
|
40996
|
-
}
|
|
40997
|
-
/**
|
|
40998
|
-
* Import dynamic flows from configs
|
|
40999
|
-
*/
|
|
41000
|
-
importDynamicFlows(configs) {
|
|
41001
|
-
const failed = [];
|
|
41002
|
-
let imported = 0;
|
|
41003
|
-
for (const config of configs) {
|
|
41004
|
-
try {
|
|
41005
|
-
if (!this.has(config.id)) {
|
|
41006
|
-
this.registerDynamic(config);
|
|
41007
|
-
imported++;
|
|
41008
|
-
}
|
|
41009
|
-
}
|
|
41010
|
-
catch {
|
|
41011
|
-
failed.push(config.id);
|
|
41012
|
-
}
|
|
41013
|
-
}
|
|
41014
|
-
return { imported, failed };
|
|
41015
|
-
}
|
|
41016
|
-
}
|
|
41017
|
-
|
|
41018
|
-
/**
|
|
41019
|
-
* Simple in-memory rate limiter
|
|
41020
|
-
*/
|
|
41021
|
-
class RateLimiter {
|
|
41022
|
-
constructor(config = {}) {
|
|
41023
|
-
this.store = new Map();
|
|
41024
|
-
this.cleanupInterval = null;
|
|
41025
|
-
this.config = {
|
|
41026
|
-
maxRequests: config.maxRequests ?? 100,
|
|
41027
|
-
windowMs: config.windowMs ?? 60000, // 1 minute
|
|
41028
|
-
message: config.message ?? 'Too many requests, please try again later',
|
|
41029
|
-
skipPaths: config.skipPaths ?? ['/docs', '/api/health'],
|
|
41030
|
-
keyExtractor: config.keyExtractor ?? this.defaultKeyExtractor
|
|
41031
|
-
};
|
|
41032
|
-
// Cleanup expired entries every minute
|
|
41033
|
-
this.cleanupInterval = setInterval(() => this.cleanup(), 60000);
|
|
41034
|
-
}
|
|
41035
|
-
/**
|
|
41036
|
-
* Default key extractor - uses IP address
|
|
41037
|
-
*/
|
|
41038
|
-
defaultKeyExtractor(req) {
|
|
41039
|
-
const forwarded = req.headers['x-forwarded-for'];
|
|
41040
|
-
if (forwarded) {
|
|
41041
|
-
const ips = Array.isArray(forwarded) ? forwarded[0] : forwarded.split(',')[0];
|
|
41042
|
-
return ips.trim();
|
|
41043
|
-
}
|
|
41044
|
-
return req.socket?.remoteAddress || 'unknown';
|
|
41045
|
-
}
|
|
41046
|
-
/**
|
|
41047
|
-
* Check if request should be rate limited
|
|
41048
|
-
*/
|
|
41049
|
-
isRateLimited(req) {
|
|
41050
|
-
const url = req.url || '/';
|
|
41051
|
-
// Skip certain paths
|
|
41052
|
-
if (this.config.skipPaths.some(path => url.startsWith(path))) {
|
|
41053
|
-
return { limited: false, remaining: this.config.maxRequests, resetTime: 0 };
|
|
41054
|
-
}
|
|
41055
|
-
const key = this.config.keyExtractor(req);
|
|
41056
|
-
const now = Date.now();
|
|
41057
|
-
let entry = this.store.get(key);
|
|
41058
|
-
// If no entry or window expired, create new entry
|
|
41059
|
-
if (!entry || now > entry.resetTime) {
|
|
41060
|
-
entry = {
|
|
41061
|
-
count: 1,
|
|
41062
|
-
resetTime: now + this.config.windowMs
|
|
41063
|
-
};
|
|
41064
|
-
this.store.set(key, entry);
|
|
41065
|
-
return {
|
|
41066
|
-
limited: false,
|
|
41067
|
-
remaining: this.config.maxRequests - 1,
|
|
41068
|
-
resetTime: entry.resetTime
|
|
41069
|
-
};
|
|
41070
|
-
}
|
|
41071
|
-
// Increment count
|
|
41072
|
-
entry.count++;
|
|
41073
|
-
// Check if over limit
|
|
41074
|
-
if (entry.count > this.config.maxRequests) {
|
|
41075
|
-
return {
|
|
41076
|
-
limited: true,
|
|
41077
|
-
remaining: 0,
|
|
41078
|
-
resetTime: entry.resetTime
|
|
41079
|
-
};
|
|
41080
|
-
}
|
|
41081
|
-
return {
|
|
41082
|
-
limited: false,
|
|
41083
|
-
remaining: this.config.maxRequests - entry.count,
|
|
41084
|
-
resetTime: entry.resetTime
|
|
41085
|
-
};
|
|
41086
|
-
}
|
|
41087
|
-
/**
|
|
41088
|
-
* Create middleware function
|
|
41089
|
-
*/
|
|
41090
|
-
middleware() {
|
|
41091
|
-
return (req, res, next) => {
|
|
41092
|
-
const result = this.isRateLimited(req);
|
|
41093
|
-
// Set rate limit headers
|
|
41094
|
-
res.setHeader('X-RateLimit-Limit', this.config.maxRequests.toString());
|
|
41095
|
-
res.setHeader('X-RateLimit-Remaining', result.remaining.toString());
|
|
41096
|
-
res.setHeader('X-RateLimit-Reset', result.resetTime.toString());
|
|
41097
|
-
if (result.limited) {
|
|
41098
|
-
res.setHeader('Retry-After', Math.ceil((result.resetTime - Date.now()) / 1000).toString());
|
|
41099
|
-
res.writeHead(429, { 'Content-Type': 'application/json' });
|
|
41100
|
-
res.end(JSON.stringify({
|
|
41101
|
-
error: this.config.message,
|
|
41102
|
-
retryAfter: Math.ceil((result.resetTime - Date.now()) / 1000)
|
|
41103
|
-
}));
|
|
41104
|
-
return;
|
|
41105
|
-
}
|
|
41106
|
-
next();
|
|
41107
|
-
};
|
|
41108
|
-
}
|
|
41109
|
-
/**
|
|
41110
|
-
* Reset rate limit for a specific key
|
|
41111
|
-
*/
|
|
41112
|
-
reset(key) {
|
|
41113
|
-
this.store.delete(key);
|
|
41114
|
-
}
|
|
41115
|
-
/**
|
|
41116
|
-
* Clear all rate limit data
|
|
41117
|
-
*/
|
|
41118
|
-
clear() {
|
|
41119
|
-
this.store.clear();
|
|
41120
|
-
}
|
|
41121
|
-
/**
|
|
41122
|
-
* Cleanup expired entries
|
|
41123
|
-
*/
|
|
41124
|
-
cleanup() {
|
|
41125
|
-
const now = Date.now();
|
|
41126
|
-
for (const [key, entry] of this.store.entries()) {
|
|
41127
|
-
if (now > entry.resetTime) {
|
|
41128
|
-
this.store.delete(key);
|
|
41129
|
-
}
|
|
41130
|
-
}
|
|
41131
|
-
}
|
|
41132
|
-
/**
|
|
41133
|
-
* Stop the cleanup interval
|
|
41134
|
-
*/
|
|
41135
|
-
destroy() {
|
|
41136
|
-
if (this.cleanupInterval) {
|
|
41137
|
-
clearInterval(this.cleanupInterval);
|
|
41138
|
-
this.cleanupInterval = null;
|
|
41139
|
-
}
|
|
41140
|
-
this.store.clear();
|
|
41141
|
-
}
|
|
41142
|
-
/**
|
|
41143
|
-
* Get current stats
|
|
41144
|
-
*/
|
|
41145
|
-
getStats() {
|
|
41146
|
-
return {
|
|
41147
|
-
activeKeys: this.store.size,
|
|
41148
|
-
config: this.config
|
|
41149
|
-
};
|
|
41150
|
-
}
|
|
41151
|
-
}
|
|
41152
|
-
|
|
41153
41102
|
/**
|
|
41154
41103
|
* REST API for managing bots via HTTP endpoints using Polka
|
|
41155
41104
|
*/
|
|
@@ -41271,15 +41220,12 @@ class BotManagerApi {
|
|
|
41271
41220
|
* Start the API server
|
|
41272
41221
|
*/
|
|
41273
41222
|
start() {
|
|
41274
|
-
this.app = polka()
|
|
41275
|
-
.use(this.cors());
|
|
41223
|
+
this.app = polka().use(this.cors());
|
|
41276
41224
|
// Add rate limiter if enabled
|
|
41277
41225
|
if (this.rateLimiter) {
|
|
41278
41226
|
this.app.use(this.rateLimiter.middleware());
|
|
41279
41227
|
}
|
|
41280
|
-
this.app
|
|
41281
|
-
.use(this.jsonParser())
|
|
41282
|
-
.use(this.auth());
|
|
41228
|
+
this.app.use(this.jsonParser()).use(this.auth());
|
|
41283
41229
|
// Swagger UI - no auth required
|
|
41284
41230
|
this.app.get('/docs', (req, res) => {
|
|
41285
41231
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
@@ -41296,23 +41242,23 @@ class BotManagerApi {
|
|
|
41296
41242
|
...health,
|
|
41297
41243
|
timestamp: new Date().toISOString(),
|
|
41298
41244
|
flows: this.flowRegistry.count(),
|
|
41299
|
-
rateLimiter: this.rateLimiter?.getStats() || null
|
|
41245
|
+
rateLimiter: this.rateLimiter?.getStats() || null,
|
|
41300
41246
|
});
|
|
41301
41247
|
});
|
|
41302
41248
|
// ============ FLOWS ============
|
|
41303
41249
|
// Get all flows
|
|
41304
41250
|
this.app.get('/api/flows', (req, res) => {
|
|
41305
|
-
const flows = this.flowRegistry.getAll().map(f => ({
|
|
41251
|
+
const flows = this.flowRegistry.getAll().map((f) => ({
|
|
41306
41252
|
id: f.id,
|
|
41307
41253
|
name: f.name,
|
|
41308
41254
|
dynamic: f.dynamic,
|
|
41309
41255
|
config: f.dynamic ? f.config : undefined,
|
|
41310
41256
|
createdAt: f.createdAt,
|
|
41311
|
-
updatedAt: f.updatedAt
|
|
41257
|
+
updatedAt: f.updatedAt,
|
|
41312
41258
|
}));
|
|
41313
41259
|
this.sendJson(res, 200, {
|
|
41314
41260
|
count: flows.length,
|
|
41315
|
-
flows
|
|
41261
|
+
flows,
|
|
41316
41262
|
});
|
|
41317
41263
|
});
|
|
41318
41264
|
// Create a new dynamic flow
|
|
@@ -41332,7 +41278,7 @@ class BotManagerApi {
|
|
|
41332
41278
|
dynamic: flow.dynamic,
|
|
41333
41279
|
config: flow.dynamic ? flow.config : undefined,
|
|
41334
41280
|
createdAt: flow.createdAt,
|
|
41335
|
-
updatedAt: flow.updatedAt
|
|
41281
|
+
updatedAt: flow.updatedAt,
|
|
41336
41282
|
});
|
|
41337
41283
|
});
|
|
41338
41284
|
// Update a dynamic flow
|
|
@@ -41348,7 +41294,7 @@ class BotManagerApi {
|
|
|
41348
41294
|
}
|
|
41349
41295
|
if (!flow.dynamic) {
|
|
41350
41296
|
return this.sendJson(res, 400, {
|
|
41351
|
-
error: 'Cannot delete programmatic flows. Only dynamic flows can be deleted.'
|
|
41297
|
+
error: 'Cannot delete programmatic flows. Only dynamic flows can be deleted.',
|
|
41352
41298
|
});
|
|
41353
41299
|
}
|
|
41354
41300
|
this.flowRegistry.remove(flowId);
|
|
@@ -41359,7 +41305,7 @@ class BotManagerApi {
|
|
|
41359
41305
|
this.app.get('/api/bots', (req, res) => {
|
|
41360
41306
|
this.sendJson(res, 200, {
|
|
41361
41307
|
count: this.manager.getBotCount(),
|
|
41362
|
-
bots: this.manager.getBotsInfo()
|
|
41308
|
+
bots: this.manager.getBotsInfo(),
|
|
41363
41309
|
});
|
|
41364
41310
|
});
|
|
41365
41311
|
// Create bot
|
|
@@ -41382,7 +41328,7 @@ class BotManagerApi {
|
|
|
41382
41328
|
uptime: Date.now() - bot.createdAt.getTime(),
|
|
41383
41329
|
providerType: bot.providerType,
|
|
41384
41330
|
databaseType: bot.databaseType,
|
|
41385
|
-
reconnectState: this.manager.getReconnectState(tenantId)
|
|
41331
|
+
reconnectState: this.manager.getReconnectState(tenantId),
|
|
41386
41332
|
});
|
|
41387
41333
|
});
|
|
41388
41334
|
// Update bot
|
|
@@ -41413,7 +41359,7 @@ class BotManagerApi {
|
|
|
41413
41359
|
this.sendJson(res, 200, {
|
|
41414
41360
|
status: bot.status,
|
|
41415
41361
|
qr: qr || null,
|
|
41416
|
-
message: qr ? 'Scan QR to connect' : 'QR not available yet'
|
|
41362
|
+
message: qr ? 'Scan QR to connect' : 'QR not available yet',
|
|
41417
41363
|
});
|
|
41418
41364
|
});
|
|
41419
41365
|
// Restart bot
|
|
@@ -41426,7 +41372,7 @@ class BotManagerApi {
|
|
|
41426
41372
|
const success = await this.manager.reconnectBot(tenantId);
|
|
41427
41373
|
this.sendJson(res, success ? 200 : 404, {
|
|
41428
41374
|
success,
|
|
41429
|
-
message: success ? 'Bot reconnection initiated' : 'Bot not found or no stored config'
|
|
41375
|
+
message: success ? 'Bot reconnection initiated' : 'Bot not found or no stored config',
|
|
41430
41376
|
});
|
|
41431
41377
|
});
|
|
41432
41378
|
// Stop bot
|
|
@@ -41435,7 +41381,7 @@ class BotManagerApi {
|
|
|
41435
41381
|
const removed = await this.manager.removeBot(tenantId);
|
|
41436
41382
|
this.sendJson(res, removed ? 200 : 404, {
|
|
41437
41383
|
success: removed,
|
|
41438
|
-
message: removed ? 'Bot stopped' : 'Bot not found'
|
|
41384
|
+
message: removed ? 'Bot stopped' : 'Bot not found',
|
|
41439
41385
|
});
|
|
41440
41386
|
});
|
|
41441
41387
|
// Start server
|
|
@@ -41465,7 +41411,7 @@ class BotManagerApi {
|
|
|
41465
41411
|
if (!validation.success || !validation.data) {
|
|
41466
41412
|
return this.sendJson(res, 400, {
|
|
41467
41413
|
error: 'Validation failed',
|
|
41468
|
-
details: validation.errors
|
|
41414
|
+
details: validation.errors,
|
|
41469
41415
|
});
|
|
41470
41416
|
}
|
|
41471
41417
|
const { tenantId, name, flowIds, port, providerOptions } = validation.data;
|
|
@@ -41474,7 +41420,7 @@ class BotManagerApi {
|
|
|
41474
41420
|
if (missing.length > 0) {
|
|
41475
41421
|
return this.sendJson(res, 400, {
|
|
41476
41422
|
error: `Flows not found: ${missing.join(', ')}`,
|
|
41477
|
-
availableFlows: this.flowRegistry.getIds()
|
|
41423
|
+
availableFlows: this.flowRegistry.getIds(),
|
|
41478
41424
|
});
|
|
41479
41425
|
}
|
|
41480
41426
|
if (this.manager.hasBot(tenantId)) {
|
|
@@ -41486,7 +41432,7 @@ class BotManagerApi {
|
|
|
41486
41432
|
name,
|
|
41487
41433
|
flows,
|
|
41488
41434
|
port,
|
|
41489
|
-
providerOptions
|
|
41435
|
+
providerOptions,
|
|
41490
41436
|
});
|
|
41491
41437
|
this.sendJson(res, 201, {
|
|
41492
41438
|
message: 'Bot created successfully',
|
|
@@ -41496,7 +41442,7 @@ class BotManagerApi {
|
|
|
41496
41442
|
port: bot.port,
|
|
41497
41443
|
flowsUsed: flowIds,
|
|
41498
41444
|
providerType: bot.providerType,
|
|
41499
|
-
databaseType: bot.databaseType
|
|
41445
|
+
databaseType: bot.databaseType,
|
|
41500
41446
|
});
|
|
41501
41447
|
}
|
|
41502
41448
|
catch (error) {
|
|
@@ -41509,7 +41455,7 @@ class BotManagerApi {
|
|
|
41509
41455
|
if (!validation.success) {
|
|
41510
41456
|
return this.sendJson(res, 400, {
|
|
41511
41457
|
error: 'Validation failed',
|
|
41512
|
-
details: validation.errors
|
|
41458
|
+
details: validation.errors,
|
|
41513
41459
|
});
|
|
41514
41460
|
}
|
|
41515
41461
|
const bot = this.manager.getBot(tenantId);
|
|
@@ -41522,7 +41468,7 @@ class BotManagerApi {
|
|
|
41522
41468
|
this.sendJson(res, 200, {
|
|
41523
41469
|
message: 'Bot updated',
|
|
41524
41470
|
tenantId: bot.tenantId,
|
|
41525
|
-
name: bot.name
|
|
41471
|
+
name: bot.name,
|
|
41526
41472
|
});
|
|
41527
41473
|
}
|
|
41528
41474
|
async handleRestartBot(req, res) {
|
|
@@ -41531,16 +41477,14 @@ class BotManagerApi {
|
|
|
41531
41477
|
if (!validation.success || !validation.data) {
|
|
41532
41478
|
return this.sendJson(res, 400, {
|
|
41533
41479
|
error: 'Validation failed',
|
|
41534
|
-
details: validation.errors
|
|
41480
|
+
details: validation.errors,
|
|
41535
41481
|
});
|
|
41536
41482
|
}
|
|
41537
41483
|
const { flowIds, port, name } = validation.data;
|
|
41538
41484
|
const { flows, missing } = this.flowRegistry.resolveFlows(flowIds);
|
|
41539
41485
|
if (flows.length === 0) {
|
|
41540
41486
|
return this.sendJson(res, 400, {
|
|
41541
|
-
error: missing.length > 0
|
|
41542
|
-
? `Flows not found: ${missing.join(', ')}`
|
|
41543
|
-
: 'No valid flows found in registry'
|
|
41487
|
+
error: missing.length > 0 ? `Flows not found: ${missing.join(', ')}` : 'No valid flows found in registry',
|
|
41544
41488
|
});
|
|
41545
41489
|
}
|
|
41546
41490
|
try {
|
|
@@ -41551,7 +41495,7 @@ class BotManagerApi {
|
|
|
41551
41495
|
this.sendJson(res, 200, {
|
|
41552
41496
|
message: 'Bot restarted',
|
|
41553
41497
|
tenantId,
|
|
41554
|
-
status: newBot.status
|
|
41498
|
+
status: newBot.status,
|
|
41555
41499
|
});
|
|
41556
41500
|
}
|
|
41557
41501
|
catch (error) {
|
|
@@ -41564,7 +41508,7 @@ class BotManagerApi {
|
|
|
41564
41508
|
if (!validation.success || !validation.data) {
|
|
41565
41509
|
return this.sendJson(res, 400, {
|
|
41566
41510
|
error: 'Validation failed',
|
|
41567
|
-
details: validation.errors
|
|
41511
|
+
details: validation.errors,
|
|
41568
41512
|
});
|
|
41569
41513
|
}
|
|
41570
41514
|
const { id } = validation.data;
|
|
@@ -41579,7 +41523,7 @@ class BotManagerApi {
|
|
|
41579
41523
|
name: flowDef.name,
|
|
41580
41524
|
dynamic: true,
|
|
41581
41525
|
config: flowDef.config,
|
|
41582
|
-
createdAt: flowDef.createdAt
|
|
41526
|
+
createdAt: flowDef.createdAt,
|
|
41583
41527
|
});
|
|
41584
41528
|
}
|
|
41585
41529
|
catch (error) {
|
|
@@ -41594,14 +41538,14 @@ class BotManagerApi {
|
|
|
41594
41538
|
}
|
|
41595
41539
|
if (!existing.dynamic) {
|
|
41596
41540
|
return this.sendJson(res, 400, {
|
|
41597
|
-
error: 'Cannot update programmatic flows. Only dynamic flows can be updated.'
|
|
41541
|
+
error: 'Cannot update programmatic flows. Only dynamic flows can be updated.',
|
|
41598
41542
|
});
|
|
41599
41543
|
}
|
|
41600
41544
|
const validation = validate(updateFlowSchema, req.body);
|
|
41601
41545
|
if (!validation.success) {
|
|
41602
41546
|
return this.sendJson(res, 400, {
|
|
41603
41547
|
error: 'Validation failed',
|
|
41604
|
-
details: validation.errors
|
|
41548
|
+
details: validation.errors,
|
|
41605
41549
|
});
|
|
41606
41550
|
}
|
|
41607
41551
|
try {
|
|
@@ -41615,7 +41559,7 @@ class BotManagerApi {
|
|
|
41615
41559
|
name: flowDef.name,
|
|
41616
41560
|
dynamic: true,
|
|
41617
41561
|
config: flowDef.config,
|
|
41618
|
-
updatedAt: flowDef.updatedAt
|
|
41562
|
+
updatedAt: flowDef.updatedAt,
|
|
41619
41563
|
});
|
|
41620
41564
|
}
|
|
41621
41565
|
catch (error) {
|
|
@@ -41633,7 +41577,7 @@ class PersistenceManager {
|
|
|
41633
41577
|
this.config = {
|
|
41634
41578
|
persistenceDir: config.persistenceDir ?? './data',
|
|
41635
41579
|
fileName: config.fileName ?? 'bots.json',
|
|
41636
|
-
autoSave: config.autoSave ?? true
|
|
41580
|
+
autoSave: config.autoSave ?? true,
|
|
41637
41581
|
};
|
|
41638
41582
|
this.filePath = require$$1$5.join(this.config.persistenceDir, this.config.fileName);
|
|
41639
41583
|
this.load();
|
|
@@ -41651,7 +41595,7 @@ class PersistenceManager {
|
|
|
41651
41595
|
databaseOptions: config.databaseOptions,
|
|
41652
41596
|
providerClassName,
|
|
41653
41597
|
databaseClassName,
|
|
41654
|
-
createdAt: new Date().toISOString()
|
|
41598
|
+
createdAt: new Date().toISOString(),
|
|
41655
41599
|
};
|
|
41656
41600
|
this.data.set(tenantId, serializableConfig);
|
|
41657
41601
|
if (this.config.autoSave) {
|
|
@@ -41713,7 +41657,7 @@ class PersistenceManager {
|
|
|
41713
41657
|
const dataToSave = {
|
|
41714
41658
|
version: 1,
|
|
41715
41659
|
updatedAt: new Date().toISOString(),
|
|
41716
|
-
bots: Array.from(this.data.entries())
|
|
41660
|
+
bots: Array.from(this.data.entries()),
|
|
41717
41661
|
};
|
|
41718
41662
|
require$$0$9.writeFileSync(this.filePath, JSON.stringify(dataToSave, null, 2), 'utf-8');
|
|
41719
41663
|
}
|