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