@exulu/backend 0.2.0 → 0.2.2

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/index.cjs CHANGED
@@ -58,25 +58,19 @@ var redisServer = {
58
58
  // src/redis/client.ts
59
59
  var client = {};
60
60
  async function redisClient() {
61
- console.log("[EXULU] redisClient", redisServer);
62
- if (!redisServer.host?.length || !redisServer.port?.length) {
63
- console.error(`[EXULU] no redis server configured.`);
64
- return {
65
- client: null
66
- };
61
+ if (!redisServer.host || !redisServer.port) {
62
+ return { client: null };
67
63
  }
68
64
  if (!client["exulu"]) {
69
65
  try {
70
66
  const url = `redis://${redisServer.host}:${redisServer.port}`;
71
- console.log(`[EXULU] connecting to redis.`);
72
67
  client["exulu"] = (0, import_redis.createClient)({
73
- // todo add password
74
68
  url
75
69
  });
76
70
  await client["exulu"].connect();
77
71
  } catch (error) {
78
- console.error(`[EXULU] error connecting to redis.`, error);
79
- throw error;
72
+ console.error(`[EXULU] error connecting to redis:`, error);
73
+ return { client: null };
80
74
  }
81
75
  }
82
76
  return {
@@ -200,6 +194,10 @@ var usersSchema = {
200
194
  name: "apikey",
201
195
  type: "text"
202
196
  },
197
+ {
198
+ name: "last_used",
199
+ type: "date"
200
+ },
203
201
  {
204
202
  name: "role",
205
203
  type: "reference",
@@ -208,10 +206,6 @@ var usersSchema = {
208
206
  field: "id",
209
207
  onDelete: "CASCADE"
210
208
  }
211
- },
212
- {
213
- name: "last_used",
214
- type: "date"
215
209
  }
216
210
  ]
217
211
  };
@@ -410,7 +404,61 @@ var sanitizeName = (name) => {
410
404
  };
411
405
 
412
406
  // src/postgres/init-db.ts
407
+ var import_bcryptjs2 = __toESM(require("bcryptjs"), 1);
408
+
409
+ // src/auth/generate-key.ts
413
410
  var import_bcryptjs = __toESM(require("bcryptjs"), 1);
411
+ var SALT_ROUNDS = 12;
412
+ async function encryptApiKey(apikey) {
413
+ const hash = await import_bcryptjs.default.hash(apikey, SALT_ROUNDS);
414
+ return hash;
415
+ }
416
+ var generateApiKey = async (name, email) => {
417
+ const { db: db2 } = await postgresClient();
418
+ console.log("[EXULU] Inserting default user and admin role.");
419
+ const existingRole = await db2.from("roles").where({ name: "admin" }).first();
420
+ let roleId;
421
+ if (!existingRole) {
422
+ console.log("[EXULU] Creating default admin role.");
423
+ const role = await db2.from("roles").insert({
424
+ name: "admin",
425
+ is_admin: true,
426
+ agents: []
427
+ }).returning("id");
428
+ roleId = role[0].id;
429
+ } else {
430
+ roleId = existingRole.id;
431
+ }
432
+ const newKeyName = name;
433
+ const plainKey = `sk_${Math.random().toString(36).substring(2, 15)}_${Math.random().toString(36).substring(2, 15)}`;
434
+ const postFix = `/${newKeyName.toLowerCase().trim().replaceAll(" ", "_")}`;
435
+ const encryptedKey = await encryptApiKey(plainKey);
436
+ const existingApiUser = await db2.from("users").where({ email }).first();
437
+ if (!existingApiUser) {
438
+ console.log("[EXULU] Creating default api user.");
439
+ await db2.from("users").insert({
440
+ name,
441
+ email,
442
+ super_admin: true,
443
+ createdAt: /* @__PURE__ */ new Date(),
444
+ updatedAt: /* @__PURE__ */ new Date(),
445
+ type: "api",
446
+ apikey: `${encryptedKey}${postFix}`,
447
+ // password: "admin", todo add this again when we implement password auth / encryption as alternative to OTP
448
+ role: roleId
449
+ });
450
+ console.log("[EXULU] Default api user created. Key: ", `${plainKey}${postFix}`);
451
+ } else {
452
+ console.log("[EXULU] API user with that name already exists.");
453
+ }
454
+ console.log("[EXULU] Key generated, copy and use the plain key from here, you will not be able to access it again.");
455
+ console.log("[EXULU] Key: ", `${plainKey}${postFix}`);
456
+ return {
457
+ key: `${plainKey}${postFix}`
458
+ };
459
+ };
460
+
461
+ // src/postgres/init-db.ts
414
462
  var up = async function(knex) {
415
463
  if (!await knex.schema.hasTable("roles")) {
416
464
  await knex.schema.createTable("roles", (table) => {
@@ -515,6 +563,7 @@ var up = async function(knex) {
515
563
  table.timestamp("emailVerified", { useTz: true });
516
564
  table.text("image");
517
565
  for (const field of usersSchema.fields) {
566
+ console.log("[EXULU] field", field);
518
567
  const { type, name, references, default: defaultValue } = field;
519
568
  if (name === "id" || name === "name" || name === "email" || name === "emailVerified" || name === "image") {
520
569
  continue;
@@ -558,9 +607,9 @@ var up = async function(knex) {
558
607
  });
559
608
  }
560
609
  };
561
- var SALT_ROUNDS = 12;
562
- async function encryptApiKey(apikey) {
563
- const hash = await import_bcryptjs.default.hash(apikey, SALT_ROUNDS);
610
+ var SALT_ROUNDS2 = 12;
611
+ async function encryptApiKey2(apikey) {
612
+ const hash = await import_bcryptjs2.default.hash(apikey, SALT_ROUNDS2);
564
613
  return hash;
565
614
  }
566
615
  var execute = async () => {
@@ -584,7 +633,7 @@ var execute = async () => {
584
633
  const newKeyName = "exulu_default_key";
585
634
  const plainKey = `sk_${Math.random().toString(36).substring(2, 15)}_${Math.random().toString(36).substring(2, 15)}`;
586
635
  const postFix = `/${newKeyName.toLowerCase().trim().replaceAll(" ", "_")}`;
587
- const encryptedKey = await encryptApiKey(plainKey);
636
+ const encryptedKey = await encryptApiKey2(plainKey);
588
637
  const existingUser = await db2.from("users").where({ email: "admin@exulu.com" }).first();
589
638
  if (!existingUser) {
590
639
  console.log("[EXULU] Creating default admin user.");
@@ -599,23 +648,9 @@ var execute = async () => {
599
648
  role: roleId
600
649
  });
601
650
  }
602
- const existingApiUser = await db2.from("users").where({ email: "api@exulu.com" }).first();
603
- if (!existingApiUser) {
604
- console.log("[EXULU] Creating default api user.");
605
- await db2.from("users").insert({
606
- name: "exulu",
607
- email: "admin@exulu.com",
608
- super_admin: true,
609
- createdAt: /* @__PURE__ */ new Date(),
610
- updatedAt: /* @__PURE__ */ new Date(),
611
- type: "api",
612
- apikey: `${encryptedKey}${postFix}`,
613
- // password: "admin", todo add this again when we implement password auth / encryption as alternative to OTP
614
- role: roleId
615
- });
616
- }
651
+ const { key } = await generateApiKey("exulu", "api@exulu.com");
617
652
  console.log("[EXULU] Database initialized.");
618
- console.log("[EXULU] Default api key: ", `${encryptedKey}${postFix}`);
653
+ console.log("[EXULU] Default api key: ", `${key}`);
619
654
  return;
620
655
  };
621
656
 
@@ -1436,30 +1471,38 @@ var import_express3 = require("express");
1436
1471
 
1437
1472
  // src/registry/rate-limiter.ts
1438
1473
  var rateLimiter = async (key, windowSeconds, limit, points) => {
1439
- const { client: client2 } = await redisClient();
1440
- if (!client2) {
1474
+ try {
1475
+ const { client: client2 } = await redisClient();
1476
+ if (!client2) {
1477
+ console.warn("[EXULU] Rate limiting disabled - Redis not available");
1478
+ return {
1479
+ status: true,
1480
+ retryAfter: null
1481
+ };
1482
+ }
1483
+ const redisKey = `exulu/${key}`;
1484
+ const current = await client2.incrBy(redisKey, points);
1485
+ if (current === points) {
1486
+ await client2.expire(redisKey, windowSeconds);
1487
+ }
1488
+ if (current > limit) {
1489
+ const ttl = await client2.ttl(redisKey);
1490
+ return {
1491
+ status: false,
1492
+ retryAfter: ttl
1493
+ };
1494
+ }
1441
1495
  return {
1442
- status: false,
1443
- retryAfter: 10
1444
- // 10 seconds
1496
+ status: true,
1497
+ retryAfter: null
1445
1498
  };
1446
- }
1447
- const redisKey = `exulu/${key}`;
1448
- const current = await client2.incrBy(redisKey, points);
1449
- if (current === points) {
1450
- await client2.expire(redisKey, windowSeconds);
1451
- }
1452
- if (current > limit) {
1453
- const ttl = await client2.ttl(redisKey);
1499
+ } catch (error) {
1500
+ console.error("[EXULU] Rate limiting error:", error);
1454
1501
  return {
1455
- status: false,
1456
- retryAfter: ttl
1502
+ status: true,
1503
+ retryAfter: null
1457
1504
  };
1458
1505
  }
1459
- return {
1460
- status: true,
1461
- retryAfter: null
1462
- };
1463
1506
  };
1464
1507
 
1465
1508
  // src/registry/route-validators/index.ts
@@ -1467,13 +1510,14 @@ var import_express = require("express");
1467
1510
  var import_jwt = require("next-auth/jwt");
1468
1511
 
1469
1512
  // src/auth/auth.ts
1470
- var import_bcryptjs2 = __toESM(require("bcryptjs"), 1);
1513
+ var import_bcryptjs3 = __toESM(require("bcryptjs"), 1);
1471
1514
  var authentication = async ({
1472
1515
  apikey,
1473
1516
  authtoken,
1474
1517
  internalkey,
1475
1518
  db: db2
1476
1519
  }) => {
1520
+ console.log("[EXULU] apikey", apikey);
1477
1521
  if (internalkey) {
1478
1522
  if (!process.env.INTERNAL_SECRET) {
1479
1523
  return {
@@ -1541,31 +1585,38 @@ var authentication = async ({
1541
1585
  code: 401
1542
1586
  };
1543
1587
  }
1544
- const keyParts = apikey.split("/");
1545
- const keyName = keyParts.pop();
1546
- const keyValue = keyParts[0];
1547
- if (!keyName) {
1588
+ const request_key_parts = apikey.split("/");
1589
+ const request_key_name = request_key_parts.pop();
1590
+ const request_key_last_slash_index = apikey.lastIndexOf("/");
1591
+ const request_key_compare_value = apikey.substring(0, request_key_last_slash_index);
1592
+ if (!request_key_name) {
1548
1593
  return {
1549
1594
  error: true,
1550
1595
  message: "Provided api key does not include postfix with key name ({key}/{name}).",
1551
1596
  code: 401
1552
1597
  };
1553
1598
  }
1554
- if (!keyValue) {
1599
+ if (!request_key_compare_value) {
1555
1600
  return {
1556
1601
  error: true,
1557
1602
  message: "Provided api key is not in the correct format.",
1558
1603
  code: 401
1559
1604
  };
1560
1605
  }
1561
- const filtered = users.filter(({ apikey: apikey2, id }) => apikey2.includes(keyName));
1606
+ console.log("[EXULU] users", users);
1607
+ console.log("[EXULU] request_key_name", request_key_name);
1608
+ console.log("[EXULU] request_key_compare_value", request_key_compare_value);
1609
+ const filtered = users.filter(({ apikey: apikey2, id }) => apikey2.includes(request_key_name));
1610
+ console.log("[EXULU] filtered", filtered);
1562
1611
  for (const user of filtered) {
1563
- const lastSlashIndex = user.apikey.lastIndexOf("/");
1564
- const compareValue = lastSlashIndex !== -1 ? user.apikey.substring(0, lastSlashIndex) : user.apikey;
1565
- const isMatch = await import_bcryptjs2.default.compare(keyValue, compareValue);
1612
+ const user_key_last_slash_index = user.apikey.lastIndexOf("/");
1613
+ const user_key_compare_value = user.apikey.substring(0, user_key_last_slash_index);
1614
+ console.log("[EXULU] user_key_compare_value", user_key_compare_value);
1615
+ const isMatch = await import_bcryptjs3.default.compare(request_key_compare_value, user_key_compare_value);
1616
+ console.log("[EXULU] isMatch", isMatch);
1566
1617
  if (isMatch) {
1567
1618
  await db2.from("users").where({ id: user.id }).update({
1568
- lastUsed: /* @__PURE__ */ new Date()
1619
+ last_used: /* @__PURE__ */ new Date()
1569
1620
  }).returning("id");
1570
1621
  return {
1571
1622
  error: false,
@@ -1574,6 +1625,12 @@ var authentication = async ({
1574
1625
  };
1575
1626
  }
1576
1627
  }
1628
+ console.log("[EXULU] No matching api key found.");
1629
+ return {
1630
+ error: true,
1631
+ message: "No matching api key found.",
1632
+ code: 401
1633
+ };
1577
1634
  }
1578
1635
  return {
1579
1636
  error: true,
@@ -2509,6 +2566,7 @@ var createUppyRoutes = async (app) => {
2509
2566
  };
2510
2567
 
2511
2568
  // src/registry/routes.ts
2569
+ var import_utils = require("@apollo/utils.keyvaluecache");
2512
2570
  var Papa = require("papaparse");
2513
2571
  var global_queues = {
2514
2572
  logs_cleaner: "logs-cleaner"
@@ -2608,8 +2666,15 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
2608
2666
  console.log("===========================", "[EXULU] no redis server configured, not setting up recurring jobs.", "===========================");
2609
2667
  }
2610
2668
  const schema = createSDL([usersSchema, rolesSchema, agentsSchema, jobsSchema]);
2611
- const server = new import_server3.ApolloServer({ schema, introspection: true });
2669
+ console.log("[EXULU] graphql server");
2670
+ const server = new import_server3.ApolloServer({
2671
+ cache: new import_utils.InMemoryLRUCache(),
2672
+ schema,
2673
+ introspection: true
2674
+ });
2675
+ console.log("[EXULU] starting graphql server");
2612
2676
  await server.start();
2677
+ console.log("[EXULU] graphql server started");
2613
2678
  app.use(
2614
2679
  "/graphql",
2615
2680
  (0, import_cors.default)(),
@@ -2830,12 +2895,25 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
2830
2895
  });
2831
2896
  app.post("/items/:context", async (req, res) => {
2832
2897
  try {
2898
+ console.log("[EXULU] post items");
2833
2899
  if (!req.params.context) {
2834
2900
  res.status(400).json({
2835
2901
  message: "Missing context in request."
2836
2902
  });
2837
2903
  return;
2838
2904
  }
2905
+ if (!req.body) {
2906
+ res.status(400).json({
2907
+ message: "Missing body in request."
2908
+ });
2909
+ return;
2910
+ }
2911
+ if (!req.body.name) {
2912
+ res.status(400).json({
2913
+ message: "Missing in body of request."
2914
+ });
2915
+ return;
2916
+ }
2839
2917
  const authenticationResult = await requestValidators.authenticate(req);
2840
2918
  if (!authenticationResult.user?.id) {
2841
2919
  res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
@@ -2942,6 +3020,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
2942
3020
  method: "DELETE",
2943
3021
  note: `Delete specific embedding for a context.`
2944
3022
  });
3023
+ console.log("[EXULU] delete embedding by id");
2945
3024
  app.delete(`items/:context/:id`, async (req, res) => {
2946
3025
  const id = req.params.id;
2947
3026
  if (!req.params.context) {
@@ -2973,6 +3052,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
2973
3052
  message: "Embedding deleted."
2974
3053
  });
2975
3054
  });
3055
+ console.log("[EXULU] statistics timeseries");
2976
3056
  app.post("/statistics/timeseries", async (req, res) => {
2977
3057
  const authenticationResult = await requestValidators.authenticate(req);
2978
3058
  if (!authenticationResult.user?.id) {
@@ -3021,6 +3101,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3021
3101
  }
3022
3102
  });
3023
3103
  });
3104
+ console.log("[EXULU] statistics totals");
3024
3105
  app.post("/statistics/totals", async (req, res) => {
3025
3106
  const authenticationResult = await requestValidators.authenticate(req);
3026
3107
  if (!authenticationResult.user?.id) {
@@ -3049,6 +3130,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3049
3130
  }
3050
3131
  });
3051
3132
  });
3133
+ console.log("[EXULU] contexts statistics");
3052
3134
  app.get("/contexts/statistics", async (req, res) => {
3053
3135
  const authenticationResult = await requestValidators.authenticate(req);
3054
3136
  if (!authenticationResult.user?.id) {
@@ -3080,6 +3162,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3080
3162
  }
3081
3163
  });
3082
3164
  });
3165
+ console.log("[EXULU] context by id");
3083
3166
  app.get(`/contexts/:id`, async (req, res) => {
3084
3167
  const authenticationResult = await requestValidators.authenticate(req);
3085
3168
  if (!authenticationResult.user?.id) {
@@ -3125,6 +3208,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3125
3208
  // todo
3126
3209
  });
3127
3210
  });
3211
+ console.log("[EXULU] items export by context");
3128
3212
  app.get(`/items/export/:context`, async (req, res) => {
3129
3213
  if (!req.params.context) {
3130
3214
  res.status(400).json({
@@ -3153,6 +3237,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3153
3237
  const ISOTime = (/* @__PURE__ */ new Date()).toISOString();
3154
3238
  res.status(200).attachment(`${context.name}-items-export-${ISOTime}.csv`).send(csv);
3155
3239
  });
3240
+ console.log("[EXULU] contexts get list");
3156
3241
  app.get(`/contexts`, async (req, res) => {
3157
3242
  console.log("contexts!!");
3158
3243
  const authenticationResult = await requestValidators.authenticate(req);
@@ -3182,6 +3267,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3182
3267
  }))
3183
3268
  })));
3184
3269
  });
3270
+ console.log("[EXULU] workflows get list");
3185
3271
  app.get(`/workflows`, async (req, res) => {
3186
3272
  const authenticationResult = await requestValidators.authenticate(req);
3187
3273
  if (!authenticationResult.user?.id) {
@@ -3197,6 +3283,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3197
3283
  inputSchema: workflow.inputSchema ? (0, import_zodex.zerialize)(workflow.inputSchema) : null
3198
3284
  })));
3199
3285
  });
3286
+ console.log("[EXULU] workflow by id");
3200
3287
  app.get(`/workflows/:id`, async (req, res) => {
3201
3288
  const authenticationResult = await requestValidators.authenticate(req);
3202
3289
  if (!authenticationResult.user?.id) {
@@ -3224,6 +3311,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3224
3311
  workflow: void 0
3225
3312
  });
3226
3313
  });
3314
+ console.log("[EXULU] contexts");
3227
3315
  contexts.forEach((context) => {
3228
3316
  const sources = context.sources.get();
3229
3317
  if (!Array.isArray(sources)) {
@@ -3239,66 +3327,14 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3239
3327
  note: `Webhook updater for ${context.name}`
3240
3328
  });
3241
3329
  app.post(`${updater.slug}/${updater.type}/:context`, async (req, res) => {
3242
- const { context: id } = req.params;
3243
- if (!id) {
3244
- res.status(400).json({
3245
- message: "Missing context id in request."
3246
- });
3247
- return;
3248
- }
3249
- const context2 = contexts.find((context3) => context3.id === id);
3250
- if (!context2) {
3251
- res.status(400).json({
3252
- message: `Context for provided id: ${id} not found.`
3253
- });
3254
- return;
3255
- }
3256
- if (!context2.embedder.queue) {
3257
- res.status(500).json({ detail: "No queue set for embedder." });
3258
- return;
3259
- }
3260
- const authenticationResult = await requestValidators.authenticate(req);
3261
- if (!authenticationResult.user?.id) {
3262
- res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
3263
- return;
3264
- }
3265
- const requestValidationResult = requestValidators.embedders(req, updater.configuration);
3266
- if (requestValidationResult.error) {
3267
- res.status(requestValidationResult.code || 500).json({ detail: `${requestValidationResult.message}` });
3268
- return;
3269
- }
3270
- const documents = await updater.fn(req.body.configuration);
3271
- const batches = [];
3272
- for (let i = 0; i < documents.length; i += context2.embedder.batchSize) {
3273
- batches.push(documents.slice(i, i + context2.embedder.batchSize));
3274
- }
3275
- let promises2 = [];
3276
- if (batches.length > 0) {
3277
- promises2 = batches.map((documents2) => {
3278
- return bullmqDecorator({
3279
- label: `Job running context '${context2.name}' with embedder '${context2.embedder.name}' for '${req.body.label}'`,
3280
- type: "embedder",
3281
- embedder: context2.embedder.id,
3282
- updater: updater.id,
3283
- context: context2.id,
3284
- trigger: updater.type,
3285
- source: source.id,
3286
- inputs: req.body.inputs,
3287
- ...updater.configuration && { configuration: req.body.configuration },
3288
- documents: documents2,
3289
- queue: context2.embedder.queue,
3290
- user: authenticationResult.user.id
3291
- });
3292
- });
3293
- }
3294
- const jobs = await Promise.all(promises2);
3295
- res.status(200).json(jobs);
3330
+ res.status(200).json([]);
3296
3331
  return;
3297
3332
  });
3298
3333
  }
3299
3334
  });
3300
3335
  });
3301
3336
  });
3337
+ console.log("[EXULU] agents");
3302
3338
  agents.forEach((agent) => {
3303
3339
  const slug = agent.slug;
3304
3340
  if (!slug) return;
@@ -3399,6 +3435,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3399
3435
  }
3400
3436
  });
3401
3437
  });
3438
+ console.log("[EXULU] workflows");
3402
3439
  workflows.forEach((workflow) => {
3403
3440
  routeLogs.push({
3404
3441
  route: workflow.slug,
@@ -3464,7 +3501,11 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3464
3501
  return;
3465
3502
  });
3466
3503
  });
3467
- await createUppyRoutes(app);
3504
+ if (process.env.COMPANION_S3_REGION && process.env.COMPANION_S3_KEY && process.env.COMPANION_S3_SECRET) {
3505
+ await createUppyRoutes(app);
3506
+ } else {
3507
+ console.log("[EXULU] skipping uppy file upload routes, because no S3 compatible region, key or secret is set in the environment.");
3508
+ }
3468
3509
  console.log("Routes:");
3469
3510
  console.table(routeLogs);
3470
3511
  };
@@ -3564,11 +3605,18 @@ var bullmq = {
3564
3605
  var fs2 = __toESM(require("fs"), 1);
3565
3606
  var import_path = __toESM(require("path"), 1);
3566
3607
  var defaultLogsDir = import_path.default.join(process.cwd(), "logs");
3567
- var redisConnection = new import_ioredis.default({
3568
- ...redisServer,
3569
- maxRetriesPerRequest: null
3570
- });
3608
+ var redisConnection;
3571
3609
  var createWorkers = async (queues2, contexts, embedders, workflows, _logsDir) => {
3610
+ if (!redisServer.host || !redisServer.port) {
3611
+ console.error("[EXULU] you are trying to start workers, but no redis server is configured in the environment.");
3612
+ throw new Error("No redis server configured in the environment, so cannot start workers.");
3613
+ }
3614
+ if (!redisConnection) {
3615
+ redisConnection = new import_ioredis.default({
3616
+ ...redisServer,
3617
+ maxRetriesPerRequest: null
3618
+ });
3619
+ }
3572
3620
  const logsDir = _logsDir || defaultLogsDir;
3573
3621
  const workers = queues2.map((queue) => {
3574
3622
  console.log(`[EXULU] creating worker for queue ${queue}.`);
@@ -3803,6 +3851,9 @@ var ExuluJobs = {
3803
3851
  var ExuluDatabase = {
3804
3852
  init: async () => {
3805
3853
  await execute();
3854
+ },
3855
+ generateApiKey: async (name, email) => {
3856
+ return await generateApiKey(name, email);
3806
3857
  }
3807
3858
  };
3808
3859
  // Annotate the CommonJS export names for ESM import in node:
package/dist/index.d.cts CHANGED
@@ -449,6 +449,9 @@ declare const ExuluJobs: {
449
449
 
450
450
  declare const ExuluDatabase: {
451
451
  init: () => Promise<void>;
452
+ generateApiKey: (name: string, email: string) => Promise<{
453
+ key: string;
454
+ }>;
452
455
  };
453
456
 
454
457
  export { type STATISTICS_TYPE as EXULU_STATISTICS_TYPE, STATISTICS_TYPE_ENUM as EXULU_STATISTICS_TYPE_ENUM, ExuluAgent, ExuluApp, authentication as ExuluAuthentication, ExuluContext, ExuluDatabase, ExuluEmbedder, ExuluJobs, queues as ExuluQueues, ExuluSource, ExuluTool, ExuluWorkflow, ExuluZodFileType };
package/dist/index.d.ts CHANGED
@@ -449,6 +449,9 @@ declare const ExuluJobs: {
449
449
 
450
450
  declare const ExuluDatabase: {
451
451
  init: () => Promise<void>;
452
+ generateApiKey: (name: string, email: string) => Promise<{
453
+ key: string;
454
+ }>;
452
455
  };
453
456
 
454
457
  export { type STATISTICS_TYPE as EXULU_STATISTICS_TYPE, STATISTICS_TYPE_ENUM as EXULU_STATISTICS_TYPE_ENUM, ExuluAgent, ExuluApp, authentication as ExuluAuthentication, ExuluContext, ExuluDatabase, ExuluEmbedder, ExuluJobs, queues as ExuluQueues, ExuluSource, ExuluTool, ExuluWorkflow, ExuluZodFileType };
package/dist/index.js CHANGED
@@ -17,25 +17,19 @@ var redisServer = {
17
17
  // src/redis/client.ts
18
18
  var client = {};
19
19
  async function redisClient() {
20
- console.log("[EXULU] redisClient", redisServer);
21
- if (!redisServer.host?.length || !redisServer.port?.length) {
22
- console.error(`[EXULU] no redis server configured.`);
23
- return {
24
- client: null
25
- };
20
+ if (!redisServer.host || !redisServer.port) {
21
+ return { client: null };
26
22
  }
27
23
  if (!client["exulu"]) {
28
24
  try {
29
25
  const url = `redis://${redisServer.host}:${redisServer.port}`;
30
- console.log(`[EXULU] connecting to redis.`);
31
26
  client["exulu"] = createClient({
32
- // todo add password
33
27
  url
34
28
  });
35
29
  await client["exulu"].connect();
36
30
  } catch (error) {
37
- console.error(`[EXULU] error connecting to redis.`, error);
38
- throw error;
31
+ console.error(`[EXULU] error connecting to redis:`, error);
32
+ return { client: null };
39
33
  }
40
34
  }
41
35
  return {
@@ -159,6 +153,10 @@ var usersSchema = {
159
153
  name: "apikey",
160
154
  type: "text"
161
155
  },
156
+ {
157
+ name: "last_used",
158
+ type: "date"
159
+ },
162
160
  {
163
161
  name: "role",
164
162
  type: "reference",
@@ -167,10 +165,6 @@ var usersSchema = {
167
165
  field: "id",
168
166
  onDelete: "CASCADE"
169
167
  }
170
- },
171
- {
172
- name: "last_used",
173
- type: "date"
174
168
  }
175
169
  ]
176
170
  };
@@ -369,7 +363,61 @@ var sanitizeName = (name) => {
369
363
  };
370
364
 
371
365
  // src/postgres/init-db.ts
366
+ import bcrypt2 from "bcryptjs";
367
+
368
+ // src/auth/generate-key.ts
372
369
  import bcrypt from "bcryptjs";
370
+ var SALT_ROUNDS = 12;
371
+ async function encryptApiKey(apikey) {
372
+ const hash = await bcrypt.hash(apikey, SALT_ROUNDS);
373
+ return hash;
374
+ }
375
+ var generateApiKey = async (name, email) => {
376
+ const { db: db2 } = await postgresClient();
377
+ console.log("[EXULU] Inserting default user and admin role.");
378
+ const existingRole = await db2.from("roles").where({ name: "admin" }).first();
379
+ let roleId;
380
+ if (!existingRole) {
381
+ console.log("[EXULU] Creating default admin role.");
382
+ const role = await db2.from("roles").insert({
383
+ name: "admin",
384
+ is_admin: true,
385
+ agents: []
386
+ }).returning("id");
387
+ roleId = role[0].id;
388
+ } else {
389
+ roleId = existingRole.id;
390
+ }
391
+ const newKeyName = name;
392
+ const plainKey = `sk_${Math.random().toString(36).substring(2, 15)}_${Math.random().toString(36).substring(2, 15)}`;
393
+ const postFix = `/${newKeyName.toLowerCase().trim().replaceAll(" ", "_")}`;
394
+ const encryptedKey = await encryptApiKey(plainKey);
395
+ const existingApiUser = await db2.from("users").where({ email }).first();
396
+ if (!existingApiUser) {
397
+ console.log("[EXULU] Creating default api user.");
398
+ await db2.from("users").insert({
399
+ name,
400
+ email,
401
+ super_admin: true,
402
+ createdAt: /* @__PURE__ */ new Date(),
403
+ updatedAt: /* @__PURE__ */ new Date(),
404
+ type: "api",
405
+ apikey: `${encryptedKey}${postFix}`,
406
+ // password: "admin", todo add this again when we implement password auth / encryption as alternative to OTP
407
+ role: roleId
408
+ });
409
+ console.log("[EXULU] Default api user created. Key: ", `${plainKey}${postFix}`);
410
+ } else {
411
+ console.log("[EXULU] API user with that name already exists.");
412
+ }
413
+ console.log("[EXULU] Key generated, copy and use the plain key from here, you will not be able to access it again.");
414
+ console.log("[EXULU] Key: ", `${plainKey}${postFix}`);
415
+ return {
416
+ key: `${plainKey}${postFix}`
417
+ };
418
+ };
419
+
420
+ // src/postgres/init-db.ts
373
421
  var up = async function(knex) {
374
422
  if (!await knex.schema.hasTable("roles")) {
375
423
  await knex.schema.createTable("roles", (table) => {
@@ -474,6 +522,7 @@ var up = async function(knex) {
474
522
  table.timestamp("emailVerified", { useTz: true });
475
523
  table.text("image");
476
524
  for (const field of usersSchema.fields) {
525
+ console.log("[EXULU] field", field);
477
526
  const { type, name, references, default: defaultValue } = field;
478
527
  if (name === "id" || name === "name" || name === "email" || name === "emailVerified" || name === "image") {
479
528
  continue;
@@ -517,9 +566,9 @@ var up = async function(knex) {
517
566
  });
518
567
  }
519
568
  };
520
- var SALT_ROUNDS = 12;
521
- async function encryptApiKey(apikey) {
522
- const hash = await bcrypt.hash(apikey, SALT_ROUNDS);
569
+ var SALT_ROUNDS2 = 12;
570
+ async function encryptApiKey2(apikey) {
571
+ const hash = await bcrypt2.hash(apikey, SALT_ROUNDS2);
523
572
  return hash;
524
573
  }
525
574
  var execute = async () => {
@@ -543,7 +592,7 @@ var execute = async () => {
543
592
  const newKeyName = "exulu_default_key";
544
593
  const plainKey = `sk_${Math.random().toString(36).substring(2, 15)}_${Math.random().toString(36).substring(2, 15)}`;
545
594
  const postFix = `/${newKeyName.toLowerCase().trim().replaceAll(" ", "_")}`;
546
- const encryptedKey = await encryptApiKey(plainKey);
595
+ const encryptedKey = await encryptApiKey2(plainKey);
547
596
  const existingUser = await db2.from("users").where({ email: "admin@exulu.com" }).first();
548
597
  if (!existingUser) {
549
598
  console.log("[EXULU] Creating default admin user.");
@@ -558,23 +607,9 @@ var execute = async () => {
558
607
  role: roleId
559
608
  });
560
609
  }
561
- const existingApiUser = await db2.from("users").where({ email: "api@exulu.com" }).first();
562
- if (!existingApiUser) {
563
- console.log("[EXULU] Creating default api user.");
564
- await db2.from("users").insert({
565
- name: "exulu",
566
- email: "admin@exulu.com",
567
- super_admin: true,
568
- createdAt: /* @__PURE__ */ new Date(),
569
- updatedAt: /* @__PURE__ */ new Date(),
570
- type: "api",
571
- apikey: `${encryptedKey}${postFix}`,
572
- // password: "admin", todo add this again when we implement password auth / encryption as alternative to OTP
573
- role: roleId
574
- });
575
- }
610
+ const { key } = await generateApiKey("exulu", "api@exulu.com");
576
611
  console.log("[EXULU] Database initialized.");
577
- console.log("[EXULU] Default api key: ", `${encryptedKey}${postFix}`);
612
+ console.log("[EXULU] Default api key: ", `${key}`);
578
613
  return;
579
614
  };
580
615
 
@@ -1395,30 +1430,38 @@ import "express";
1395
1430
 
1396
1431
  // src/registry/rate-limiter.ts
1397
1432
  var rateLimiter = async (key, windowSeconds, limit, points) => {
1398
- const { client: client2 } = await redisClient();
1399
- if (!client2) {
1433
+ try {
1434
+ const { client: client2 } = await redisClient();
1435
+ if (!client2) {
1436
+ console.warn("[EXULU] Rate limiting disabled - Redis not available");
1437
+ return {
1438
+ status: true,
1439
+ retryAfter: null
1440
+ };
1441
+ }
1442
+ const redisKey = `exulu/${key}`;
1443
+ const current = await client2.incrBy(redisKey, points);
1444
+ if (current === points) {
1445
+ await client2.expire(redisKey, windowSeconds);
1446
+ }
1447
+ if (current > limit) {
1448
+ const ttl = await client2.ttl(redisKey);
1449
+ return {
1450
+ status: false,
1451
+ retryAfter: ttl
1452
+ };
1453
+ }
1400
1454
  return {
1401
- status: false,
1402
- retryAfter: 10
1403
- // 10 seconds
1455
+ status: true,
1456
+ retryAfter: null
1404
1457
  };
1405
- }
1406
- const redisKey = `exulu/${key}`;
1407
- const current = await client2.incrBy(redisKey, points);
1408
- if (current === points) {
1409
- await client2.expire(redisKey, windowSeconds);
1410
- }
1411
- if (current > limit) {
1412
- const ttl = await client2.ttl(redisKey);
1458
+ } catch (error) {
1459
+ console.error("[EXULU] Rate limiting error:", error);
1413
1460
  return {
1414
- status: false,
1415
- retryAfter: ttl
1461
+ status: true,
1462
+ retryAfter: null
1416
1463
  };
1417
1464
  }
1418
- return {
1419
- status: true,
1420
- retryAfter: null
1421
- };
1422
1465
  };
1423
1466
 
1424
1467
  // src/registry/route-validators/index.ts
@@ -1426,13 +1469,14 @@ import "express";
1426
1469
  import { getToken } from "next-auth/jwt";
1427
1470
 
1428
1471
  // src/auth/auth.ts
1429
- import bcrypt2 from "bcryptjs";
1472
+ import bcrypt3 from "bcryptjs";
1430
1473
  var authentication = async ({
1431
1474
  apikey,
1432
1475
  authtoken,
1433
1476
  internalkey,
1434
1477
  db: db2
1435
1478
  }) => {
1479
+ console.log("[EXULU] apikey", apikey);
1436
1480
  if (internalkey) {
1437
1481
  if (!process.env.INTERNAL_SECRET) {
1438
1482
  return {
@@ -1500,31 +1544,38 @@ var authentication = async ({
1500
1544
  code: 401
1501
1545
  };
1502
1546
  }
1503
- const keyParts = apikey.split("/");
1504
- const keyName = keyParts.pop();
1505
- const keyValue = keyParts[0];
1506
- if (!keyName) {
1547
+ const request_key_parts = apikey.split("/");
1548
+ const request_key_name = request_key_parts.pop();
1549
+ const request_key_last_slash_index = apikey.lastIndexOf("/");
1550
+ const request_key_compare_value = apikey.substring(0, request_key_last_slash_index);
1551
+ if (!request_key_name) {
1507
1552
  return {
1508
1553
  error: true,
1509
1554
  message: "Provided api key does not include postfix with key name ({key}/{name}).",
1510
1555
  code: 401
1511
1556
  };
1512
1557
  }
1513
- if (!keyValue) {
1558
+ if (!request_key_compare_value) {
1514
1559
  return {
1515
1560
  error: true,
1516
1561
  message: "Provided api key is not in the correct format.",
1517
1562
  code: 401
1518
1563
  };
1519
1564
  }
1520
- const filtered = users.filter(({ apikey: apikey2, id }) => apikey2.includes(keyName));
1565
+ console.log("[EXULU] users", users);
1566
+ console.log("[EXULU] request_key_name", request_key_name);
1567
+ console.log("[EXULU] request_key_compare_value", request_key_compare_value);
1568
+ const filtered = users.filter(({ apikey: apikey2, id }) => apikey2.includes(request_key_name));
1569
+ console.log("[EXULU] filtered", filtered);
1521
1570
  for (const user of filtered) {
1522
- const lastSlashIndex = user.apikey.lastIndexOf("/");
1523
- const compareValue = lastSlashIndex !== -1 ? user.apikey.substring(0, lastSlashIndex) : user.apikey;
1524
- const isMatch = await bcrypt2.compare(keyValue, compareValue);
1571
+ const user_key_last_slash_index = user.apikey.lastIndexOf("/");
1572
+ const user_key_compare_value = user.apikey.substring(0, user_key_last_slash_index);
1573
+ console.log("[EXULU] user_key_compare_value", user_key_compare_value);
1574
+ const isMatch = await bcrypt3.compare(request_key_compare_value, user_key_compare_value);
1575
+ console.log("[EXULU] isMatch", isMatch);
1525
1576
  if (isMatch) {
1526
1577
  await db2.from("users").where({ id: user.id }).update({
1527
- lastUsed: /* @__PURE__ */ new Date()
1578
+ last_used: /* @__PURE__ */ new Date()
1528
1579
  }).returning("id");
1529
1580
  return {
1530
1581
  error: false,
@@ -1533,6 +1584,12 @@ var authentication = async ({
1533
1584
  };
1534
1585
  }
1535
1586
  }
1587
+ console.log("[EXULU] No matching api key found.");
1588
+ return {
1589
+ error: true,
1590
+ message: "No matching api key found.",
1591
+ code: 401
1592
+ };
1536
1593
  }
1537
1594
  return {
1538
1595
  error: true,
@@ -2468,6 +2525,7 @@ var createUppyRoutes = async (app) => {
2468
2525
  };
2469
2526
 
2470
2527
  // src/registry/routes.ts
2528
+ import { InMemoryLRUCache } from "@apollo/utils.keyvaluecache";
2471
2529
  var Papa = __require("papaparse");
2472
2530
  var global_queues = {
2473
2531
  logs_cleaner: "logs-cleaner"
@@ -2567,8 +2625,15 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
2567
2625
  console.log("===========================", "[EXULU] no redis server configured, not setting up recurring jobs.", "===========================");
2568
2626
  }
2569
2627
  const schema = createSDL([usersSchema, rolesSchema, agentsSchema, jobsSchema]);
2570
- const server = new ApolloServer({ schema, introspection: true });
2628
+ console.log("[EXULU] graphql server");
2629
+ const server = new ApolloServer({
2630
+ cache: new InMemoryLRUCache(),
2631
+ schema,
2632
+ introspection: true
2633
+ });
2634
+ console.log("[EXULU] starting graphql server");
2571
2635
  await server.start();
2636
+ console.log("[EXULU] graphql server started");
2572
2637
  app.use(
2573
2638
  "/graphql",
2574
2639
  cors(),
@@ -2789,12 +2854,25 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
2789
2854
  });
2790
2855
  app.post("/items/:context", async (req, res) => {
2791
2856
  try {
2857
+ console.log("[EXULU] post items");
2792
2858
  if (!req.params.context) {
2793
2859
  res.status(400).json({
2794
2860
  message: "Missing context in request."
2795
2861
  });
2796
2862
  return;
2797
2863
  }
2864
+ if (!req.body) {
2865
+ res.status(400).json({
2866
+ message: "Missing body in request."
2867
+ });
2868
+ return;
2869
+ }
2870
+ if (!req.body.name) {
2871
+ res.status(400).json({
2872
+ message: "Missing in body of request."
2873
+ });
2874
+ return;
2875
+ }
2798
2876
  const authenticationResult = await requestValidators.authenticate(req);
2799
2877
  if (!authenticationResult.user?.id) {
2800
2878
  res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
@@ -2901,6 +2979,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
2901
2979
  method: "DELETE",
2902
2980
  note: `Delete specific embedding for a context.`
2903
2981
  });
2982
+ console.log("[EXULU] delete embedding by id");
2904
2983
  app.delete(`items/:context/:id`, async (req, res) => {
2905
2984
  const id = req.params.id;
2906
2985
  if (!req.params.context) {
@@ -2932,6 +3011,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
2932
3011
  message: "Embedding deleted."
2933
3012
  });
2934
3013
  });
3014
+ console.log("[EXULU] statistics timeseries");
2935
3015
  app.post("/statistics/timeseries", async (req, res) => {
2936
3016
  const authenticationResult = await requestValidators.authenticate(req);
2937
3017
  if (!authenticationResult.user?.id) {
@@ -2980,6 +3060,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
2980
3060
  }
2981
3061
  });
2982
3062
  });
3063
+ console.log("[EXULU] statistics totals");
2983
3064
  app.post("/statistics/totals", async (req, res) => {
2984
3065
  const authenticationResult = await requestValidators.authenticate(req);
2985
3066
  if (!authenticationResult.user?.id) {
@@ -3008,6 +3089,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3008
3089
  }
3009
3090
  });
3010
3091
  });
3092
+ console.log("[EXULU] contexts statistics");
3011
3093
  app.get("/contexts/statistics", async (req, res) => {
3012
3094
  const authenticationResult = await requestValidators.authenticate(req);
3013
3095
  if (!authenticationResult.user?.id) {
@@ -3039,6 +3121,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3039
3121
  }
3040
3122
  });
3041
3123
  });
3124
+ console.log("[EXULU] context by id");
3042
3125
  app.get(`/contexts/:id`, async (req, res) => {
3043
3126
  const authenticationResult = await requestValidators.authenticate(req);
3044
3127
  if (!authenticationResult.user?.id) {
@@ -3084,6 +3167,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3084
3167
  // todo
3085
3168
  });
3086
3169
  });
3170
+ console.log("[EXULU] items export by context");
3087
3171
  app.get(`/items/export/:context`, async (req, res) => {
3088
3172
  if (!req.params.context) {
3089
3173
  res.status(400).json({
@@ -3112,6 +3196,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3112
3196
  const ISOTime = (/* @__PURE__ */ new Date()).toISOString();
3113
3197
  res.status(200).attachment(`${context.name}-items-export-${ISOTime}.csv`).send(csv);
3114
3198
  });
3199
+ console.log("[EXULU] contexts get list");
3115
3200
  app.get(`/contexts`, async (req, res) => {
3116
3201
  console.log("contexts!!");
3117
3202
  const authenticationResult = await requestValidators.authenticate(req);
@@ -3141,6 +3226,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3141
3226
  }))
3142
3227
  })));
3143
3228
  });
3229
+ console.log("[EXULU] workflows get list");
3144
3230
  app.get(`/workflows`, async (req, res) => {
3145
3231
  const authenticationResult = await requestValidators.authenticate(req);
3146
3232
  if (!authenticationResult.user?.id) {
@@ -3156,6 +3242,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3156
3242
  inputSchema: workflow.inputSchema ? zerialize(workflow.inputSchema) : null
3157
3243
  })));
3158
3244
  });
3245
+ console.log("[EXULU] workflow by id");
3159
3246
  app.get(`/workflows/:id`, async (req, res) => {
3160
3247
  const authenticationResult = await requestValidators.authenticate(req);
3161
3248
  if (!authenticationResult.user?.id) {
@@ -3183,6 +3270,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3183
3270
  workflow: void 0
3184
3271
  });
3185
3272
  });
3273
+ console.log("[EXULU] contexts");
3186
3274
  contexts.forEach((context) => {
3187
3275
  const sources = context.sources.get();
3188
3276
  if (!Array.isArray(sources)) {
@@ -3198,66 +3286,14 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3198
3286
  note: `Webhook updater for ${context.name}`
3199
3287
  });
3200
3288
  app.post(`${updater.slug}/${updater.type}/:context`, async (req, res) => {
3201
- const { context: id } = req.params;
3202
- if (!id) {
3203
- res.status(400).json({
3204
- message: "Missing context id in request."
3205
- });
3206
- return;
3207
- }
3208
- const context2 = contexts.find((context3) => context3.id === id);
3209
- if (!context2) {
3210
- res.status(400).json({
3211
- message: `Context for provided id: ${id} not found.`
3212
- });
3213
- return;
3214
- }
3215
- if (!context2.embedder.queue) {
3216
- res.status(500).json({ detail: "No queue set for embedder." });
3217
- return;
3218
- }
3219
- const authenticationResult = await requestValidators.authenticate(req);
3220
- if (!authenticationResult.user?.id) {
3221
- res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
3222
- return;
3223
- }
3224
- const requestValidationResult = requestValidators.embedders(req, updater.configuration);
3225
- if (requestValidationResult.error) {
3226
- res.status(requestValidationResult.code || 500).json({ detail: `${requestValidationResult.message}` });
3227
- return;
3228
- }
3229
- const documents = await updater.fn(req.body.configuration);
3230
- const batches = [];
3231
- for (let i = 0; i < documents.length; i += context2.embedder.batchSize) {
3232
- batches.push(documents.slice(i, i + context2.embedder.batchSize));
3233
- }
3234
- let promises2 = [];
3235
- if (batches.length > 0) {
3236
- promises2 = batches.map((documents2) => {
3237
- return bullmqDecorator({
3238
- label: `Job running context '${context2.name}' with embedder '${context2.embedder.name}' for '${req.body.label}'`,
3239
- type: "embedder",
3240
- embedder: context2.embedder.id,
3241
- updater: updater.id,
3242
- context: context2.id,
3243
- trigger: updater.type,
3244
- source: source.id,
3245
- inputs: req.body.inputs,
3246
- ...updater.configuration && { configuration: req.body.configuration },
3247
- documents: documents2,
3248
- queue: context2.embedder.queue,
3249
- user: authenticationResult.user.id
3250
- });
3251
- });
3252
- }
3253
- const jobs = await Promise.all(promises2);
3254
- res.status(200).json(jobs);
3289
+ res.status(200).json([]);
3255
3290
  return;
3256
3291
  });
3257
3292
  }
3258
3293
  });
3259
3294
  });
3260
3295
  });
3296
+ console.log("[EXULU] agents");
3261
3297
  agents.forEach((agent) => {
3262
3298
  const slug = agent.slug;
3263
3299
  if (!slug) return;
@@ -3358,6 +3394,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3358
3394
  }
3359
3395
  });
3360
3396
  });
3397
+ console.log("[EXULU] workflows");
3361
3398
  workflows.forEach((workflow) => {
3362
3399
  routeLogs.push({
3363
3400
  route: workflow.slug,
@@ -3423,7 +3460,11 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3423
3460
  return;
3424
3461
  });
3425
3462
  });
3426
- await createUppyRoutes(app);
3463
+ if (process.env.COMPANION_S3_REGION && process.env.COMPANION_S3_KEY && process.env.COMPANION_S3_SECRET) {
3464
+ await createUppyRoutes(app);
3465
+ } else {
3466
+ console.log("[EXULU] skipping uppy file upload routes, because no S3 compatible region, key or secret is set in the environment.");
3467
+ }
3427
3468
  console.log("Routes:");
3428
3469
  console.table(routeLogs);
3429
3470
  };
@@ -3523,11 +3564,18 @@ var bullmq = {
3523
3564
  import * as fs2 from "fs";
3524
3565
  import path2 from "path";
3525
3566
  var defaultLogsDir = path2.join(process.cwd(), "logs");
3526
- var redisConnection = new IORedis({
3527
- ...redisServer,
3528
- maxRetriesPerRequest: null
3529
- });
3567
+ var redisConnection;
3530
3568
  var createWorkers = async (queues2, contexts, embedders, workflows, _logsDir) => {
3569
+ if (!redisServer.host || !redisServer.port) {
3570
+ console.error("[EXULU] you are trying to start workers, but no redis server is configured in the environment.");
3571
+ throw new Error("No redis server configured in the environment, so cannot start workers.");
3572
+ }
3573
+ if (!redisConnection) {
3574
+ redisConnection = new IORedis({
3575
+ ...redisServer,
3576
+ maxRetriesPerRequest: null
3577
+ });
3578
+ }
3531
3579
  const logsDir = _logsDir || defaultLogsDir;
3532
3580
  const workers = queues2.map((queue) => {
3533
3581
  console.log(`[EXULU] creating worker for queue ${queue}.`);
@@ -3762,6 +3810,9 @@ var ExuluJobs = {
3762
3810
  var ExuluDatabase = {
3763
3811
  init: async () => {
3764
3812
  await execute();
3813
+ },
3814
+ generateApiKey: async (name, email) => {
3815
+ return await generateApiKey(name, email);
3765
3816
  }
3766
3817
  };
3767
3818
  export {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@exulu/backend",
3
3
  "author": "Qventu Bv.",
4
- "version": "0.2.0",
4
+ "version": "0.2.2",
5
5
  "main": "./dist/index.js",
6
6
  "private": false,
7
7
  "publishConfig": {