@librechat/data-schemas 0.0.8 → 0.0.11
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 +957 -119
- package/dist/index.cjs.map +1 -1
- package/dist/index.es.js +957 -120
- package/dist/index.es.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/methods/index.d.ts +23 -1
- package/dist/types/methods/memory.d.ts +35 -0
- package/dist/types/methods/pluginAuth.d.ts +35 -0
- package/dist/types/methods/share.d.ts +38 -0
- package/dist/types/methods/share.test.d.ts +1 -0
- package/dist/types/methods/user.d.ts +1 -0
- package/dist/types/models/index.d.ts +1 -0
- package/dist/types/models/memory.d.ts +27 -0
- package/dist/types/models/plugins/mongoMeili.d.ts +47 -1
- package/dist/types/schema/defaults.d.ts +14 -1
- package/dist/types/schema/index.d.ts +1 -0
- package/dist/types/schema/memory.d.ts +29 -0
- package/dist/types/schema/pluginAuth.d.ts +2 -9
- package/dist/types/schema/preset.d.ts +4 -0
- package/dist/types/types/convo.d.ts +4 -0
- package/dist/types/types/index.d.ts +30 -0
- package/dist/types/types/memory.d.ts +63 -0
- package/dist/types/types/pluginAuth.d.ts +59 -0
- package/dist/types/types/share.d.ts +82 -0
- package/dist/types/types/user.d.ts +3 -0
- package/package.json +6 -4
- package/README.md +0 -114
package/dist/index.es.js
CHANGED
|
@@ -9,6 +9,7 @@ import winston from 'winston';
|
|
|
9
9
|
import 'winston-daily-rotate-file';
|
|
10
10
|
import { klona } from 'klona';
|
|
11
11
|
import traverse from 'traverse';
|
|
12
|
+
import { nanoid } from 'nanoid';
|
|
12
13
|
|
|
13
14
|
async function signPayload({ payload, secret, expirationTime, }) {
|
|
14
15
|
return jwt.sign(payload, secret, { expiresIn: expirationTime });
|
|
@@ -424,10 +425,23 @@ const conversationPreset = {
|
|
|
424
425
|
max_tokens: {
|
|
425
426
|
type: Number,
|
|
426
427
|
},
|
|
427
|
-
|
|
428
|
+
useResponsesApi: {
|
|
429
|
+
type: Boolean,
|
|
430
|
+
},
|
|
431
|
+
/** OpenAI Responses API / Anthropic API / Google API */
|
|
432
|
+
web_search: {
|
|
433
|
+
type: Boolean,
|
|
434
|
+
},
|
|
435
|
+
disableStreaming: {
|
|
436
|
+
type: Boolean,
|
|
437
|
+
},
|
|
438
|
+
/** Reasoning models only */
|
|
428
439
|
reasoning_effort: {
|
|
429
440
|
type: String,
|
|
430
441
|
},
|
|
442
|
+
reasoning_summary: {
|
|
443
|
+
type: String,
|
|
444
|
+
},
|
|
431
445
|
};
|
|
432
446
|
|
|
433
447
|
const convoSchema = new Schema({
|
|
@@ -868,7 +882,7 @@ const promptGroupSchema = new Schema({
|
|
|
868
882
|
validator: function (v) {
|
|
869
883
|
return v === undefined || v === null || v === '' || /^[a-z0-9-]+$/.test(v);
|
|
870
884
|
},
|
|
871
|
-
message: (props) => `${props.value} is not a valid command. Only lowercase alphanumeric characters and hyphens are allowed
|
|
885
|
+
message: (props) => { var _a; return `${(_a = props === null || props === void 0 ? void 0 : props.value) !== null && _a !== void 0 ? _a : 'Value'} is not a valid command. Only lowercase alphanumeric characters and hyphens are allowed.`; },
|
|
872
886
|
},
|
|
873
887
|
maxlength: [
|
|
874
888
|
Constants.COMMANDS_MAX_LENGTH,
|
|
@@ -880,7 +894,9 @@ const promptGroupSchema = new Schema({
|
|
|
880
894
|
});
|
|
881
895
|
promptGroupSchema.index({ createdAt: 1, updatedAt: 1 });
|
|
882
896
|
|
|
883
|
-
|
|
897
|
+
/**
|
|
898
|
+
* Uses a sub-schema for permissions. Notice we disable `_id` for this subdocument.
|
|
899
|
+
*/
|
|
884
900
|
const rolePermissionsSchema = new Schema({
|
|
885
901
|
[PermissionTypes.BOOKMARKS]: {
|
|
886
902
|
[Permissions.USE]: { type: Boolean, default: true },
|
|
@@ -890,6 +906,13 @@ const rolePermissionsSchema = new Schema({
|
|
|
890
906
|
[Permissions.USE]: { type: Boolean, default: true },
|
|
891
907
|
[Permissions.CREATE]: { type: Boolean, default: true },
|
|
892
908
|
},
|
|
909
|
+
[PermissionTypes.MEMORIES]: {
|
|
910
|
+
[Permissions.USE]: { type: Boolean, default: true },
|
|
911
|
+
[Permissions.CREATE]: { type: Boolean, default: true },
|
|
912
|
+
[Permissions.UPDATE]: { type: Boolean, default: true },
|
|
913
|
+
[Permissions.READ]: { type: Boolean, default: true },
|
|
914
|
+
[Permissions.OPT_OUT]: { type: Boolean, default: true },
|
|
915
|
+
},
|
|
893
916
|
[PermissionTypes.AGENTS]: {
|
|
894
917
|
[Permissions.SHARED_GLOBAL]: { type: Boolean, default: false },
|
|
895
918
|
[Permissions.USE]: { type: Boolean, default: true },
|
|
@@ -907,6 +930,9 @@ const rolePermissionsSchema = new Schema({
|
|
|
907
930
|
[PermissionTypes.WEB_SEARCH]: {
|
|
908
931
|
[Permissions.USE]: { type: Boolean, default: true },
|
|
909
932
|
},
|
|
933
|
+
[PermissionTypes.FILE_SEARCH]: {
|
|
934
|
+
[Permissions.USE]: { type: Boolean, default: true },
|
|
935
|
+
},
|
|
910
936
|
}, { _id: false });
|
|
911
937
|
const roleSchema = new Schema({
|
|
912
938
|
name: { type: String, required: true, unique: true, index: true },
|
|
@@ -919,6 +945,12 @@ const roleSchema = new Schema({
|
|
|
919
945
|
[Permissions.USE]: true,
|
|
920
946
|
[Permissions.CREATE]: true,
|
|
921
947
|
},
|
|
948
|
+
[PermissionTypes.MEMORIES]: {
|
|
949
|
+
[Permissions.USE]: true,
|
|
950
|
+
[Permissions.CREATE]: true,
|
|
951
|
+
[Permissions.UPDATE]: true,
|
|
952
|
+
[Permissions.READ]: true,
|
|
953
|
+
},
|
|
922
954
|
[PermissionTypes.AGENTS]: {
|
|
923
955
|
[Permissions.SHARED_GLOBAL]: false,
|
|
924
956
|
[Permissions.USE]: true,
|
|
@@ -928,6 +960,7 @@ const roleSchema = new Schema({
|
|
|
928
960
|
[PermissionTypes.TEMPORARY_CHAT]: { [Permissions.USE]: true },
|
|
929
961
|
[PermissionTypes.RUN_CODE]: { [Permissions.USE]: true },
|
|
930
962
|
[PermissionTypes.WEB_SEARCH]: { [Permissions.USE]: true },
|
|
963
|
+
[PermissionTypes.FILE_SEARCH]: { [Permissions.USE]: true },
|
|
931
964
|
}),
|
|
932
965
|
},
|
|
933
966
|
});
|
|
@@ -1196,8 +1229,46 @@ const userSchema = new Schema({
|
|
|
1196
1229
|
type: Boolean,
|
|
1197
1230
|
default: false,
|
|
1198
1231
|
},
|
|
1232
|
+
personalization: {
|
|
1233
|
+
type: {
|
|
1234
|
+
memories: {
|
|
1235
|
+
type: Boolean,
|
|
1236
|
+
default: true,
|
|
1237
|
+
},
|
|
1238
|
+
},
|
|
1239
|
+
default: {},
|
|
1240
|
+
},
|
|
1199
1241
|
}, { timestamps: true });
|
|
1200
1242
|
|
|
1243
|
+
const MemoryEntrySchema = new Schema({
|
|
1244
|
+
userId: {
|
|
1245
|
+
type: Schema.Types.ObjectId,
|
|
1246
|
+
ref: 'User',
|
|
1247
|
+
index: true,
|
|
1248
|
+
required: true,
|
|
1249
|
+
},
|
|
1250
|
+
key: {
|
|
1251
|
+
type: String,
|
|
1252
|
+
required: true,
|
|
1253
|
+
validate: {
|
|
1254
|
+
validator: (v) => /^[a-z_]+$/.test(v),
|
|
1255
|
+
message: 'Key must only contain lowercase letters and underscores',
|
|
1256
|
+
},
|
|
1257
|
+
},
|
|
1258
|
+
value: {
|
|
1259
|
+
type: String,
|
|
1260
|
+
required: true,
|
|
1261
|
+
},
|
|
1262
|
+
tokenCount: {
|
|
1263
|
+
type: Number,
|
|
1264
|
+
default: 0,
|
|
1265
|
+
},
|
|
1266
|
+
updated_at: {
|
|
1267
|
+
type: Date,
|
|
1268
|
+
default: Date.now,
|
|
1269
|
+
},
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1201
1272
|
/**
|
|
1202
1273
|
* Creates or returns the User model using the provided mongoose instance and schema
|
|
1203
1274
|
*/
|
|
@@ -1226,7 +1297,7 @@ function createBalanceModel(mongoose) {
|
|
|
1226
1297
|
return mongoose.models.Balance || mongoose.model('Balance', balanceSchema);
|
|
1227
1298
|
}
|
|
1228
1299
|
|
|
1229
|
-
const logDir$1 = path.join(__dirname, '..', 'logs');
|
|
1300
|
+
const logDir$1 = path.join(__dirname, '..', '..', '..', 'api', 'logs');
|
|
1230
1301
|
const { NODE_ENV: NODE_ENV$1, DEBUG_LOGGING: DEBUG_LOGGING$1 = 'false' } = process.env;
|
|
1231
1302
|
const useDebugLogging$1 = (typeof DEBUG_LOGGING$1 === 'string' && DEBUG_LOGGING$1.toLowerCase() === 'true') ||
|
|
1232
1303
|
DEBUG_LOGGING$1 === 'true';
|
|
@@ -1284,6 +1355,13 @@ const searchEnabled = process.env.SEARCH != null && process.env.SEARCH.toLowerCa
|
|
|
1284
1355
|
* Flag to indicate if MeiliSearch is enabled based on required environment variables.
|
|
1285
1356
|
*/
|
|
1286
1357
|
const meiliEnabled = process.env.MEILI_HOST != null && process.env.MEILI_MASTER_KEY != null && searchEnabled;
|
|
1358
|
+
/**
|
|
1359
|
+
* Get sync configuration from environment variables
|
|
1360
|
+
*/
|
|
1361
|
+
const getSyncConfig = () => ({
|
|
1362
|
+
batchSize: parseInt(process.env.MEILI_SYNC_BATCH_SIZE || '100', 10),
|
|
1363
|
+
delayMs: parseInt(process.env.MEILI_SYNC_DELAY_MS || '100', 10),
|
|
1364
|
+
});
|
|
1287
1365
|
/**
|
|
1288
1366
|
* Local implementation of parseTextParts to avoid dependency on librechat-data-provider
|
|
1289
1367
|
* Extracts text content from an array of content items
|
|
@@ -1315,6 +1393,19 @@ const validateOptions = (options) => {
|
|
|
1315
1393
|
}
|
|
1316
1394
|
});
|
|
1317
1395
|
};
|
|
1396
|
+
/**
|
|
1397
|
+
* Helper function to process documents in batches with rate limiting
|
|
1398
|
+
*/
|
|
1399
|
+
const processBatch = async (items, batchSize, delayMs, processor) => {
|
|
1400
|
+
for (let i = 0; i < items.length; i += batchSize) {
|
|
1401
|
+
const batch = items.slice(i, i + batchSize);
|
|
1402
|
+
await processor(batch);
|
|
1403
|
+
// Add delay between batches to prevent overwhelming resources
|
|
1404
|
+
if (i + batchSize < items.length && delayMs > 0) {
|
|
1405
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1318
1409
|
/**
|
|
1319
1410
|
* Factory function to create a MeiliMongooseModel class which extends a Mongoose model.
|
|
1320
1411
|
* This class contains static and instance methods to synchronize and manage the MeiliSearch index
|
|
@@ -1323,97 +1414,148 @@ const validateOptions = (options) => {
|
|
|
1323
1414
|
* @param config - Configuration object.
|
|
1324
1415
|
* @param config.index - The MeiliSearch index object.
|
|
1325
1416
|
* @param config.attributesToIndex - List of attributes to index.
|
|
1417
|
+
* @param config.syncOptions - Sync configuration options.
|
|
1326
1418
|
* @returns A class definition that will be loaded into the Mongoose schema.
|
|
1327
1419
|
*/
|
|
1328
|
-
const createMeiliMongooseModel = ({ index, attributesToIndex, }) => {
|
|
1420
|
+
const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) => {
|
|
1329
1421
|
const primaryKey = attributesToIndex[0];
|
|
1422
|
+
const syncConfig = { ...getSyncConfig(), ...syncOptions };
|
|
1330
1423
|
class MeiliMongooseModel {
|
|
1424
|
+
/**
|
|
1425
|
+
* Get the current sync progress
|
|
1426
|
+
*/
|
|
1427
|
+
static async getSyncProgress() {
|
|
1428
|
+
const totalDocuments = await this.countDocuments();
|
|
1429
|
+
const indexedDocuments = await this.countDocuments({ _meiliIndex: true });
|
|
1430
|
+
return {
|
|
1431
|
+
totalProcessed: indexedDocuments,
|
|
1432
|
+
totalDocuments,
|
|
1433
|
+
isComplete: indexedDocuments === totalDocuments,
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1331
1436
|
/**
|
|
1332
1437
|
* Synchronizes the data between the MongoDB collection and the MeiliSearch index.
|
|
1333
|
-
*
|
|
1334
|
-
* The synchronization process involves:
|
|
1335
|
-
* 1. Fetching all documents from the MongoDB collection and MeiliSearch index.
|
|
1336
|
-
* 2. Comparing documents from both sources.
|
|
1337
|
-
* 3. Deleting documents from MeiliSearch that no longer exist in MongoDB.
|
|
1338
|
-
* 4. Adding documents to MeiliSearch that exist in MongoDB but not in the index.
|
|
1339
|
-
* 5. Updating documents in MeiliSearch if key fields (such as `text` or `title`) differ.
|
|
1340
|
-
* 6. Updating the `_meiliIndex` field in MongoDB to indicate the indexing status.
|
|
1341
|
-
*
|
|
1342
|
-
* Note: The function processes documents in batches because MeiliSearch's
|
|
1343
|
-
* `index.getDocuments` requires an exact limit and `index.addDocuments` does not handle
|
|
1344
|
-
* partial failures in a batch.
|
|
1345
|
-
*
|
|
1346
|
-
* @returns {Promise<void>} Resolves when the synchronization is complete.
|
|
1438
|
+
* Now uses streaming and batching to reduce memory usage.
|
|
1347
1439
|
*/
|
|
1348
|
-
static async syncWithMeili() {
|
|
1440
|
+
static async syncWithMeili(options) {
|
|
1349
1441
|
try {
|
|
1350
|
-
|
|
1351
|
-
const
|
|
1442
|
+
const startTime = Date.now();
|
|
1443
|
+
const { batchSize, delayMs } = syncConfig;
|
|
1444
|
+
logger$1.info(`[syncWithMeili] Starting sync for ${primaryKey === 'messageId' ? 'messages' : 'conversations'} with batch size ${batchSize}`);
|
|
1445
|
+
// Build query with resume capability
|
|
1446
|
+
const query = {};
|
|
1447
|
+
if (options === null || options === void 0 ? void 0 : options.resumeFromId) {
|
|
1448
|
+
query._id = { $gt: options.resumeFromId };
|
|
1449
|
+
}
|
|
1450
|
+
// Get total count for progress tracking
|
|
1451
|
+
const totalCount = await this.countDocuments(query);
|
|
1452
|
+
let processedCount = 0;
|
|
1453
|
+
// First, handle documents that need to be removed from Meili
|
|
1454
|
+
await this.cleanupMeiliIndex(index, primaryKey, batchSize, delayMs);
|
|
1455
|
+
// Process MongoDB documents in batches using cursor
|
|
1456
|
+
const cursor = this.find(query)
|
|
1457
|
+
.select(attributesToIndex.join(' ') + ' _meiliIndex')
|
|
1458
|
+
.sort({ _id: 1 })
|
|
1459
|
+
.batchSize(batchSize)
|
|
1460
|
+
.cursor();
|
|
1352
1461
|
const format = (doc) => _.omitBy(_.pick(doc, attributesToIndex), (v, k) => k.startsWith('$'));
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1462
|
+
let documentBatch = [];
|
|
1463
|
+
let updateOps = [];
|
|
1464
|
+
// Process documents in streaming fashion
|
|
1465
|
+
for await (const doc of cursor) {
|
|
1466
|
+
const typedDoc = doc.toObject();
|
|
1467
|
+
const formatted = format(typedDoc);
|
|
1468
|
+
// Check if document needs indexing
|
|
1469
|
+
if (!typedDoc._meiliIndex) {
|
|
1470
|
+
documentBatch.push(formatted);
|
|
1471
|
+
updateOps.push({
|
|
1472
|
+
updateOne: {
|
|
1473
|
+
filter: { _id: typedDoc._id },
|
|
1474
|
+
update: { $set: { _meiliIndex: true } },
|
|
1475
|
+
},
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
processedCount++;
|
|
1479
|
+
// Process batch when it reaches the configured size
|
|
1480
|
+
if (documentBatch.length >= batchSize) {
|
|
1481
|
+
await this.processSyncBatch(index, documentBatch, updateOps);
|
|
1482
|
+
documentBatch = [];
|
|
1483
|
+
updateOps = [];
|
|
1484
|
+
// Log progress
|
|
1485
|
+
const progress = Math.round((processedCount / totalCount) * 100);
|
|
1486
|
+
logger$1.info(`[syncWithMeili] Progress: ${progress}% (${processedCount}/${totalCount})`);
|
|
1487
|
+
// Add delay to prevent overwhelming resources
|
|
1488
|
+
if (delayMs > 0) {
|
|
1489
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
// Process remaining documents
|
|
1494
|
+
if (documentBatch.length > 0) {
|
|
1495
|
+
await this.processSyncBatch(index, documentBatch, updateOps);
|
|
1496
|
+
}
|
|
1497
|
+
const duration = Date.now() - startTime;
|
|
1498
|
+
logger$1.info(`[syncWithMeili] Completed sync for ${primaryKey === 'messageId' ? 'messages' : 'conversations'} in ${duration}ms`);
|
|
1499
|
+
}
|
|
1500
|
+
catch (error) {
|
|
1501
|
+
logger$1.error('[syncWithMeili] Error during sync:', error);
|
|
1502
|
+
throw error;
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
/**
|
|
1506
|
+
* Process a batch of documents for syncing
|
|
1507
|
+
*/
|
|
1508
|
+
static async processSyncBatch(index, documents, updateOps) {
|
|
1509
|
+
if (documents.length === 0) {
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1512
|
+
try {
|
|
1513
|
+
// Add documents to MeiliSearch
|
|
1514
|
+
await index.addDocuments(documents);
|
|
1515
|
+
// Update MongoDB to mark documents as indexed
|
|
1516
|
+
if (updateOps.length > 0) {
|
|
1517
|
+
await this.collection.bulkWrite(updateOps);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
catch (error) {
|
|
1521
|
+
logger$1.error('[processSyncBatch] Error processing batch:', error);
|
|
1522
|
+
// Don't throw - allow sync to continue with other documents
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
/**
|
|
1526
|
+
* Clean up documents in MeiliSearch that no longer exist in MongoDB
|
|
1527
|
+
*/
|
|
1528
|
+
static async cleanupMeiliIndex(index, primaryKey, batchSize, delayMs) {
|
|
1529
|
+
try {
|
|
1358
1530
|
let offset = 0;
|
|
1359
|
-
|
|
1531
|
+
let moreDocuments = true;
|
|
1360
1532
|
while (moreDocuments) {
|
|
1361
1533
|
const batch = await index.getDocuments({ limit: batchSize, offset });
|
|
1362
1534
|
if (batch.results.length === 0) {
|
|
1363
1535
|
moreDocuments = false;
|
|
1536
|
+
break;
|
|
1364
1537
|
}
|
|
1365
|
-
|
|
1366
|
-
|
|
1538
|
+
const meiliIds = batch.results.map((doc) => doc[primaryKey]);
|
|
1539
|
+
const query = {};
|
|
1540
|
+
query[primaryKey] = { $in: meiliIds };
|
|
1541
|
+
// Find which documents exist in MongoDB
|
|
1542
|
+
const existingDocs = await this.find(query).select(primaryKey).lean();
|
|
1543
|
+
const existingIds = new Set(existingDocs.map((doc) => doc[primaryKey]));
|
|
1544
|
+
// Delete documents that don't exist in MongoDB
|
|
1545
|
+
const toDelete = meiliIds.filter((id) => !existingIds.has(id));
|
|
1546
|
+
if (toDelete.length > 0) {
|
|
1547
|
+
await Promise.all(toDelete.map((id) => index.deleteDocument(id)));
|
|
1548
|
+
logger$1.debug(`[cleanupMeiliIndex] Deleted ${toDelete.length} orphaned documents`);
|
|
1367
1549
|
}
|
|
1368
1550
|
offset += batchSize;
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
// Process documents present in the MeiliSearch index
|
|
1373
|
-
for (const [id, doc] of indexMap) {
|
|
1374
|
-
const update = {};
|
|
1375
|
-
update[primaryKey] = id;
|
|
1376
|
-
if (mongoMap.has(id)) {
|
|
1377
|
-
const mongoDoc = mongoMap.get(id);
|
|
1378
|
-
if ((doc.text && doc.text !== (mongoDoc === null || mongoDoc === void 0 ? void 0 : mongoDoc.text)) ||
|
|
1379
|
-
(doc.title && doc.title !== (mongoDoc === null || mongoDoc === void 0 ? void 0 : mongoDoc.title))) {
|
|
1380
|
-
logger$1.debug(`[syncWithMeili] ${id} had document discrepancy in ${doc.text ? 'text' : 'title'} field`);
|
|
1381
|
-
updateOps.push({
|
|
1382
|
-
updateOne: { filter: update, update: { $set: { _meiliIndex: true } } },
|
|
1383
|
-
});
|
|
1384
|
-
await index.addDocuments([doc]);
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
|
-
else {
|
|
1388
|
-
await index.deleteDocument(id);
|
|
1389
|
-
updateOps.push({
|
|
1390
|
-
updateOne: { filter: update, update: { $set: { _meiliIndex: false } } },
|
|
1391
|
-
});
|
|
1392
|
-
}
|
|
1393
|
-
}
|
|
1394
|
-
// Process documents present in MongoDB
|
|
1395
|
-
for (const [id, doc] of mongoMap) {
|
|
1396
|
-
const update = {};
|
|
1397
|
-
update[primaryKey] = id;
|
|
1398
|
-
if (!indexMap.has(id)) {
|
|
1399
|
-
await index.addDocuments([doc]);
|
|
1400
|
-
updateOps.push({
|
|
1401
|
-
updateOne: { filter: update, update: { $set: { _meiliIndex: true } } },
|
|
1402
|
-
});
|
|
1403
|
-
}
|
|
1404
|
-
else if (doc._meiliIndex === false) {
|
|
1405
|
-
updateOps.push({
|
|
1406
|
-
updateOne: { filter: update, update: { $set: { _meiliIndex: true } } },
|
|
1407
|
-
});
|
|
1551
|
+
// Add delay between batches
|
|
1552
|
+
if (delayMs > 0) {
|
|
1553
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1408
1554
|
}
|
|
1409
1555
|
}
|
|
1410
|
-
if (updateOps.length > 0) {
|
|
1411
|
-
await this.collection.bulkWrite(updateOps);
|
|
1412
|
-
logger$1.debug(`[syncWithMeili] Finished indexing ${primaryKey === 'messageId' ? 'messages' : 'conversations'}`);
|
|
1413
|
-
}
|
|
1414
1556
|
}
|
|
1415
1557
|
catch (error) {
|
|
1416
|
-
logger$1.error('[
|
|
1558
|
+
logger$1.error('[cleanupMeiliIndex] Error during cleanup:', error);
|
|
1417
1559
|
}
|
|
1418
1560
|
}
|
|
1419
1561
|
/**
|
|
@@ -1469,32 +1611,64 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, }) => {
|
|
|
1469
1611
|
return object;
|
|
1470
1612
|
}
|
|
1471
1613
|
/**
|
|
1472
|
-
* Adds the current document to the MeiliSearch index
|
|
1614
|
+
* Adds the current document to the MeiliSearch index with retry logic
|
|
1473
1615
|
*/
|
|
1474
|
-
async addObjectToMeili() {
|
|
1616
|
+
async addObjectToMeili(next) {
|
|
1475
1617
|
const object = this.preprocessObjectForIndex();
|
|
1618
|
+
const maxRetries = 3;
|
|
1619
|
+
let retryCount = 0;
|
|
1620
|
+
while (retryCount < maxRetries) {
|
|
1621
|
+
try {
|
|
1622
|
+
await index.addDocuments([object]);
|
|
1623
|
+
break;
|
|
1624
|
+
}
|
|
1625
|
+
catch (error) {
|
|
1626
|
+
retryCount++;
|
|
1627
|
+
if (retryCount >= maxRetries) {
|
|
1628
|
+
logger$1.error('[addObjectToMeili] Error adding document to Meili after retries:', error);
|
|
1629
|
+
return next();
|
|
1630
|
+
}
|
|
1631
|
+
// Exponential backoff
|
|
1632
|
+
await new Promise((resolve) => setTimeout(resolve, Math.pow(2, retryCount) * 1000));
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1476
1635
|
try {
|
|
1477
|
-
await
|
|
1636
|
+
await this.collection.updateMany({ _id: this._id }, { $set: { _meiliIndex: true } });
|
|
1478
1637
|
}
|
|
1479
1638
|
catch (error) {
|
|
1480
|
-
logger$1.error('[addObjectToMeili] Error
|
|
1639
|
+
logger$1.error('[addObjectToMeili] Error updating _meiliIndex field:', error);
|
|
1640
|
+
return next();
|
|
1481
1641
|
}
|
|
1482
|
-
|
|
1642
|
+
next();
|
|
1483
1643
|
}
|
|
1484
1644
|
/**
|
|
1485
1645
|
* Updates the current document in the MeiliSearch index
|
|
1486
1646
|
*/
|
|
1487
|
-
async updateObjectToMeili() {
|
|
1488
|
-
|
|
1489
|
-
|
|
1647
|
+
async updateObjectToMeili(next) {
|
|
1648
|
+
try {
|
|
1649
|
+
const object = _.omitBy(_.pick(this.toJSON(), attributesToIndex), (v, k) => k.startsWith('$'));
|
|
1650
|
+
await index.updateDocuments([object]);
|
|
1651
|
+
next();
|
|
1652
|
+
}
|
|
1653
|
+
catch (error) {
|
|
1654
|
+
logger$1.error('[updateObjectToMeili] Error updating document in Meili:', error);
|
|
1655
|
+
return next();
|
|
1656
|
+
}
|
|
1490
1657
|
}
|
|
1491
1658
|
/**
|
|
1492
1659
|
* Deletes the current document from the MeiliSearch index.
|
|
1493
1660
|
*
|
|
1494
1661
|
* @returns {Promise<void>}
|
|
1495
1662
|
*/
|
|
1496
|
-
async deleteObjectFromMeili() {
|
|
1497
|
-
|
|
1663
|
+
async deleteObjectFromMeili(next) {
|
|
1664
|
+
try {
|
|
1665
|
+
await index.deleteDocument(this._id);
|
|
1666
|
+
next();
|
|
1667
|
+
}
|
|
1668
|
+
catch (error) {
|
|
1669
|
+
logger$1.error('[deleteObjectFromMeili] Error deleting document from Meili:', error);
|
|
1670
|
+
return next();
|
|
1671
|
+
}
|
|
1498
1672
|
}
|
|
1499
1673
|
/**
|
|
1500
1674
|
* Post-save hook to synchronize the document with MeiliSearch.
|
|
@@ -1502,12 +1676,12 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, }) => {
|
|
|
1502
1676
|
* If the document is already indexed (i.e. `_meiliIndex` is true), it updates it;
|
|
1503
1677
|
* otherwise, it adds the document to the index.
|
|
1504
1678
|
*/
|
|
1505
|
-
postSaveHook() {
|
|
1679
|
+
postSaveHook(next) {
|
|
1506
1680
|
if (this._meiliIndex) {
|
|
1507
|
-
this.updateObjectToMeili();
|
|
1681
|
+
this.updateObjectToMeili(next);
|
|
1508
1682
|
}
|
|
1509
1683
|
else {
|
|
1510
|
-
this.addObjectToMeili();
|
|
1684
|
+
this.addObjectToMeili(next);
|
|
1511
1685
|
}
|
|
1512
1686
|
}
|
|
1513
1687
|
/**
|
|
@@ -1516,9 +1690,12 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, }) => {
|
|
|
1516
1690
|
* This hook is triggered after a document update, ensuring that changes are
|
|
1517
1691
|
* propagated to the MeiliSearch index if the document is indexed.
|
|
1518
1692
|
*/
|
|
1519
|
-
postUpdateHook() {
|
|
1693
|
+
postUpdateHook(next) {
|
|
1520
1694
|
if (this._meiliIndex) {
|
|
1521
|
-
this.updateObjectToMeili();
|
|
1695
|
+
this.updateObjectToMeili(next);
|
|
1696
|
+
}
|
|
1697
|
+
else {
|
|
1698
|
+
next();
|
|
1522
1699
|
}
|
|
1523
1700
|
}
|
|
1524
1701
|
/**
|
|
@@ -1527,9 +1704,12 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, }) => {
|
|
|
1527
1704
|
* This hook is triggered after a document is removed, ensuring that the document
|
|
1528
1705
|
* is also removed from the MeiliSearch index if it was previously indexed.
|
|
1529
1706
|
*/
|
|
1530
|
-
postRemoveHook() {
|
|
1707
|
+
postRemoveHook(next) {
|
|
1531
1708
|
if (this._meiliIndex) {
|
|
1532
|
-
this.deleteObjectFromMeili();
|
|
1709
|
+
this.deleteObjectFromMeili(next);
|
|
1710
|
+
}
|
|
1711
|
+
else {
|
|
1712
|
+
next();
|
|
1533
1713
|
}
|
|
1534
1714
|
}
|
|
1535
1715
|
}
|
|
@@ -1551,6 +1731,8 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, }) => {
|
|
|
1551
1731
|
* @param options.apiKey - The MeiliSearch API key.
|
|
1552
1732
|
* @param options.indexName - The name of the MeiliSearch index.
|
|
1553
1733
|
* @param options.primaryKey - The primary key field for indexing.
|
|
1734
|
+
* @param options.syncBatchSize - Batch size for sync operations.
|
|
1735
|
+
* @param options.syncDelayMs - Delay between batches in milliseconds.
|
|
1554
1736
|
*/
|
|
1555
1737
|
function mongoMeili(schema, options) {
|
|
1556
1738
|
const mongoose = options.mongoose;
|
|
@@ -1565,9 +1747,37 @@ function mongoMeili(schema, options) {
|
|
|
1565
1747
|
},
|
|
1566
1748
|
});
|
|
1567
1749
|
const { host, apiKey, indexName, primaryKey } = options;
|
|
1750
|
+
const syncOptions = {
|
|
1751
|
+
batchSize: options.syncBatchSize || getSyncConfig().batchSize,
|
|
1752
|
+
delayMs: options.syncDelayMs || getSyncConfig().delayMs,
|
|
1753
|
+
};
|
|
1568
1754
|
const client = new MeiliSearch({ host, apiKey });
|
|
1569
|
-
|
|
1755
|
+
/** Create index only if it doesn't exist */
|
|
1570
1756
|
const index = client.index(indexName);
|
|
1757
|
+
// Check if index exists and create if needed
|
|
1758
|
+
(async () => {
|
|
1759
|
+
try {
|
|
1760
|
+
await index.getRawInfo();
|
|
1761
|
+
logger$1.debug(`[mongoMeili] Index ${indexName} already exists`);
|
|
1762
|
+
}
|
|
1763
|
+
catch (error) {
|
|
1764
|
+
const errorCode = error === null || error === void 0 ? void 0 : error.code;
|
|
1765
|
+
if (errorCode === 'index_not_found') {
|
|
1766
|
+
try {
|
|
1767
|
+
logger$1.info(`[mongoMeili] Creating new index: ${indexName}`);
|
|
1768
|
+
await client.createIndex(indexName, { primaryKey });
|
|
1769
|
+
logger$1.info(`[mongoMeili] Successfully created index: ${indexName}`);
|
|
1770
|
+
}
|
|
1771
|
+
catch (createError) {
|
|
1772
|
+
// Index might have been created by another instance
|
|
1773
|
+
logger$1.debug(`[mongoMeili] Index ${indexName} may already exist:`, createError);
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
else {
|
|
1777
|
+
logger$1.error(`[mongoMeili] Error checking index ${indexName}:`, error);
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
})();
|
|
1571
1781
|
// Collect attributes from the schema that should be indexed
|
|
1572
1782
|
const attributesToIndex = [
|
|
1573
1783
|
...Object.entries(schema.obj).reduce((results, [key, value]) => {
|
|
@@ -1575,19 +1785,19 @@ function mongoMeili(schema, options) {
|
|
|
1575
1785
|
return schemaValue.meiliIndex ? [...results, key] : results;
|
|
1576
1786
|
}, []),
|
|
1577
1787
|
];
|
|
1578
|
-
schema.loadClass(createMeiliMongooseModel({ index, attributesToIndex }));
|
|
1788
|
+
schema.loadClass(createMeiliMongooseModel({ index, attributesToIndex, syncOptions }));
|
|
1579
1789
|
// Register Mongoose hooks
|
|
1580
|
-
schema.post('save', function (doc) {
|
|
1790
|
+
schema.post('save', function (doc, next) {
|
|
1581
1791
|
var _a;
|
|
1582
|
-
(_a = doc.postSaveHook) === null || _a === void 0 ? void 0 : _a.call(doc);
|
|
1792
|
+
(_a = doc.postSaveHook) === null || _a === void 0 ? void 0 : _a.call(doc, next);
|
|
1583
1793
|
});
|
|
1584
|
-
schema.post('updateOne', function (doc) {
|
|
1794
|
+
schema.post('updateOne', function (doc, next) {
|
|
1585
1795
|
var _a;
|
|
1586
|
-
(_a = doc.postUpdateHook) === null || _a === void 0 ? void 0 : _a.call(doc);
|
|
1796
|
+
(_a = doc.postUpdateHook) === null || _a === void 0 ? void 0 : _a.call(doc, next);
|
|
1587
1797
|
});
|
|
1588
|
-
schema.post('deleteOne', function (doc) {
|
|
1798
|
+
schema.post('deleteOne', function (doc, next) {
|
|
1589
1799
|
var _a;
|
|
1590
|
-
(_a = doc.postRemoveHook) === null || _a === void 0 ? void 0 : _a.call(doc);
|
|
1800
|
+
(_a = doc.postRemoveHook) === null || _a === void 0 ? void 0 : _a.call(doc, next);
|
|
1591
1801
|
});
|
|
1592
1802
|
// Pre-deleteMany hook: remove corresponding documents from MeiliSearch when multiple documents are deleted.
|
|
1593
1803
|
schema.pre('deleteMany', async function (next) {
|
|
@@ -1596,41 +1806,50 @@ function mongoMeili(schema, options) {
|
|
|
1596
1806
|
}
|
|
1597
1807
|
try {
|
|
1598
1808
|
const conditions = this.getQuery();
|
|
1809
|
+
const { batchSize, delayMs } = syncOptions;
|
|
1599
1810
|
if (Object.prototype.hasOwnProperty.call(schema.obj, 'messages')) {
|
|
1600
1811
|
const convoIndex = client.index('convos');
|
|
1601
1812
|
const deletedConvos = await mongoose
|
|
1602
1813
|
.model('Conversation')
|
|
1603
1814
|
.find(conditions)
|
|
1815
|
+
.select('conversationId')
|
|
1604
1816
|
.lean();
|
|
1605
|
-
|
|
1606
|
-
await
|
|
1817
|
+
// Process deletions in batches
|
|
1818
|
+
await processBatch(deletedConvos, batchSize, delayMs, async (batch) => {
|
|
1819
|
+
const promises = batch.map((convo) => convoIndex.deleteDocument(convo.conversationId));
|
|
1820
|
+
await Promise.all(promises);
|
|
1821
|
+
});
|
|
1607
1822
|
}
|
|
1608
1823
|
if (Object.prototype.hasOwnProperty.call(schema.obj, 'messageId')) {
|
|
1609
1824
|
const messageIndex = client.index('messages');
|
|
1610
1825
|
const deletedMessages = await mongoose
|
|
1611
1826
|
.model('Message')
|
|
1612
1827
|
.find(conditions)
|
|
1828
|
+
.select('messageId')
|
|
1613
1829
|
.lean();
|
|
1614
|
-
|
|
1615
|
-
await
|
|
1830
|
+
// Process deletions in batches
|
|
1831
|
+
await processBatch(deletedMessages, batchSize, delayMs, async (batch) => {
|
|
1832
|
+
const promises = batch.map((message) => messageIndex.deleteDocument(message.messageId));
|
|
1833
|
+
await Promise.all(promises);
|
|
1834
|
+
});
|
|
1616
1835
|
}
|
|
1617
1836
|
return next();
|
|
1618
1837
|
}
|
|
1619
1838
|
catch (error) {
|
|
1620
1839
|
if (meiliEnabled) {
|
|
1621
|
-
logger$1.error('[MeiliMongooseModel.deleteMany] There was an issue deleting conversation indexes upon deletion. Next startup may
|
|
1840
|
+
logger$1.error('[MeiliMongooseModel.deleteMany] There was an issue deleting conversation indexes upon deletion. Next startup may trigger syncing.', error);
|
|
1622
1841
|
}
|
|
1623
1842
|
return next();
|
|
1624
1843
|
}
|
|
1625
1844
|
});
|
|
1626
1845
|
// Post-findOneAndUpdate hook
|
|
1627
|
-
schema.post('findOneAndUpdate', async function (doc) {
|
|
1846
|
+
schema.post('findOneAndUpdate', async function (doc, next) {
|
|
1628
1847
|
var _a;
|
|
1629
1848
|
if (!meiliEnabled) {
|
|
1630
|
-
return;
|
|
1849
|
+
return next();
|
|
1631
1850
|
}
|
|
1632
1851
|
if (doc.unfinished) {
|
|
1633
|
-
return;
|
|
1852
|
+
return next();
|
|
1634
1853
|
}
|
|
1635
1854
|
let meiliDoc;
|
|
1636
1855
|
if (doc.messages) {
|
|
@@ -1643,9 +1862,9 @@ function mongoMeili(schema, options) {
|
|
|
1643
1862
|
}
|
|
1644
1863
|
}
|
|
1645
1864
|
if (meiliDoc && meiliDoc.title === doc.title) {
|
|
1646
|
-
return;
|
|
1865
|
+
return next();
|
|
1647
1866
|
}
|
|
1648
|
-
(_a = doc.postSaveHook) === null || _a === void 0 ? void 0 : _a.call(doc);
|
|
1867
|
+
(_a = doc.postSaveHook) === null || _a === void 0 ? void 0 : _a.call(doc, next);
|
|
1649
1868
|
});
|
|
1650
1869
|
}
|
|
1651
1870
|
|
|
@@ -1796,6 +2015,10 @@ function createToolCallModel(mongoose) {
|
|
|
1796
2015
|
return mongoose.models.ToolCall || mongoose.model('ToolCall', toolCallSchema);
|
|
1797
2016
|
}
|
|
1798
2017
|
|
|
2018
|
+
function createMemoryModel(mongoose) {
|
|
2019
|
+
return mongoose.models.MemoryEntry || mongoose.model('MemoryEntry', MemoryEntrySchema);
|
|
2020
|
+
}
|
|
2021
|
+
|
|
1799
2022
|
/**
|
|
1800
2023
|
* Creates all database models for all collections
|
|
1801
2024
|
*/
|
|
@@ -1823,6 +2046,7 @@ function createModels(mongoose) {
|
|
|
1823
2046
|
ConversationTag: createConversationTagModel(mongoose),
|
|
1824
2047
|
SharedLink: createSharedLinkModel(mongoose),
|
|
1825
2048
|
ToolCall: createToolCallModel(mongoose),
|
|
2049
|
+
MemoryEntry: createMemoryModel(mongoose),
|
|
1826
2050
|
};
|
|
1827
2051
|
}
|
|
1828
2052
|
|
|
@@ -1958,6 +2182,28 @@ function createUserMethods(mongoose) {
|
|
|
1958
2182
|
expirationTime: expires / 1000,
|
|
1959
2183
|
});
|
|
1960
2184
|
}
|
|
2185
|
+
/**
|
|
2186
|
+
* Update a user's personalization memories setting.
|
|
2187
|
+
* Handles the edge case where the personalization object doesn't exist.
|
|
2188
|
+
*/
|
|
2189
|
+
async function toggleUserMemories(userId, memoriesEnabled) {
|
|
2190
|
+
const User = mongoose.models.User;
|
|
2191
|
+
// First, ensure the personalization object exists
|
|
2192
|
+
const user = await User.findById(userId);
|
|
2193
|
+
if (!user) {
|
|
2194
|
+
return null;
|
|
2195
|
+
}
|
|
2196
|
+
// Use $set to update the nested field, which will create the personalization object if it doesn't exist
|
|
2197
|
+
const updateOperation = {
|
|
2198
|
+
$set: {
|
|
2199
|
+
'personalization.memories': memoriesEnabled,
|
|
2200
|
+
},
|
|
2201
|
+
};
|
|
2202
|
+
return (await User.findByIdAndUpdate(userId, updateOperation, {
|
|
2203
|
+
new: true,
|
|
2204
|
+
runValidators: true,
|
|
2205
|
+
}).lean());
|
|
2206
|
+
}
|
|
1961
2207
|
// Return all methods
|
|
1962
2208
|
return {
|
|
1963
2209
|
findUser,
|
|
@@ -1967,6 +2213,7 @@ function createUserMethods(mongoose) {
|
|
|
1967
2213
|
getUserById,
|
|
1968
2214
|
deleteUserById,
|
|
1969
2215
|
generateToken,
|
|
2216
|
+
toggleUserMemories,
|
|
1970
2217
|
};
|
|
1971
2218
|
}
|
|
1972
2219
|
|
|
@@ -2171,14 +2418,11 @@ const jsonTruncateFormat = winston.format((info) => {
|
|
|
2171
2418
|
return truncateObject(info);
|
|
2172
2419
|
});
|
|
2173
2420
|
|
|
2174
|
-
|
|
2175
|
-
const logDir = path.join(__dirname, '..', 'logs');
|
|
2176
|
-
// Type-safe environment variables
|
|
2421
|
+
const logDir = path.join(__dirname, '..', '..', '..', 'api', 'logs');
|
|
2177
2422
|
const { NODE_ENV, DEBUG_LOGGING, CONSOLE_JSON, DEBUG_CONSOLE } = process.env;
|
|
2178
2423
|
const useConsoleJson = typeof CONSOLE_JSON === 'string' && CONSOLE_JSON.toLowerCase() === 'true';
|
|
2179
2424
|
const useDebugConsole = typeof DEBUG_CONSOLE === 'string' && DEBUG_CONSOLE.toLowerCase() === 'true';
|
|
2180
2425
|
const useDebugLogging = typeof DEBUG_LOGGING === 'string' && DEBUG_LOGGING.toLowerCase() === 'true';
|
|
2181
|
-
// Define custom log levels
|
|
2182
2426
|
const levels = {
|
|
2183
2427
|
error: 0,
|
|
2184
2428
|
warn: 1,
|
|
@@ -2258,7 +2502,7 @@ const logger = winston.createLogger({
|
|
|
2258
2502
|
transports,
|
|
2259
2503
|
});
|
|
2260
2504
|
|
|
2261
|
-
var _a
|
|
2505
|
+
var _a;
|
|
2262
2506
|
class SessionError extends Error {
|
|
2263
2507
|
constructor(message, code = 'SESSION_ERROR') {
|
|
2264
2508
|
super(message);
|
|
@@ -2267,10 +2511,9 @@ class SessionError extends Error {
|
|
|
2267
2511
|
}
|
|
2268
2512
|
}
|
|
2269
2513
|
const { REFRESH_TOKEN_EXPIRY } = (_a = process.env) !== null && _a !== void 0 ? _a : {};
|
|
2270
|
-
const expires =
|
|
2514
|
+
const expires = REFRESH_TOKEN_EXPIRY ? eval(REFRESH_TOKEN_EXPIRY) : 1000 * 60 * 60 * 24 * 7; // 7 days default
|
|
2271
2515
|
// Factory function that takes mongoose instance and returns the methods
|
|
2272
2516
|
function createSessionMethods(mongoose) {
|
|
2273
|
-
const Session = mongoose.models.Session;
|
|
2274
2517
|
/**
|
|
2275
2518
|
* Creates a new session for a user
|
|
2276
2519
|
*/
|
|
@@ -2279,12 +2522,13 @@ function createSessionMethods(mongoose) {
|
|
|
2279
2522
|
throw new SessionError('User ID is required', 'INVALID_USER_ID');
|
|
2280
2523
|
}
|
|
2281
2524
|
try {
|
|
2282
|
-
const
|
|
2525
|
+
const Session = mongoose.models.Session;
|
|
2526
|
+
const currentSession = new Session({
|
|
2283
2527
|
user: userId,
|
|
2284
2528
|
expiration: options.expiration || new Date(Date.now() + expires),
|
|
2285
2529
|
});
|
|
2286
|
-
const refreshToken = await generateRefreshToken(
|
|
2287
|
-
return { session, refreshToken };
|
|
2530
|
+
const refreshToken = await generateRefreshToken(currentSession);
|
|
2531
|
+
return { session: currentSession, refreshToken };
|
|
2288
2532
|
}
|
|
2289
2533
|
catch (error) {
|
|
2290
2534
|
logger.error('[createSession] Error creating session:', error);
|
|
@@ -2296,6 +2540,7 @@ function createSessionMethods(mongoose) {
|
|
|
2296
2540
|
*/
|
|
2297
2541
|
async function findSession(params, options = { lean: true }) {
|
|
2298
2542
|
try {
|
|
2543
|
+
const Session = mongoose.models.Session;
|
|
2299
2544
|
const query = {};
|
|
2300
2545
|
if (!params.refreshToken && !params.userId && !params.sessionId) {
|
|
2301
2546
|
throw new SessionError('At least one search parameter is required', 'INVALID_SEARCH_PARAMS');
|
|
@@ -2336,6 +2581,7 @@ function createSessionMethods(mongoose) {
|
|
|
2336
2581
|
*/
|
|
2337
2582
|
async function updateExpiration(session, newExpiration) {
|
|
2338
2583
|
try {
|
|
2584
|
+
const Session = mongoose.models.Session;
|
|
2339
2585
|
const sessionDoc = typeof session === 'string' ? await Session.findById(session) : session;
|
|
2340
2586
|
if (!sessionDoc) {
|
|
2341
2587
|
throw new SessionError('Session not found', 'SESSION_NOT_FOUND');
|
|
@@ -2353,6 +2599,7 @@ function createSessionMethods(mongoose) {
|
|
|
2353
2599
|
*/
|
|
2354
2600
|
async function deleteSession(params) {
|
|
2355
2601
|
try {
|
|
2602
|
+
const Session = mongoose.models.Session;
|
|
2356
2603
|
if (!params.refreshToken && !params.sessionId) {
|
|
2357
2604
|
throw new SessionError('Either refreshToken or sessionId is required', 'INVALID_DELETE_PARAMS');
|
|
2358
2605
|
}
|
|
@@ -2379,6 +2626,7 @@ function createSessionMethods(mongoose) {
|
|
|
2379
2626
|
*/
|
|
2380
2627
|
async function deleteAllUserSessions(userId, options = {}) {
|
|
2381
2628
|
try {
|
|
2629
|
+
const Session = mongoose.models.Session;
|
|
2382
2630
|
if (!userId) {
|
|
2383
2631
|
throw new SessionError('User ID is required', 'INVALID_USER_ID');
|
|
2384
2632
|
}
|
|
@@ -2435,6 +2683,7 @@ function createSessionMethods(mongoose) {
|
|
|
2435
2683
|
*/
|
|
2436
2684
|
async function countActiveSessions(userId) {
|
|
2437
2685
|
try {
|
|
2686
|
+
const Session = mongoose.models.Session;
|
|
2438
2687
|
if (!userId) {
|
|
2439
2688
|
throw new SessionError('User ID is required', 'INVALID_USER_ID');
|
|
2440
2689
|
}
|
|
@@ -2597,6 +2846,591 @@ function createRoleMethods(mongoose) {
|
|
|
2597
2846
|
};
|
|
2598
2847
|
}
|
|
2599
2848
|
|
|
2849
|
+
/**
|
|
2850
|
+
* Formats a date in YYYY-MM-DD format
|
|
2851
|
+
*/
|
|
2852
|
+
const formatDate = (date) => {
|
|
2853
|
+
return date.toISOString().split('T')[0];
|
|
2854
|
+
};
|
|
2855
|
+
// Factory function that takes mongoose instance and returns the methods
|
|
2856
|
+
function createMemoryMethods(mongoose) {
|
|
2857
|
+
/**
|
|
2858
|
+
* Creates a new memory entry for a user
|
|
2859
|
+
* Throws an error if a memory with the same key already exists
|
|
2860
|
+
*/
|
|
2861
|
+
async function createMemory({ userId, key, value, tokenCount = 0, }) {
|
|
2862
|
+
try {
|
|
2863
|
+
if ((key === null || key === void 0 ? void 0 : key.toLowerCase()) === 'nothing') {
|
|
2864
|
+
return { ok: false };
|
|
2865
|
+
}
|
|
2866
|
+
const MemoryEntry = mongoose.models.MemoryEntry;
|
|
2867
|
+
const existingMemory = await MemoryEntry.findOne({ userId, key });
|
|
2868
|
+
if (existingMemory) {
|
|
2869
|
+
throw new Error('Memory with this key already exists');
|
|
2870
|
+
}
|
|
2871
|
+
await MemoryEntry.create({
|
|
2872
|
+
userId,
|
|
2873
|
+
key,
|
|
2874
|
+
value,
|
|
2875
|
+
tokenCount,
|
|
2876
|
+
updated_at: new Date(),
|
|
2877
|
+
});
|
|
2878
|
+
return { ok: true };
|
|
2879
|
+
}
|
|
2880
|
+
catch (error) {
|
|
2881
|
+
throw new Error(`Failed to create memory: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2884
|
+
/**
|
|
2885
|
+
* Sets or updates a memory entry for a user
|
|
2886
|
+
*/
|
|
2887
|
+
async function setMemory({ userId, key, value, tokenCount = 0, }) {
|
|
2888
|
+
try {
|
|
2889
|
+
if ((key === null || key === void 0 ? void 0 : key.toLowerCase()) === 'nothing') {
|
|
2890
|
+
return { ok: false };
|
|
2891
|
+
}
|
|
2892
|
+
const MemoryEntry = mongoose.models.MemoryEntry;
|
|
2893
|
+
await MemoryEntry.findOneAndUpdate({ userId, key }, {
|
|
2894
|
+
value,
|
|
2895
|
+
tokenCount,
|
|
2896
|
+
updated_at: new Date(),
|
|
2897
|
+
}, {
|
|
2898
|
+
upsert: true,
|
|
2899
|
+
new: true,
|
|
2900
|
+
});
|
|
2901
|
+
return { ok: true };
|
|
2902
|
+
}
|
|
2903
|
+
catch (error) {
|
|
2904
|
+
throw new Error(`Failed to set memory: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
/**
|
|
2908
|
+
* Deletes a specific memory entry for a user
|
|
2909
|
+
*/
|
|
2910
|
+
async function deleteMemory({ userId, key }) {
|
|
2911
|
+
try {
|
|
2912
|
+
const MemoryEntry = mongoose.models.MemoryEntry;
|
|
2913
|
+
const result = await MemoryEntry.findOneAndDelete({ userId, key });
|
|
2914
|
+
return { ok: !!result };
|
|
2915
|
+
}
|
|
2916
|
+
catch (error) {
|
|
2917
|
+
throw new Error(`Failed to delete memory: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
/**
|
|
2921
|
+
* Gets all memory entries for a user
|
|
2922
|
+
*/
|
|
2923
|
+
async function getAllUserMemories(userId) {
|
|
2924
|
+
try {
|
|
2925
|
+
const MemoryEntry = mongoose.models.MemoryEntry;
|
|
2926
|
+
return (await MemoryEntry.find({ userId }).lean());
|
|
2927
|
+
}
|
|
2928
|
+
catch (error) {
|
|
2929
|
+
throw new Error(`Failed to get all memories: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
/**
|
|
2933
|
+
* Gets and formats all memories for a user in two different formats
|
|
2934
|
+
*/
|
|
2935
|
+
async function getFormattedMemories({ userId, }) {
|
|
2936
|
+
try {
|
|
2937
|
+
const memories = await getAllUserMemories(userId);
|
|
2938
|
+
if (!memories || memories.length === 0) {
|
|
2939
|
+
return { withKeys: '', withoutKeys: '', totalTokens: 0 };
|
|
2940
|
+
}
|
|
2941
|
+
const sortedMemories = memories.sort((a, b) => new Date(a.updated_at).getTime() - new Date(b.updated_at).getTime());
|
|
2942
|
+
const totalTokens = sortedMemories.reduce((sum, memory) => {
|
|
2943
|
+
return sum + (memory.tokenCount || 0);
|
|
2944
|
+
}, 0);
|
|
2945
|
+
const withKeys = sortedMemories
|
|
2946
|
+
.map((memory, index) => {
|
|
2947
|
+
const date = formatDate(new Date(memory.updated_at));
|
|
2948
|
+
const tokenInfo = memory.tokenCount ? ` [${memory.tokenCount} tokens]` : '';
|
|
2949
|
+
return `${index + 1}. [${date}]. ["key": "${memory.key}"]${tokenInfo}. ["value": "${memory.value}"]`;
|
|
2950
|
+
})
|
|
2951
|
+
.join('\n\n');
|
|
2952
|
+
const withoutKeys = sortedMemories
|
|
2953
|
+
.map((memory, index) => {
|
|
2954
|
+
const date = formatDate(new Date(memory.updated_at));
|
|
2955
|
+
return `${index + 1}. [${date}]. ${memory.value}`;
|
|
2956
|
+
})
|
|
2957
|
+
.join('\n\n');
|
|
2958
|
+
return { withKeys, withoutKeys, totalTokens };
|
|
2959
|
+
}
|
|
2960
|
+
catch (error) {
|
|
2961
|
+
logger.error('Failed to get formatted memories:', error);
|
|
2962
|
+
return { withKeys: '', withoutKeys: '', totalTokens: 0 };
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
return {
|
|
2966
|
+
setMemory,
|
|
2967
|
+
createMemory,
|
|
2968
|
+
deleteMemory,
|
|
2969
|
+
getAllUserMemories,
|
|
2970
|
+
getFormattedMemories,
|
|
2971
|
+
};
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
class ShareServiceError extends Error {
|
|
2975
|
+
constructor(message, code) {
|
|
2976
|
+
super(message);
|
|
2977
|
+
this.name = 'ShareServiceError';
|
|
2978
|
+
this.code = code;
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
function memoizedAnonymizeId(prefix) {
|
|
2982
|
+
const memo = new Map();
|
|
2983
|
+
return (id) => {
|
|
2984
|
+
if (!memo.has(id)) {
|
|
2985
|
+
memo.set(id, `${prefix}_${nanoid()}`);
|
|
2986
|
+
}
|
|
2987
|
+
return memo.get(id);
|
|
2988
|
+
};
|
|
2989
|
+
}
|
|
2990
|
+
const anonymizeConvoId = memoizedAnonymizeId('convo');
|
|
2991
|
+
const anonymizeAssistantId = memoizedAnonymizeId('a');
|
|
2992
|
+
const anonymizeMessageId = (id) => id === Constants.NO_PARENT ? id : memoizedAnonymizeId('msg')(id);
|
|
2993
|
+
function anonymizeConvo(conversation) {
|
|
2994
|
+
if (!conversation) {
|
|
2995
|
+
return null;
|
|
2996
|
+
}
|
|
2997
|
+
const newConvo = { ...conversation };
|
|
2998
|
+
if (newConvo.assistant_id) {
|
|
2999
|
+
newConvo.assistant_id = anonymizeAssistantId(newConvo.assistant_id);
|
|
3000
|
+
}
|
|
3001
|
+
return newConvo;
|
|
3002
|
+
}
|
|
3003
|
+
function anonymizeMessages(messages, newConvoId) {
|
|
3004
|
+
if (!Array.isArray(messages)) {
|
|
3005
|
+
return [];
|
|
3006
|
+
}
|
|
3007
|
+
const idMap = new Map();
|
|
3008
|
+
return messages.map((message) => {
|
|
3009
|
+
var _a, _b;
|
|
3010
|
+
const newMessageId = anonymizeMessageId(message.messageId);
|
|
3011
|
+
idMap.set(message.messageId, newMessageId);
|
|
3012
|
+
const anonymizedAttachments = (_a = message.attachments) === null || _a === void 0 ? void 0 : _a.map((attachment) => {
|
|
3013
|
+
return {
|
|
3014
|
+
...attachment,
|
|
3015
|
+
messageId: newMessageId,
|
|
3016
|
+
conversationId: newConvoId,
|
|
3017
|
+
};
|
|
3018
|
+
});
|
|
3019
|
+
return {
|
|
3020
|
+
...message,
|
|
3021
|
+
messageId: newMessageId,
|
|
3022
|
+
parentMessageId: idMap.get(message.parentMessageId || '') ||
|
|
3023
|
+
anonymizeMessageId(message.parentMessageId || ''),
|
|
3024
|
+
conversationId: newConvoId,
|
|
3025
|
+
model: ((_b = message.model) === null || _b === void 0 ? void 0 : _b.startsWith('asst_'))
|
|
3026
|
+
? anonymizeAssistantId(message.model)
|
|
3027
|
+
: message.model,
|
|
3028
|
+
attachments: anonymizedAttachments,
|
|
3029
|
+
};
|
|
3030
|
+
});
|
|
3031
|
+
}
|
|
3032
|
+
/** Factory function that takes mongoose instance and returns the methods */
|
|
3033
|
+
function createShareMethods(mongoose) {
|
|
3034
|
+
/**
|
|
3035
|
+
* Get shared messages for a public share link
|
|
3036
|
+
*/
|
|
3037
|
+
async function getSharedMessages(shareId) {
|
|
3038
|
+
try {
|
|
3039
|
+
const SharedLink = mongoose.models.SharedLink;
|
|
3040
|
+
const share = (await SharedLink.findOne({ shareId, isPublic: true })
|
|
3041
|
+
.populate({
|
|
3042
|
+
path: 'messages',
|
|
3043
|
+
select: '-_id -__v -user',
|
|
3044
|
+
})
|
|
3045
|
+
.select('-_id -__v -user')
|
|
3046
|
+
.lean());
|
|
3047
|
+
if (!(share === null || share === void 0 ? void 0 : share.conversationId) || !share.isPublic) {
|
|
3048
|
+
return null;
|
|
3049
|
+
}
|
|
3050
|
+
const newConvoId = anonymizeConvoId(share.conversationId);
|
|
3051
|
+
const result = {
|
|
3052
|
+
shareId: share.shareId || shareId,
|
|
3053
|
+
title: share.title,
|
|
3054
|
+
isPublic: share.isPublic,
|
|
3055
|
+
createdAt: share.createdAt,
|
|
3056
|
+
updatedAt: share.updatedAt,
|
|
3057
|
+
conversationId: newConvoId,
|
|
3058
|
+
messages: anonymizeMessages(share.messages, newConvoId),
|
|
3059
|
+
};
|
|
3060
|
+
return result;
|
|
3061
|
+
}
|
|
3062
|
+
catch (error) {
|
|
3063
|
+
logger.error('[getSharedMessages] Error getting share link', {
|
|
3064
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
3065
|
+
shareId,
|
|
3066
|
+
});
|
|
3067
|
+
throw new ShareServiceError('Error getting share link', 'SHARE_FETCH_ERROR');
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
/**
|
|
3071
|
+
* Get shared links for a specific user with pagination and search
|
|
3072
|
+
*/
|
|
3073
|
+
async function getSharedLinks(user, pageParam, pageSize = 10, isPublic = true, sortBy = 'createdAt', sortDirection = 'desc', search) {
|
|
3074
|
+
var _a;
|
|
3075
|
+
try {
|
|
3076
|
+
const SharedLink = mongoose.models.SharedLink;
|
|
3077
|
+
const Conversation = mongoose.models.Conversation;
|
|
3078
|
+
const query = { user, isPublic };
|
|
3079
|
+
if (pageParam) {
|
|
3080
|
+
if (sortDirection === 'desc') {
|
|
3081
|
+
query[sortBy] = { $lt: pageParam };
|
|
3082
|
+
}
|
|
3083
|
+
else {
|
|
3084
|
+
query[sortBy] = { $gt: pageParam };
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
if (search && search.trim()) {
|
|
3088
|
+
try {
|
|
3089
|
+
const searchResults = await Conversation.meiliSearch(search);
|
|
3090
|
+
if (!((_a = searchResults === null || searchResults === void 0 ? void 0 : searchResults.hits) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
3091
|
+
return {
|
|
3092
|
+
links: [],
|
|
3093
|
+
nextCursor: undefined,
|
|
3094
|
+
hasNextPage: false,
|
|
3095
|
+
};
|
|
3096
|
+
}
|
|
3097
|
+
const conversationIds = searchResults.hits.map((hit) => hit.conversationId);
|
|
3098
|
+
query['conversationId'] = { $in: conversationIds };
|
|
3099
|
+
}
|
|
3100
|
+
catch (searchError) {
|
|
3101
|
+
logger.error('[getSharedLinks] Meilisearch error', {
|
|
3102
|
+
error: searchError instanceof Error ? searchError.message : 'Unknown error',
|
|
3103
|
+
user,
|
|
3104
|
+
});
|
|
3105
|
+
return {
|
|
3106
|
+
links: [],
|
|
3107
|
+
nextCursor: undefined,
|
|
3108
|
+
hasNextPage: false,
|
|
3109
|
+
};
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
const sort = {};
|
|
3113
|
+
sort[sortBy] = sortDirection === 'desc' ? -1 : 1;
|
|
3114
|
+
const sharedLinks = await SharedLink.find(query)
|
|
3115
|
+
.sort(sort)
|
|
3116
|
+
.limit(pageSize + 1)
|
|
3117
|
+
.select('-__v -user')
|
|
3118
|
+
.lean();
|
|
3119
|
+
const hasNextPage = sharedLinks.length > pageSize;
|
|
3120
|
+
const links = sharedLinks.slice(0, pageSize);
|
|
3121
|
+
const nextCursor = hasNextPage
|
|
3122
|
+
? links[links.length - 1][sortBy]
|
|
3123
|
+
: undefined;
|
|
3124
|
+
return {
|
|
3125
|
+
links: links.map((link) => ({
|
|
3126
|
+
shareId: link.shareId || '',
|
|
3127
|
+
title: (link === null || link === void 0 ? void 0 : link.title) || 'Untitled',
|
|
3128
|
+
isPublic: link.isPublic,
|
|
3129
|
+
createdAt: link.createdAt || new Date(),
|
|
3130
|
+
conversationId: link.conversationId,
|
|
3131
|
+
})),
|
|
3132
|
+
nextCursor,
|
|
3133
|
+
hasNextPage,
|
|
3134
|
+
};
|
|
3135
|
+
}
|
|
3136
|
+
catch (error) {
|
|
3137
|
+
logger.error('[getSharedLinks] Error getting shares', {
|
|
3138
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
3139
|
+
user,
|
|
3140
|
+
});
|
|
3141
|
+
throw new ShareServiceError('Error getting shares', 'SHARES_FETCH_ERROR');
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
/**
|
|
3145
|
+
* Delete all shared links for a user
|
|
3146
|
+
*/
|
|
3147
|
+
async function deleteAllSharedLinks(user) {
|
|
3148
|
+
try {
|
|
3149
|
+
const SharedLink = mongoose.models.SharedLink;
|
|
3150
|
+
const result = await SharedLink.deleteMany({ user });
|
|
3151
|
+
return {
|
|
3152
|
+
message: 'All shared links deleted successfully',
|
|
3153
|
+
deletedCount: result.deletedCount,
|
|
3154
|
+
};
|
|
3155
|
+
}
|
|
3156
|
+
catch (error) {
|
|
3157
|
+
logger.error('[deleteAllSharedLinks] Error deleting shared links', {
|
|
3158
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
3159
|
+
user,
|
|
3160
|
+
});
|
|
3161
|
+
throw new ShareServiceError('Error deleting shared links', 'BULK_DELETE_ERROR');
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
/**
|
|
3165
|
+
* Create a new shared link for a conversation
|
|
3166
|
+
*/
|
|
3167
|
+
async function createSharedLink(user, conversationId) {
|
|
3168
|
+
if (!user || !conversationId) {
|
|
3169
|
+
throw new ShareServiceError('Missing required parameters', 'INVALID_PARAMS');
|
|
3170
|
+
}
|
|
3171
|
+
try {
|
|
3172
|
+
const Message = mongoose.models.Message;
|
|
3173
|
+
const SharedLink = mongoose.models.SharedLink;
|
|
3174
|
+
const Conversation = mongoose.models.Conversation;
|
|
3175
|
+
const [existingShare, conversationMessages] = await Promise.all([
|
|
3176
|
+
SharedLink.findOne({ conversationId, user, isPublic: true })
|
|
3177
|
+
.select('-_id -__v -user')
|
|
3178
|
+
.lean(),
|
|
3179
|
+
Message.find({ conversationId, user }).sort({ createdAt: 1 }).lean(),
|
|
3180
|
+
]);
|
|
3181
|
+
if (existingShare && existingShare.isPublic) {
|
|
3182
|
+
logger.error('[createSharedLink] Share already exists', {
|
|
3183
|
+
user,
|
|
3184
|
+
conversationId,
|
|
3185
|
+
});
|
|
3186
|
+
throw new ShareServiceError('Share already exists', 'SHARE_EXISTS');
|
|
3187
|
+
}
|
|
3188
|
+
else if (existingShare) {
|
|
3189
|
+
await SharedLink.deleteOne({ conversationId, user });
|
|
3190
|
+
}
|
|
3191
|
+
const conversation = (await Conversation.findOne({ conversationId, user }).lean());
|
|
3192
|
+
// Check if user owns the conversation
|
|
3193
|
+
if (!conversation) {
|
|
3194
|
+
throw new ShareServiceError('Conversation not found or access denied', 'CONVERSATION_NOT_FOUND');
|
|
3195
|
+
}
|
|
3196
|
+
// Check if there are any messages to share
|
|
3197
|
+
if (!conversationMessages || conversationMessages.length === 0) {
|
|
3198
|
+
throw new ShareServiceError('No messages to share', 'NO_MESSAGES');
|
|
3199
|
+
}
|
|
3200
|
+
const title = conversation.title || 'Untitled';
|
|
3201
|
+
const shareId = nanoid();
|
|
3202
|
+
await SharedLink.create({
|
|
3203
|
+
shareId,
|
|
3204
|
+
conversationId,
|
|
3205
|
+
messages: conversationMessages,
|
|
3206
|
+
title,
|
|
3207
|
+
user,
|
|
3208
|
+
});
|
|
3209
|
+
return { shareId, conversationId };
|
|
3210
|
+
}
|
|
3211
|
+
catch (error) {
|
|
3212
|
+
if (error instanceof ShareServiceError) {
|
|
3213
|
+
throw error;
|
|
3214
|
+
}
|
|
3215
|
+
logger.error('[createSharedLink] Error creating shared link', {
|
|
3216
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
3217
|
+
user,
|
|
3218
|
+
conversationId,
|
|
3219
|
+
});
|
|
3220
|
+
throw new ShareServiceError('Error creating shared link', 'SHARE_CREATE_ERROR');
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3223
|
+
/**
|
|
3224
|
+
* Get a shared link for a conversation
|
|
3225
|
+
*/
|
|
3226
|
+
async function getSharedLink(user, conversationId) {
|
|
3227
|
+
if (!user || !conversationId) {
|
|
3228
|
+
throw new ShareServiceError('Missing required parameters', 'INVALID_PARAMS');
|
|
3229
|
+
}
|
|
3230
|
+
try {
|
|
3231
|
+
const SharedLink = mongoose.models.SharedLink;
|
|
3232
|
+
const share = (await SharedLink.findOne({ conversationId, user, isPublic: true })
|
|
3233
|
+
.select('shareId -_id')
|
|
3234
|
+
.lean());
|
|
3235
|
+
if (!share) {
|
|
3236
|
+
return { shareId: null, success: false };
|
|
3237
|
+
}
|
|
3238
|
+
return { shareId: share.shareId || null, success: true };
|
|
3239
|
+
}
|
|
3240
|
+
catch (error) {
|
|
3241
|
+
logger.error('[getSharedLink] Error getting shared link', {
|
|
3242
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
3243
|
+
user,
|
|
3244
|
+
conversationId,
|
|
3245
|
+
});
|
|
3246
|
+
throw new ShareServiceError('Error getting shared link', 'SHARE_FETCH_ERROR');
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
/**
|
|
3250
|
+
* Update a shared link with new messages
|
|
3251
|
+
*/
|
|
3252
|
+
async function updateSharedLink(user, shareId) {
|
|
3253
|
+
if (!user || !shareId) {
|
|
3254
|
+
throw new ShareServiceError('Missing required parameters', 'INVALID_PARAMS');
|
|
3255
|
+
}
|
|
3256
|
+
try {
|
|
3257
|
+
const SharedLink = mongoose.models.SharedLink;
|
|
3258
|
+
const Message = mongoose.models.Message;
|
|
3259
|
+
const share = (await SharedLink.findOne({ shareId, user })
|
|
3260
|
+
.select('-_id -__v -user')
|
|
3261
|
+
.lean());
|
|
3262
|
+
if (!share) {
|
|
3263
|
+
throw new ShareServiceError('Share not found', 'SHARE_NOT_FOUND');
|
|
3264
|
+
}
|
|
3265
|
+
const updatedMessages = await Message.find({ conversationId: share.conversationId, user })
|
|
3266
|
+
.sort({ createdAt: 1 })
|
|
3267
|
+
.lean();
|
|
3268
|
+
const newShareId = nanoid();
|
|
3269
|
+
const update = {
|
|
3270
|
+
messages: updatedMessages,
|
|
3271
|
+
user,
|
|
3272
|
+
shareId: newShareId,
|
|
3273
|
+
};
|
|
3274
|
+
const updatedShare = (await SharedLink.findOneAndUpdate({ shareId, user }, update, {
|
|
3275
|
+
new: true,
|
|
3276
|
+
upsert: false,
|
|
3277
|
+
runValidators: true,
|
|
3278
|
+
}).lean());
|
|
3279
|
+
if (!updatedShare) {
|
|
3280
|
+
throw new ShareServiceError('Share update failed', 'SHARE_UPDATE_ERROR');
|
|
3281
|
+
}
|
|
3282
|
+
anonymizeConvo(updatedShare);
|
|
3283
|
+
return { shareId: newShareId, conversationId: updatedShare.conversationId };
|
|
3284
|
+
}
|
|
3285
|
+
catch (error) {
|
|
3286
|
+
logger.error('[updateSharedLink] Error updating shared link', {
|
|
3287
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
3288
|
+
user,
|
|
3289
|
+
shareId,
|
|
3290
|
+
});
|
|
3291
|
+
throw new ShareServiceError(error instanceof ShareServiceError ? error.message : 'Error updating shared link', error instanceof ShareServiceError ? error.code : 'SHARE_UPDATE_ERROR');
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
/**
|
|
3295
|
+
* Delete a shared link
|
|
3296
|
+
*/
|
|
3297
|
+
async function deleteSharedLink(user, shareId) {
|
|
3298
|
+
if (!user || !shareId) {
|
|
3299
|
+
throw new ShareServiceError('Missing required parameters', 'INVALID_PARAMS');
|
|
3300
|
+
}
|
|
3301
|
+
try {
|
|
3302
|
+
const SharedLink = mongoose.models.SharedLink;
|
|
3303
|
+
const result = await SharedLink.findOneAndDelete({ shareId, user }).lean();
|
|
3304
|
+
if (!result) {
|
|
3305
|
+
return null;
|
|
3306
|
+
}
|
|
3307
|
+
return {
|
|
3308
|
+
success: true,
|
|
3309
|
+
shareId,
|
|
3310
|
+
message: 'Share deleted successfully',
|
|
3311
|
+
};
|
|
3312
|
+
}
|
|
3313
|
+
catch (error) {
|
|
3314
|
+
logger.error('[deleteSharedLink] Error deleting shared link', {
|
|
3315
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
3316
|
+
user,
|
|
3317
|
+
shareId,
|
|
3318
|
+
});
|
|
3319
|
+
throw new ShareServiceError('Error deleting shared link', 'SHARE_DELETE_ERROR');
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
// Return all methods
|
|
3323
|
+
return {
|
|
3324
|
+
getSharedLink,
|
|
3325
|
+
getSharedLinks,
|
|
3326
|
+
createSharedLink,
|
|
3327
|
+
updateSharedLink,
|
|
3328
|
+
deleteSharedLink,
|
|
3329
|
+
getSharedMessages,
|
|
3330
|
+
deleteAllSharedLinks,
|
|
3331
|
+
};
|
|
3332
|
+
}
|
|
3333
|
+
|
|
3334
|
+
// Factory function that takes mongoose instance and returns the methods
|
|
3335
|
+
function createPluginAuthMethods(mongoose) {
|
|
3336
|
+
/**
|
|
3337
|
+
* Finds a single plugin auth entry by userId and authField
|
|
3338
|
+
*/
|
|
3339
|
+
async function findOnePluginAuth({ userId, authField, }) {
|
|
3340
|
+
try {
|
|
3341
|
+
const PluginAuth = mongoose.models.PluginAuth;
|
|
3342
|
+
return await PluginAuth.findOne({ userId, authField }).lean();
|
|
3343
|
+
}
|
|
3344
|
+
catch (error) {
|
|
3345
|
+
throw new Error(`Failed to find plugin auth: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
/**
|
|
3349
|
+
* Finds multiple plugin auth entries by userId and pluginKeys
|
|
3350
|
+
*/
|
|
3351
|
+
async function findPluginAuthsByKeys({ userId, pluginKeys, }) {
|
|
3352
|
+
try {
|
|
3353
|
+
if (!pluginKeys || pluginKeys.length === 0) {
|
|
3354
|
+
return [];
|
|
3355
|
+
}
|
|
3356
|
+
const PluginAuth = mongoose.models.PluginAuth;
|
|
3357
|
+
return await PluginAuth.find({
|
|
3358
|
+
userId,
|
|
3359
|
+
pluginKey: { $in: pluginKeys },
|
|
3360
|
+
}).lean();
|
|
3361
|
+
}
|
|
3362
|
+
catch (error) {
|
|
3363
|
+
throw new Error(`Failed to find plugin auths: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
/**
|
|
3367
|
+
* Updates or creates a plugin auth entry
|
|
3368
|
+
*/
|
|
3369
|
+
async function updatePluginAuth({ userId, authField, pluginKey, value, }) {
|
|
3370
|
+
try {
|
|
3371
|
+
const PluginAuth = mongoose.models.PluginAuth;
|
|
3372
|
+
const existingAuth = await PluginAuth.findOne({ userId, pluginKey, authField }).lean();
|
|
3373
|
+
if (existingAuth) {
|
|
3374
|
+
return await PluginAuth.findOneAndUpdate({ userId, pluginKey, authField }, { $set: { value } }, { new: true, upsert: true }).lean();
|
|
3375
|
+
}
|
|
3376
|
+
else {
|
|
3377
|
+
const newPluginAuth = await new PluginAuth({
|
|
3378
|
+
userId,
|
|
3379
|
+
authField,
|
|
3380
|
+
value,
|
|
3381
|
+
pluginKey,
|
|
3382
|
+
});
|
|
3383
|
+
await newPluginAuth.save();
|
|
3384
|
+
return newPluginAuth.toObject();
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
catch (error) {
|
|
3388
|
+
throw new Error(`Failed to update plugin auth: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
/**
|
|
3392
|
+
* Deletes plugin auth entries based on provided parameters
|
|
3393
|
+
*/
|
|
3394
|
+
async function deletePluginAuth({ userId, authField, pluginKey, all = false, }) {
|
|
3395
|
+
try {
|
|
3396
|
+
const PluginAuth = mongoose.models.PluginAuth;
|
|
3397
|
+
if (all) {
|
|
3398
|
+
const filter = { userId };
|
|
3399
|
+
if (pluginKey) {
|
|
3400
|
+
filter.pluginKey = pluginKey;
|
|
3401
|
+
}
|
|
3402
|
+
return await PluginAuth.deleteMany(filter);
|
|
3403
|
+
}
|
|
3404
|
+
if (!authField) {
|
|
3405
|
+
throw new Error('authField is required when all is false');
|
|
3406
|
+
}
|
|
3407
|
+
return await PluginAuth.deleteOne({ userId, authField });
|
|
3408
|
+
}
|
|
3409
|
+
catch (error) {
|
|
3410
|
+
throw new Error(`Failed to delete plugin auth: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
/**
|
|
3414
|
+
* Deletes all plugin auth entries for a user
|
|
3415
|
+
*/
|
|
3416
|
+
async function deleteAllUserPluginAuths(userId) {
|
|
3417
|
+
try {
|
|
3418
|
+
const PluginAuth = mongoose.models.PluginAuth;
|
|
3419
|
+
return await PluginAuth.deleteMany({ userId });
|
|
3420
|
+
}
|
|
3421
|
+
catch (error) {
|
|
3422
|
+
throw new Error(`Failed to delete all user plugin auths: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
return {
|
|
3426
|
+
findOnePluginAuth,
|
|
3427
|
+
findPluginAuthsByKeys,
|
|
3428
|
+
updatePluginAuth,
|
|
3429
|
+
deletePluginAuth,
|
|
3430
|
+
deleteAllUserPluginAuths,
|
|
3431
|
+
};
|
|
3432
|
+
}
|
|
3433
|
+
|
|
2600
3434
|
/**
|
|
2601
3435
|
* Creates all database methods for all collections
|
|
2602
3436
|
*/
|
|
@@ -2606,8 +3440,11 @@ function createMethods(mongoose) {
|
|
|
2606
3440
|
...createSessionMethods(mongoose),
|
|
2607
3441
|
...createTokenMethods(mongoose),
|
|
2608
3442
|
...createRoleMethods(mongoose),
|
|
3443
|
+
...createMemoryMethods(mongoose),
|
|
3444
|
+
...createShareMethods(mongoose),
|
|
3445
|
+
...createPluginAuthMethods(mongoose),
|
|
2609
3446
|
};
|
|
2610
3447
|
}
|
|
2611
3448
|
|
|
2612
|
-
export { Action as actionSchema, agentSchema, assistantSchema, balanceSchema, bannerSchema, categoriesSchema, conversationTag as conversationTagSchema, convoSchema, createMethods, createModels, file as fileSchema, hashToken, keySchema, logger, logger$1 as meiliLogger, messageSchema, pluginAuthSchema, presetSchema, projectSchema, promptGroupSchema, promptSchema, roleSchema, sessionSchema, shareSchema, signPayload, tokenSchema, toolCallSchema, transactionSchema, userSchema };
|
|
3449
|
+
export { Action as actionSchema, agentSchema, assistantSchema, balanceSchema, bannerSchema, categoriesSchema, conversationTag as conversationTagSchema, convoSchema, createMethods, createModels, file as fileSchema, hashToken, keySchema, logger, logger$1 as meiliLogger, MemoryEntrySchema as memorySchema, messageSchema, pluginAuthSchema, presetSchema, projectSchema, promptGroupSchema, promptSchema, roleSchema, sessionSchema, shareSchema, signPayload, tokenSchema, toolCallSchema, transactionSchema, userSchema };
|
|
2613
3450
|
//# sourceMappingURL=index.es.js.map
|