mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-19 18:00:15 +01:00
Move usermethods and models to data-schema
This commit is contained in:
parent
4808c5be48
commit
4049b5572c
93 changed files with 2396 additions and 1267 deletions
|
|
@ -10,18 +10,45 @@ const mockPluginService = {
|
||||||
getUserPluginAuthValue: jest.fn(),
|
getUserPluginAuthValue: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
jest.mock('~/models/User', () => {
|
const mockModels = {
|
||||||
return function () {
|
User: mockUser,
|
||||||
return mockUser;
|
};
|
||||||
|
jest.mock('~/lib/db/connectDb', () => {
|
||||||
|
return {
|
||||||
|
connectDb: jest.fn(),
|
||||||
|
get models() {
|
||||||
|
return mockModels;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
jest.mock('@librechat/data-schemas', () => {
|
||||||
|
const userModelMock = {
|
||||||
|
createUser: jest.fn(() => mockUser),
|
||||||
|
findUser: jest.fn(),
|
||||||
|
updateUser: jest.fn(),
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
registerModels: jest.fn().mockReturnValue({
|
||||||
|
User: userModelMock,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('~/models/Message', () => ({
|
||||||
|
Message: jest.fn(),
|
||||||
|
}));
|
||||||
|
jest.mock('~/models/Conversation', () => ({
|
||||||
|
Conversation: jest.fn(),
|
||||||
|
}));
|
||||||
|
jest.mock('~/models/File', () => ({
|
||||||
|
File: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
jest.mock('~/server/services/PluginService', () => mockPluginService);
|
jest.mock('~/server/services/PluginService', () => mockPluginService);
|
||||||
|
|
||||||
const { BaseLLM } = require('@langchain/openai');
|
const { BaseLLM } = require('@langchain/openai');
|
||||||
const { Calculator } = require('@langchain/community/tools/calculator');
|
const { Calculator } = require('@langchain/community/tools/calculator');
|
||||||
|
|
||||||
const User = require('~/models/User');
|
|
||||||
const PluginService = require('~/server/services/PluginService');
|
const PluginService = require('~/server/services/PluginService');
|
||||||
const { validateTools, loadTools, loadToolWithAuth } = require('./handleTools');
|
const { validateTools, loadTools, loadToolWithAuth } = require('./handleTools');
|
||||||
const { StructuredSD, availableTools, DALLE3 } = require('../');
|
const { StructuredSD, availableTools, DALLE3 } = require('../');
|
||||||
|
|
@ -36,6 +63,9 @@ describe('Tool Handlers', () => {
|
||||||
const mainPlugin = availableTools.find((tool) => tool.pluginKey === pluginKey);
|
const mainPlugin = availableTools.find((tool) => tool.pluginKey === pluginKey);
|
||||||
const authConfigs = mainPlugin.authConfig;
|
const authConfigs = mainPlugin.authConfig;
|
||||||
|
|
||||||
|
const { registerModels } = require('@librechat/data-schemas');
|
||||||
|
let User = registerModels().User;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
mockUser.save.mockResolvedValue(undefined);
|
mockUser.save.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
|
@ -52,7 +82,7 @@ describe('Tool Handlers', () => {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
fakeUser = new User({
|
fakeUser = await User.createUser({
|
||||||
name: 'Fake User',
|
name: 'Fake User',
|
||||||
username: 'fakeuser',
|
username: 'fakeuser',
|
||||||
email: 'fakeuser@example.com',
|
email: 'fakeuser@example.com',
|
||||||
|
|
|
||||||
6
api/cache/banViolation.js
vendored
6
api/cache/banViolation.js
vendored
|
|
@ -1,8 +1,8 @@
|
||||||
const { ViolationTypes } = require('librechat-data-provider');
|
const { ViolationTypes } = require('librechat-data-provider');
|
||||||
const { isEnabled, math, removePorts } = require('~/server/utils');
|
const { isEnabled, math, removePorts } = require('~/server/utils');
|
||||||
const { deleteAllUserSessions } = require('~/models');
|
|
||||||
const getLogStores = require('./getLogStores');
|
const getLogStores = require('./getLogStores');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
const { BAN_VIOLATIONS, BAN_INTERVAL } = process.env ?? {};
|
const { BAN_VIOLATIONS, BAN_INTERVAL } = process.env ?? {};
|
||||||
const interval = math(BAN_INTERVAL, 20);
|
const interval = math(BAN_INTERVAL, 20);
|
||||||
|
|
@ -32,7 +32,6 @@ const banViolation = async (req, res, errorMessage) => {
|
||||||
if (!isEnabled(BAN_VIOLATIONS)) {
|
if (!isEnabled(BAN_VIOLATIONS)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!errorMessage) {
|
if (!errorMessage) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -46,12 +45,11 @@ const banViolation = async (req, res, errorMessage) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await deleteAllUserSessions({ userId: user_id });
|
await db.models.Session.deleteAllUserSessions({ userId: user_id });
|
||||||
res.clearCookie('refreshToken');
|
res.clearCookie('refreshToken');
|
||||||
|
|
||||||
const banLogs = getLogStores(ViolationTypes.BAN);
|
const banLogs = getLogStores(ViolationTypes.BAN);
|
||||||
const duration = errorMessage.duration || banLogs.opts.ttl;
|
const duration = errorMessage.duration || banLogs.opts.ttl;
|
||||||
|
|
||||||
if (duration <= 0) {
|
if (duration <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
35
api/cache/banViolation.spec.js
vendored
35
api/cache/banViolation.spec.js
vendored
|
|
@ -1,7 +1,40 @@
|
||||||
const banViolation = require('./banViolation');
|
const banViolation = require('./banViolation');
|
||||||
|
|
||||||
|
jest.mock('@librechat/data-schemas', () => {
|
||||||
|
const sessionModelMock = {
|
||||||
|
deleteAllUserSessions: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
registerModels: jest.fn().mockReturnValue({
|
||||||
|
Session: sessionModelMock,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockModels = {
|
||||||
|
Session: {
|
||||||
|
deleteAllUserSessions: jest.fn(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.mock('~/lib/db/connectDb', () => {
|
||||||
|
return {
|
||||||
|
connectDb: jest.fn(),
|
||||||
|
get models() {
|
||||||
|
return mockModels;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('~/server/utils', () => ({
|
||||||
|
isEnabled: jest.fn(() => true), // default to false, override per test if needed
|
||||||
|
math: jest.fn(() => 20), // default to false, override per test if needed
|
||||||
|
removePorts: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
jest.mock('keyv');
|
jest.mock('keyv');
|
||||||
jest.mock('../models/Session');
|
// jest.mock('../models/Session');
|
||||||
// Mocking the getLogStores function
|
// Mocking the getLogStores function
|
||||||
jest.mock('./getLogStores', () => {
|
jest.mock('./getLogStores', () => {
|
||||||
return jest.fn().mockImplementation(() => {
|
return jest.fn().mockImplementation(() => {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
const MONGO_URI = process.env.MONGO_URI;
|
const { registerModels } = require('@librechat/data-schemas');
|
||||||
|
|
||||||
if (!MONGO_URI) {
|
if (!process.env.MONGO_URI) {
|
||||||
throw new Error('Please define the MONGO_URI environment variable');
|
throw new Error('Please define the MONGO_URI environment variable');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -17,7 +17,7 @@ if (!cached) {
|
||||||
cached = global.mongoose = { conn: null, promise: null };
|
cached = global.mongoose = { conn: null, promise: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function connectDb() {
|
async function connectDb(mongoUri = process.env.MONGO_URI) {
|
||||||
if (cached.conn && cached.conn?._readyState === 1) {
|
if (cached.conn && cached.conn?._readyState === 1) {
|
||||||
return cached.conn;
|
return cached.conn;
|
||||||
}
|
}
|
||||||
|
|
@ -34,12 +34,30 @@ async function connectDb() {
|
||||||
};
|
};
|
||||||
|
|
||||||
mongoose.set('strictQuery', true);
|
mongoose.set('strictQuery', true);
|
||||||
cached.promise = mongoose.connect(MONGO_URI, opts).then((mongoose) => {
|
cached.promise = mongoose.connect(mongoUri, opts).then((mongoose) => {
|
||||||
return mongoose;
|
return mongoose;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
cached.conn = await cached.promise;
|
cached.conn = await cached.promise;
|
||||||
|
|
||||||
|
// Register models once
|
||||||
|
if (!cached.models) {
|
||||||
|
cached.models = registerModels(mongoose);
|
||||||
|
}
|
||||||
|
|
||||||
return cached.conn;
|
return cached.conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = connectDb;
|
function getModels() {
|
||||||
|
return cached.models;
|
||||||
|
}
|
||||||
|
module.exports = {
|
||||||
|
connectDb,
|
||||||
|
getModels,
|
||||||
|
get models() {
|
||||||
|
if (!cached.models) {
|
||||||
|
throw new Error('Models not registered. ');
|
||||||
|
}
|
||||||
|
return cached.models;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
const connectDb = require('./connectDb');
|
const { connectDb, getModels} = require('./connectDb');
|
||||||
const indexSync = require('./indexSync');
|
const indexSync = require('./indexSync');
|
||||||
|
|
||||||
module.exports = { connectDb, indexSync };
|
module.exports = { connectDb, getModels, indexSync };
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
const { MeiliSearch } = require('meilisearch');
|
const { MeiliSearch } = require('meilisearch');
|
||||||
const { Conversation } = require('~/models/Conversation');
|
|
||||||
const { Message } = require('~/models/Message');
|
|
||||||
const { isEnabled } = require('~/server/utils');
|
const { isEnabled } = require('~/server/utils');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
const searchEnabled = isEnabled(process.env.SEARCH);
|
const searchEnabled = isEnabled(process.env.SEARCH);
|
||||||
const indexingDisabled = isEnabled(process.env.MEILI_NO_SYNC);
|
const indexingDisabled = isEnabled(process.env.MEILI_NO_SYNC);
|
||||||
|
|
@ -29,7 +28,7 @@ async function indexSync() {
|
||||||
if (!searchEnabled) {
|
if (!searchEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const { Message, Conversation } = db.models;
|
||||||
try {
|
try {
|
||||||
const client = MeiliSearchClient.getInstance();
|
const client = MeiliSearchClient.getInstance();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
const crypto = require('node:crypto');
|
const crypto = require('node:crypto');
|
||||||
const { agentSchema } = require('@librechat/data-schemas');
|
|
||||||
const { SystemRoles, Tools, actionDelimiter } = require('librechat-data-provider');
|
const { SystemRoles, Tools, actionDelimiter } = require('librechat-data-provider');
|
||||||
const { GLOBAL_PROJECT_NAME, EPHEMERAL_AGENT_ID, mcp_delimiter } =
|
const { GLOBAL_PROJECT_NAME, EPHEMERAL_AGENT_ID, mcp_delimiter } =
|
||||||
require('librechat-data-provider').Constants;
|
require('librechat-data-provider').Constants;
|
||||||
|
|
@ -15,7 +14,7 @@ const getLogStores = require('~/cache/getLogStores');
|
||||||
const { getActions } = require('./Action');
|
const { getActions } = require('./Action');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
|
||||||
const Agent = mongoose.model('agent', agentSchema);
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an agent with the provided data.
|
* Create an agent with the provided data.
|
||||||
|
|
@ -36,7 +35,7 @@ const createAgent = async (agentData) => {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
return (await Agent.create(initialAgentData)).toObject();
|
return (await db.models.Agent.create(initialAgentData)).toObject();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -47,7 +46,7 @@ const createAgent = async (agentData) => {
|
||||||
* @param {string} searchParameter.author - The user ID of the agent's author.
|
* @param {string} searchParameter.author - The user ID of the agent's author.
|
||||||
* @returns {Promise<Agent|null>} The agent document as a plain object, or null if not found.
|
* @returns {Promise<Agent|null>} The agent document as a plain object, or null if not found.
|
||||||
*/
|
*/
|
||||||
const getAgent = async (searchParameter) => await Agent.findOne(searchParameter).lean();
|
const getAgent = async (searchParameter) => await db.models.Agent.findOne(searchParameter).lean();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load an agent based on the provided ID
|
* Load an agent based on the provided ID
|
||||||
|
|
@ -269,6 +268,7 @@ const updateAgent = async (searchParameter, updateData, options = {}) => {
|
||||||
const { updatingUserId = null, forceVersion = false } = options;
|
const { updatingUserId = null, forceVersion = false } = options;
|
||||||
const mongoOptions = { new: true, upsert: false };
|
const mongoOptions = { new: true, upsert: false };
|
||||||
|
|
||||||
|
const Agent = db.models?.Agent;
|
||||||
const currentAgent = await Agent.findOne(searchParameter);
|
const currentAgent = await Agent.findOne(searchParameter);
|
||||||
if (currentAgent) {
|
if (currentAgent) {
|
||||||
const { __v, _id, id, versions, author, ...versionData } = currentAgent.toObject();
|
const { __v, _id, id, versions, author, ...versionData } = currentAgent.toObject();
|
||||||
|
|
@ -362,6 +362,7 @@ const updateAgent = async (searchParameter, updateData, options = {}) => {
|
||||||
* @returns {Promise<Agent>} The updated agent.
|
* @returns {Promise<Agent>} The updated agent.
|
||||||
*/
|
*/
|
||||||
const addAgentResourceFile = async ({ req, agent_id, tool_resource, file_id }) => {
|
const addAgentResourceFile = async ({ req, agent_id, tool_resource, file_id }) => {
|
||||||
|
const Agent = db.models?.Agent;
|
||||||
const searchParameter = { id: agent_id };
|
const searchParameter = { id: agent_id };
|
||||||
let agent = await getAgent(searchParameter);
|
let agent = await getAgent(searchParameter);
|
||||||
if (!agent) {
|
if (!agent) {
|
||||||
|
|
@ -427,7 +428,7 @@ const removeAgentResourceFiles = async ({ agent_id, files }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatePullData = { $pull: pullOps };
|
const updatePullData = { $pull: pullOps };
|
||||||
const agentAfterPull = await Agent.findOneAndUpdate(searchParameter, updatePullData, {
|
const agentAfterPull = await db.models.Agent.findOneAndUpdate(searchParameter, updatePullData, {
|
||||||
new: true,
|
new: true,
|
||||||
}).lean();
|
}).lean();
|
||||||
|
|
||||||
|
|
@ -457,7 +458,7 @@ const removeAgentResourceFiles = async ({ agent_id, files }) => {
|
||||||
* @returns {Promise<void>} Resolves when the agent has been successfully deleted.
|
* @returns {Promise<void>} Resolves when the agent has been successfully deleted.
|
||||||
*/
|
*/
|
||||||
const deleteAgent = async (searchParameter) => {
|
const deleteAgent = async (searchParameter) => {
|
||||||
const agent = await Agent.findOneAndDelete(searchParameter);
|
const agent = await db.models.Agent.findOneAndDelete(searchParameter);
|
||||||
if (agent) {
|
if (agent) {
|
||||||
await removeAgentFromAllProjects(agent.id);
|
await removeAgentFromAllProjects(agent.id);
|
||||||
}
|
}
|
||||||
|
|
@ -481,9 +482,8 @@ const getListAgents = async (searchParameter) => {
|
||||||
delete globalQuery.author;
|
delete globalQuery.author;
|
||||||
query = { $or: [globalQuery, query] };
|
query = { $or: [globalQuery, query] };
|
||||||
}
|
}
|
||||||
|
|
||||||
const agents = (
|
const agents = (
|
||||||
await Agent.find(query, {
|
await db.models.Agent.find(query, {
|
||||||
id: 1,
|
id: 1,
|
||||||
_id: 0,
|
_id: 0,
|
||||||
name: 1,
|
name: 1,
|
||||||
|
|
@ -580,6 +580,7 @@ const updateAgentProjects = async ({ user, agentId, projectIds, removeProjectIds
|
||||||
* @throws {Error} If the agent is not found or the specified version does not exist.
|
* @throws {Error} If the agent is not found or the specified version does not exist.
|
||||||
*/
|
*/
|
||||||
const revertAgentVersion = async (searchParameter, versionIndex) => {
|
const revertAgentVersion = async (searchParameter, versionIndex) => {
|
||||||
|
const Agent = db.models?.Agent;
|
||||||
const agent = await Agent.findOne(searchParameter);
|
const agent = await Agent.findOne(searchParameter);
|
||||||
if (!agent) {
|
if (!agent) {
|
||||||
throw new Error('Agent not found');
|
throw new Error('Agent not found');
|
||||||
|
|
@ -662,7 +663,6 @@ const generateActionMetadataHash = async (actionIds, actions) => {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Agent,
|
|
||||||
getAgent,
|
getAgent,
|
||||||
loadAgent,
|
loadAgent,
|
||||||
createAgent,
|
createAgent,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ const mongoose = require('mongoose');
|
||||||
const { v4: uuidv4 } = require('uuid');
|
const { v4: uuidv4 } = require('uuid');
|
||||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||||
const {
|
const {
|
||||||
Agent,
|
|
||||||
addAgentResourceFile,
|
addAgentResourceFile,
|
||||||
removeAgentResourceFiles,
|
removeAgentResourceFiles,
|
||||||
createAgent,
|
createAgent,
|
||||||
|
|
@ -20,6 +19,9 @@ const {
|
||||||
getListAgents,
|
getListAgents,
|
||||||
updateAgentProjects,
|
updateAgentProjects,
|
||||||
} = require('./Agent');
|
} = require('./Agent');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
|
let Agent;
|
||||||
|
|
||||||
describe('Agent Resource File Operations', () => {
|
describe('Agent Resource File Operations', () => {
|
||||||
let mongoServer;
|
let mongoServer;
|
||||||
|
|
@ -27,7 +29,9 @@ describe('Agent Resource File Operations', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
mongoServer = await MongoMemoryServer.create();
|
mongoServer = await MongoMemoryServer.create();
|
||||||
const mongoUri = mongoServer.getUri();
|
const mongoUri = mongoServer.getUri();
|
||||||
await mongoose.connect(mongoUri);
|
await db.connectDb(mongoUri);
|
||||||
|
|
||||||
|
Agent = db.models.Agent;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|
@ -55,6 +59,7 @@ describe('Agent Resource File Operations', () => {
|
||||||
|
|
||||||
test('should add tool_resource to tools if missing', async () => {
|
test('should add tool_resource to tools if missing', async () => {
|
||||||
const agent = await createBasicAgent();
|
const agent = await createBasicAgent();
|
||||||
|
|
||||||
const fileId = uuidv4();
|
const fileId = uuidv4();
|
||||||
const toolResource = 'file_search';
|
const toolResource = 'file_search';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
const mongoose = require('mongoose');
|
const db = require('~/lib/db/connectDb');
|
||||||
const { assistantSchema } = require('@librechat/data-schemas');
|
|
||||||
|
|
||||||
const Assistant = mongoose.model('assistant', assistantSchema);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update an assistant with new data without overwriting existing properties,
|
* Update an assistant with new data without overwriting existing properties,
|
||||||
|
|
@ -15,7 +12,7 @@ const Assistant = mongoose.model('assistant', assistantSchema);
|
||||||
*/
|
*/
|
||||||
const updateAssistantDoc = async (searchParams, updateData) => {
|
const updateAssistantDoc = async (searchParams, updateData) => {
|
||||||
const options = { new: true, upsert: true };
|
const options = { new: true, upsert: true };
|
||||||
return await Assistant.findOneAndUpdate(searchParams, updateData, options).lean();
|
return await db.models.Assistant.findOneAndUpdate(searchParams, updateData, options).lean();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -26,7 +23,7 @@ const updateAssistantDoc = async (searchParams, updateData) => {
|
||||||
* @param {string} searchParams.user - The user ID of the assistant's author.
|
* @param {string} searchParams.user - The user ID of the assistant's author.
|
||||||
* @returns {Promise<AssistantDocument|null>} The assistant document as a plain object, or null if not found.
|
* @returns {Promise<AssistantDocument|null>} The assistant document as a plain object, or null if not found.
|
||||||
*/
|
*/
|
||||||
const getAssistant = async (searchParams) => await Assistant.findOne(searchParams).lean();
|
const getAssistant = async (searchParams) => await db.models.Assistant.findOne(searchParams).lean();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves all assistants that match the given search parameters.
|
* Retrieves all assistants that match the given search parameters.
|
||||||
|
|
@ -36,7 +33,7 @@ const getAssistant = async (searchParams) => await Assistant.findOne(searchParam
|
||||||
* @returns {Promise<Array<AssistantDocument>>} A promise that resolves to an array of assistant documents as plain objects.
|
* @returns {Promise<Array<AssistantDocument>>} A promise that resolves to an array of assistant documents as plain objects.
|
||||||
*/
|
*/
|
||||||
const getAssistants = async (searchParams, select = null) => {
|
const getAssistants = async (searchParams, select = null) => {
|
||||||
let query = Assistant.find(searchParams);
|
let query = db.models.Assistant.find(searchParams);
|
||||||
|
|
||||||
if (select) {
|
if (select) {
|
||||||
query = query.select(select);
|
query = query.select(select);
|
||||||
|
|
@ -54,7 +51,7 @@ const getAssistants = async (searchParams, select = null) => {
|
||||||
* @returns {Promise<void>} Resolves when the assistant has been successfully deleted.
|
* @returns {Promise<void>} Resolves when the assistant has been successfully deleted.
|
||||||
*/
|
*/
|
||||||
const deleteAssistant = async (searchParams) => {
|
const deleteAssistant = async (searchParams) => {
|
||||||
return await Assistant.findOneAndDelete(searchParams);
|
return await db.models.Assistant.findOneAndDelete(searchParams);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
const mongoose = require('mongoose');
|
|
||||||
const { balanceSchema } = require('@librechat/data-schemas');
|
|
||||||
|
|
||||||
module.exports = mongoose.model('Balance', balanceSchema);
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
const mongoose = require('mongoose');
|
|
||||||
const logger = require('~/config/winston');
|
const logger = require('~/config/winston');
|
||||||
const { bannerSchema } = require('@librechat/data-schemas');
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
const Banner = mongoose.model('Banner', bannerSchema);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the current active banner.
|
* Retrieves the current active banner.
|
||||||
|
|
@ -11,7 +8,7 @@ const Banner = mongoose.model('Banner', bannerSchema);
|
||||||
const getBanner = async (user) => {
|
const getBanner = async (user) => {
|
||||||
try {
|
try {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const banner = await Banner.findOne({
|
const banner = await db.models.Banner.findOne({
|
||||||
displayFrom: { $lte: now },
|
displayFrom: { $lte: now },
|
||||||
$or: [{ displayTo: { $gte: now } }, { displayTo: null }],
|
$or: [{ displayTo: { $gte: now } }, { displayTo: null }],
|
||||||
type: 'banner',
|
type: 'banner',
|
||||||
|
|
@ -28,4 +25,4 @@ const getBanner = async (user) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = { Banner, getBanner };
|
module.exports = { getBanner };
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
const Conversation = require('./schema/convoSchema');
|
|
||||||
const { getMessages, deleteMessages } = require('./Message');
|
const { getMessages, deleteMessages } = require('./Message');
|
||||||
const logger = require('~/config/winston');
|
const logger = require('~/config/winston');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
/**
|
/**
|
||||||
* Searches for a conversation by conversationId and returns a lean document with only conversationId and user.
|
* Searches for a conversation by conversationId and returns a lean document with only conversationId and user.
|
||||||
* @param {string} conversationId - The conversation's ID.
|
* @param {string} conversationId - The conversation's ID.
|
||||||
|
|
@ -9,7 +8,7 @@ const logger = require('~/config/winston');
|
||||||
*/
|
*/
|
||||||
const searchConversation = async (conversationId) => {
|
const searchConversation = async (conversationId) => {
|
||||||
try {
|
try {
|
||||||
return await Conversation.findOne({ conversationId }, 'conversationId user').lean();
|
return await db.models.Conversation.findOne({ conversationId }, 'conversationId user').lean();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[searchConversation] Error searching conversation', error);
|
logger.error('[searchConversation] Error searching conversation', error);
|
||||||
throw new Error('Error searching conversation');
|
throw new Error('Error searching conversation');
|
||||||
|
|
@ -24,7 +23,7 @@ const searchConversation = async (conversationId) => {
|
||||||
*/
|
*/
|
||||||
const getConvo = async (user, conversationId) => {
|
const getConvo = async (user, conversationId) => {
|
||||||
try {
|
try {
|
||||||
return await Conversation.findOne({ user, conversationId }).lean();
|
return await db.models.Conversation.findOne({ user, conversationId }).lean();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[getConvo] Error getting single conversation', error);
|
logger.error('[getConvo] Error getting single conversation', error);
|
||||||
return { message: 'Error getting single conversation' };
|
return { message: 'Error getting single conversation' };
|
||||||
|
|
@ -41,7 +40,7 @@ const deleteNullOrEmptyConversations = async () => {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await Conversation.deleteMany(filter);
|
const result = await db.models.Conversation.deleteMany(filter);
|
||||||
|
|
||||||
// Delete associated messages
|
// Delete associated messages
|
||||||
const messageDeleteResult = await deleteMessages(filter);
|
const messageDeleteResult = await deleteMessages(filter);
|
||||||
|
|
@ -67,7 +66,7 @@ const deleteNullOrEmptyConversations = async () => {
|
||||||
*/
|
*/
|
||||||
const getConvoFiles = async (conversationId) => {
|
const getConvoFiles = async (conversationId) => {
|
||||||
try {
|
try {
|
||||||
return (await Conversation.findOne({ conversationId }, 'files').lean())?.files ?? [];
|
return (await db.models.Conversation.findOne({ conversationId }, 'files').lean())?.files ?? [];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[getConvoFiles] Error getting conversation files', error);
|
logger.error('[getConvoFiles] Error getting conversation files', error);
|
||||||
throw new Error('Error getting conversation files');
|
throw new Error('Error getting conversation files');
|
||||||
|
|
@ -75,7 +74,6 @@ const getConvoFiles = async (conversationId) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Conversation,
|
|
||||||
getConvoFiles,
|
getConvoFiles,
|
||||||
searchConversation,
|
searchConversation,
|
||||||
deleteNullOrEmptyConversations,
|
deleteNullOrEmptyConversations,
|
||||||
|
|
@ -114,7 +112,7 @@ module.exports = {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Note: the resulting Model object is necessary for Meilisearch operations */
|
/** Note: the resulting Model object is necessary for Meilisearch operations */
|
||||||
const conversation = await Conversation.findOneAndUpdate(
|
const conversation = await db.models.Conversation.findOneAndUpdate(
|
||||||
{ conversationId, user: req.user.id },
|
{ conversationId, user: req.user.id },
|
||||||
updateOperation,
|
updateOperation,
|
||||||
{
|
{
|
||||||
|
|
@ -143,7 +141,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const result = await Conversation.bulkWrite(bulkOps);
|
const result = await db.models.Conversation.bulkWrite(bulkOps);
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[saveBulkConversations] Error saving conversations in bulk', error);
|
logger.error('[saveBulkConversations] Error saving conversations in bulk', error);
|
||||||
|
|
@ -155,7 +153,7 @@ module.exports = {
|
||||||
{ cursor, limit = 25, isArchived = false, tags, search, order = 'desc' } = {},
|
{ cursor, limit = 25, isArchived = false, tags, search, order = 'desc' } = {},
|
||||||
) => {
|
) => {
|
||||||
const filters = [{ user }];
|
const filters = [{ user }];
|
||||||
|
const { Conversation } = db.models;
|
||||||
if (isArchived) {
|
if (isArchived) {
|
||||||
filters.push({ isArchived: true });
|
filters.push({ isArchived: true });
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -219,7 +217,7 @@ module.exports = {
|
||||||
|
|
||||||
const conversationIds = convoIds.map((convo) => convo.conversationId);
|
const conversationIds = convoIds.map((convo) => convo.conversationId);
|
||||||
|
|
||||||
const results = await Conversation.find({
|
const results = await db.models.Conversation.find({
|
||||||
user,
|
user,
|
||||||
conversationId: { $in: conversationIds },
|
conversationId: { $in: conversationIds },
|
||||||
$or: [{ expiredAt: { $exists: false } }, { expiredAt: null }],
|
$or: [{ expiredAt: { $exists: false } }, { expiredAt: null }],
|
||||||
|
|
@ -288,7 +286,7 @@ module.exports = {
|
||||||
deleteConvos: async (user, filter) => {
|
deleteConvos: async (user, filter) => {
|
||||||
try {
|
try {
|
||||||
const userFilter = { ...filter, user };
|
const userFilter = { ...filter, user };
|
||||||
|
const { Conversation } = db.models;
|
||||||
const conversations = await Conversation.find(userFilter).select('conversationId');
|
const conversations = await Conversation.find(userFilter).select('conversationId');
|
||||||
const conversationIds = conversations.map((c) => c.conversationId);
|
const conversationIds = conversations.map((c) => c.conversationId);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,5 @@
|
||||||
const mongoose = require('mongoose');
|
|
||||||
const Conversation = require('./schema/convoSchema');
|
|
||||||
const logger = require('~/config/winston');
|
const logger = require('~/config/winston');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
const { conversationTagSchema } = require('@librechat/data-schemas');
|
|
||||||
|
|
||||||
const ConversationTag = mongoose.model('ConversationTag', conversationTagSchema);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves all conversation tags for a user.
|
* Retrieves all conversation tags for a user.
|
||||||
|
|
@ -13,7 +8,7 @@ const ConversationTag = mongoose.model('ConversationTag', conversationTagSchema)
|
||||||
*/
|
*/
|
||||||
const getConversationTags = async (user) => {
|
const getConversationTags = async (user) => {
|
||||||
try {
|
try {
|
||||||
return await ConversationTag.find({ user }).sort({ position: 1 }).lean();
|
return await db.models.ConversationTag.find({ user }).sort({ position: 1 }).lean();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[getConversationTags] Error getting conversation tags', error);
|
logger.error('[getConversationTags] Error getting conversation tags', error);
|
||||||
throw new Error('Error getting conversation tags');
|
throw new Error('Error getting conversation tags');
|
||||||
|
|
@ -34,6 +29,7 @@ const createConversationTag = async (user, data) => {
|
||||||
try {
|
try {
|
||||||
const { tag, description, addToConversation, conversationId } = data;
|
const { tag, description, addToConversation, conversationId } = data;
|
||||||
|
|
||||||
|
const { ConversationTag, Conversation } = db.models;
|
||||||
const existingTag = await ConversationTag.findOne({ user, tag }).lean();
|
const existingTag = await ConversationTag.findOne({ user, tag }).lean();
|
||||||
if (existingTag) {
|
if (existingTag) {
|
||||||
return existingTag;
|
return existingTag;
|
||||||
|
|
@ -88,6 +84,7 @@ const updateConversationTag = async (user, oldTag, data) => {
|
||||||
try {
|
try {
|
||||||
const { tag: newTag, description, position } = data;
|
const { tag: newTag, description, position } = data;
|
||||||
|
|
||||||
|
const { ConversationTag, Conversation } = db.models;
|
||||||
const existingTag = await ConversationTag.findOne({ user, tag: oldTag }).lean();
|
const existingTag = await ConversationTag.findOne({ user, tag: oldTag }).lean();
|
||||||
if (!existingTag) {
|
if (!existingTag) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -140,15 +137,15 @@ const adjustPositions = async (user, oldPosition, newPosition) => {
|
||||||
const position =
|
const position =
|
||||||
oldPosition < newPosition
|
oldPosition < newPosition
|
||||||
? {
|
? {
|
||||||
$gt: Math.min(oldPosition, newPosition),
|
$gt: Math.min(oldPosition, newPosition),
|
||||||
$lte: Math.max(oldPosition, newPosition),
|
$lte: Math.max(oldPosition, newPosition),
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
$gte: Math.min(oldPosition, newPosition),
|
$gte: Math.min(oldPosition, newPosition),
|
||||||
$lt: Math.max(oldPosition, newPosition),
|
$lt: Math.max(oldPosition, newPosition),
|
||||||
};
|
};
|
||||||
|
|
||||||
await ConversationTag.updateMany(
|
await db.models.ConversationTag.updateMany(
|
||||||
{
|
{
|
||||||
user,
|
user,
|
||||||
position,
|
position,
|
||||||
|
|
@ -165,6 +162,7 @@ const adjustPositions = async (user, oldPosition, newPosition) => {
|
||||||
*/
|
*/
|
||||||
const deleteConversationTag = async (user, tag) => {
|
const deleteConversationTag = async (user, tag) => {
|
||||||
try {
|
try {
|
||||||
|
const { ConversationTag, Conversation } = db.models;
|
||||||
const deletedTag = await ConversationTag.findOneAndDelete({ user, tag }).lean();
|
const deletedTag = await ConversationTag.findOneAndDelete({ user, tag }).lean();
|
||||||
if (!deletedTag) {
|
if (!deletedTag) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -193,6 +191,7 @@ const deleteConversationTag = async (user, tag) => {
|
||||||
*/
|
*/
|
||||||
const updateTagsForConversation = async (user, conversationId, tags) => {
|
const updateTagsForConversation = async (user, conversationId, tags) => {
|
||||||
try {
|
try {
|
||||||
|
const { ConversationTag, Conversation } = db.models;
|
||||||
const conversation = await Conversation.findOne({ user, conversationId }).lean();
|
const conversation = await Conversation.findOne({ user, conversationId }).lean();
|
||||||
if (!conversation) {
|
if (!conversation) {
|
||||||
throw new Error('Conversation not found');
|
throw new Error('Conversation not found');
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
const { EToolResources } = require('librechat-data-provider');
|
const { EToolResources } = require('librechat-data-provider');
|
||||||
const { fileSchema } = require('@librechat/data-schemas');
|
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
const File = mongoose.model('File', fileSchema);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds a file by its file_id with additional query options.
|
* Finds a file by its file_id with additional query options.
|
||||||
|
|
@ -12,7 +10,7 @@ const File = mongoose.model('File', fileSchema);
|
||||||
* @returns {Promise<MongoFile>} A promise that resolves to the file document or null.
|
* @returns {Promise<MongoFile>} A promise that resolves to the file document or null.
|
||||||
*/
|
*/
|
||||||
const findFileById = async (file_id, options = {}) => {
|
const findFileById = async (file_id, options = {}) => {
|
||||||
return await File.findOne({ file_id, ...options }).lean();
|
return await db.models.File.findOne({ file_id, ...options }).lean();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -25,7 +23,7 @@ const findFileById = async (file_id, options = {}) => {
|
||||||
*/
|
*/
|
||||||
const getFiles = async (filter, _sortOptions, selectFields = { text: 0 }) => {
|
const getFiles = async (filter, _sortOptions, selectFields = { text: 0 }) => {
|
||||||
const sortOptions = { updatedAt: -1, ..._sortOptions };
|
const sortOptions = { updatedAt: -1, ..._sortOptions };
|
||||||
return await File.find(filter).select(selectFields).sort(sortOptions).lean();
|
return await db.models.File.find(filter).select(selectFields).sort(sortOptions).lean();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -81,7 +79,7 @@ const createFile = async (data, disableTTL) => {
|
||||||
delete fileData.expiresAt;
|
delete fileData.expiresAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await File.findOneAndUpdate({ file_id: data.file_id }, fileData, {
|
return await db.models.File.findOneAndUpdate({ file_id: data.file_id }, fileData, {
|
||||||
new: true,
|
new: true,
|
||||||
upsert: true,
|
upsert: true,
|
||||||
}).lean();
|
}).lean();
|
||||||
|
|
@ -98,7 +96,7 @@ const updateFile = async (data) => {
|
||||||
$set: update,
|
$set: update,
|
||||||
$unset: { expiresAt: '' }, // Remove the expiresAt field to prevent TTL
|
$unset: { expiresAt: '' }, // Remove the expiresAt field to prevent TTL
|
||||||
};
|
};
|
||||||
return await File.findOneAndUpdate({ file_id }, updateOperation, { new: true }).lean();
|
return await db.models.File.findOneAndUpdate({ file_id }, updateOperation, { new: true }).lean();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -112,7 +110,7 @@ const updateFileUsage = async (data) => {
|
||||||
$inc: { usage: inc },
|
$inc: { usage: inc },
|
||||||
$unset: { expiresAt: '', temp_file_id: '' },
|
$unset: { expiresAt: '', temp_file_id: '' },
|
||||||
};
|
};
|
||||||
return await File.findOneAndUpdate({ file_id }, updateOperation, { new: true }).lean();
|
return await db.models.File.findOneAndUpdate({ file_id }, updateOperation, { new: true }).lean();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -121,7 +119,7 @@ const updateFileUsage = async (data) => {
|
||||||
* @returns {Promise<MongoFile>} A promise that resolves to the deleted file document or null.
|
* @returns {Promise<MongoFile>} A promise that resolves to the deleted file document or null.
|
||||||
*/
|
*/
|
||||||
const deleteFile = async (file_id) => {
|
const deleteFile = async (file_id) => {
|
||||||
return await File.findOneAndDelete({ file_id }).lean();
|
return await db.models.File.findOneAndDelete({ file_id }).lean();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -130,7 +128,7 @@ const deleteFile = async (file_id) => {
|
||||||
* @returns {Promise<MongoFile>} A promise that resolves to the deleted file document or null.
|
* @returns {Promise<MongoFile>} A promise that resolves to the deleted file document or null.
|
||||||
*/
|
*/
|
||||||
const deleteFileByFilter = async (filter) => {
|
const deleteFileByFilter = async (filter) => {
|
||||||
return await File.findOneAndDelete(filter).lean();
|
return await db.models.File.findOneAndDelete(filter).lean();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -143,7 +141,7 @@ const deleteFiles = async (file_ids, user) => {
|
||||||
if (user) {
|
if (user) {
|
||||||
deleteQuery = { user: user };
|
deleteQuery = { user: user };
|
||||||
}
|
}
|
||||||
return await File.deleteMany(deleteQuery);
|
return await db.models.File.deleteMany(deleteQuery);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -169,7 +167,6 @@ async function batchUpdateFiles(updates) {
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
File,
|
|
||||||
findFileById,
|
findFileById,
|
||||||
getFiles,
|
getFiles,
|
||||||
getToolFilesByIds,
|
getToolFilesByIds,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
const mongoose = require('mongoose');
|
|
||||||
const { keySchema } = require('@librechat/data-schemas');
|
|
||||||
|
|
||||||
module.exports = mongoose.model('Key', keySchema);
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
const { z } = require('zod');
|
const { z } = require('zod');
|
||||||
const Message = require('./schema/messageSchema');
|
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
const idSchema = z.string().uuid();
|
const idSchema = z.string().uuid();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -68,8 +67,7 @@ async function saveMessage(req, params, metadata) {
|
||||||
logger.info(`---\`saveMessage\` context: ${metadata?.context}`);
|
logger.info(`---\`saveMessage\` context: ${metadata?.context}`);
|
||||||
update.tokenCount = 0;
|
update.tokenCount = 0;
|
||||||
}
|
}
|
||||||
|
const message = await db.models.Message.findOneAndUpdate(
|
||||||
const message = await Message.findOneAndUpdate(
|
|
||||||
{ messageId: params.messageId, user: req.user.id },
|
{ messageId: params.messageId, user: req.user.id },
|
||||||
update,
|
update,
|
||||||
{ upsert: true, new: true },
|
{ upsert: true, new: true },
|
||||||
|
|
@ -87,7 +85,7 @@ async function saveMessage(req, params, metadata) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Try to find the existing message with this ID
|
// Try to find the existing message with this ID
|
||||||
const existingMessage = await Message.findOne({
|
const existingMessage = await db.models.Message.findOne({
|
||||||
messageId: params.messageId,
|
messageId: params.messageId,
|
||||||
user: req.user.id,
|
user: req.user.id,
|
||||||
});
|
});
|
||||||
|
|
@ -140,8 +138,7 @@ async function bulkSaveMessages(messages, overrideTimestamp = false) {
|
||||||
upsert: true,
|
upsert: true,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
const result = await db.models.Message.bulkWrite(bulkOps);
|
||||||
const result = await Message.bulkWrite(bulkOps);
|
|
||||||
return result;
|
return result;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('Error saving messages in bulk:', err);
|
logger.error('Error saving messages in bulk:', err);
|
||||||
|
|
@ -183,7 +180,7 @@ async function recordMessage({
|
||||||
...rest,
|
...rest,
|
||||||
};
|
};
|
||||||
|
|
||||||
return await Message.findOneAndUpdate({ user, messageId }, message, {
|
return await db.models.Message.findOneAndUpdate({ user, messageId }, message, {
|
||||||
upsert: true,
|
upsert: true,
|
||||||
new: true,
|
new: true,
|
||||||
});
|
});
|
||||||
|
|
@ -207,7 +204,7 @@ async function recordMessage({
|
||||||
*/
|
*/
|
||||||
async function updateMessageText(req, { messageId, text }) {
|
async function updateMessageText(req, { messageId, text }) {
|
||||||
try {
|
try {
|
||||||
await Message.updateOne({ messageId, user: req.user.id }, { text });
|
await db.models?.Message.updateOne({ messageId, user: req.user.id }, { text });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('Error updating message text:', err);
|
logger.error('Error updating message text:', err);
|
||||||
throw err;
|
throw err;
|
||||||
|
|
@ -235,7 +232,7 @@ async function updateMessageText(req, { messageId, text }) {
|
||||||
async function updateMessage(req, message, metadata) {
|
async function updateMessage(req, message, metadata) {
|
||||||
try {
|
try {
|
||||||
const { messageId, ...update } = message;
|
const { messageId, ...update } = message;
|
||||||
const updatedMessage = await Message.findOneAndUpdate(
|
const updatedMessage = await db.models.Message.findOneAndUpdate(
|
||||||
{ messageId, user: req.user.id },
|
{ messageId, user: req.user.id },
|
||||||
update,
|
update,
|
||||||
{
|
{
|
||||||
|
|
@ -279,10 +276,10 @@ async function updateMessage(req, message, metadata) {
|
||||||
*/
|
*/
|
||||||
async function deleteMessagesSince(req, { messageId, conversationId }) {
|
async function deleteMessagesSince(req, { messageId, conversationId }) {
|
||||||
try {
|
try {
|
||||||
const message = await Message.findOne({ messageId, user: req.user.id }).lean();
|
const message = await db.models.Message.findOne({ messageId, user: req.user.id }).lean();
|
||||||
|
|
||||||
if (message) {
|
if (message) {
|
||||||
const query = Message.find({ conversationId, user: req.user.id });
|
const query = db.models.Message.find({ conversationId, user: req.user.id });
|
||||||
return await query.deleteMany({
|
return await query.deleteMany({
|
||||||
createdAt: { $gt: message.createdAt },
|
createdAt: { $gt: message.createdAt },
|
||||||
});
|
});
|
||||||
|
|
@ -306,10 +303,10 @@ async function deleteMessagesSince(req, { messageId, conversationId }) {
|
||||||
async function getMessages(filter, select) {
|
async function getMessages(filter, select) {
|
||||||
try {
|
try {
|
||||||
if (select) {
|
if (select) {
|
||||||
return await Message.find(filter).select(select).sort({ createdAt: 1 }).lean();
|
return await db.models.Message.find(filter).select(select).sort({ createdAt: 1 }).lean();
|
||||||
}
|
}
|
||||||
|
|
||||||
return await Message.find(filter).sort({ createdAt: 1 }).lean();
|
return await db.models.Message.find(filter).sort({ createdAt: 1 }).lean();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('Error getting messages:', err);
|
logger.error('Error getting messages:', err);
|
||||||
throw err;
|
throw err;
|
||||||
|
|
@ -326,7 +323,7 @@ async function getMessages(filter, select) {
|
||||||
*/
|
*/
|
||||||
async function getMessage({ user, messageId }) {
|
async function getMessage({ user, messageId }) {
|
||||||
try {
|
try {
|
||||||
return await Message.findOne({
|
return await db.models.Message.findOne({
|
||||||
user,
|
user,
|
||||||
messageId,
|
messageId,
|
||||||
}).lean();
|
}).lean();
|
||||||
|
|
@ -347,7 +344,7 @@ async function getMessage({ user, messageId }) {
|
||||||
*/
|
*/
|
||||||
async function deleteMessages(filter) {
|
async function deleteMessages(filter) {
|
||||||
try {
|
try {
|
||||||
return await Message.deleteMany(filter);
|
return await db.models.Message.deleteMany(filter);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('Error deleting messages:', err);
|
logger.error('Error deleting messages:', err);
|
||||||
throw err;
|
throw err;
|
||||||
|
|
@ -355,7 +352,6 @@ async function deleteMessages(filter) {
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Message,
|
|
||||||
saveMessage,
|
saveMessage,
|
||||||
bulkSaveMessages,
|
bulkSaveMessages,
|
||||||
recordMessage,
|
recordMessage,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
const { v4: uuidv4 } = require('uuid');
|
const { v4: uuidv4 } = require('uuid');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
jest.mock('mongoose');
|
jest.mock('mongoose');
|
||||||
|
|
||||||
|
|
@ -20,14 +21,28 @@ const mockSchema = {
|
||||||
deleteMany: jest.fn(),
|
deleteMany: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
mongoose.model.mockReturnValue(mockSchema);
|
|
||||||
|
|
||||||
jest.mock('~/models/schema/messageSchema', () => mockSchema);
|
|
||||||
|
|
||||||
jest.mock('~/config/winston', () => ({
|
jest.mock('~/config/winston', () => ({
|
||||||
error: jest.fn(),
|
error: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const mockModels = {
|
||||||
|
Message: {
|
||||||
|
findOneAndUpdate: mockSchema.findOneAndUpdate,
|
||||||
|
updateOne: mockSchema.updateOne,
|
||||||
|
findOne: mockSchema.findOne,
|
||||||
|
find: mockSchema.find,
|
||||||
|
deleteMany: mockSchema.deleteMany,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.mock('~/lib/db/connectDb', () => {
|
||||||
|
return {
|
||||||
|
get models() {
|
||||||
|
return mockModels;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
saveMessage,
|
saveMessage,
|
||||||
getMessages,
|
getMessages,
|
||||||
|
|
@ -153,7 +168,7 @@ describe('Message Operations', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Conversation Hijacking Prevention', () => {
|
describe('Conversation Hijacking Prevention', () => {
|
||||||
it('should not allow editing a message in another user\'s conversation', async () => {
|
it("should not allow editing a message in another user's conversation", async () => {
|
||||||
const attackerReq = { user: { id: 'attacker123' } };
|
const attackerReq = { user: { id: 'attacker123' } };
|
||||||
const victimConversationId = 'victim-convo-123';
|
const victimConversationId = 'victim-convo-123';
|
||||||
const victimMessageId = 'victim-msg-123';
|
const victimMessageId = 'victim-msg-123';
|
||||||
|
|
@ -175,7 +190,7 @@ describe('Message Operations', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not allow deleting messages from another user\'s conversation', async () => {
|
it("should not allow deleting messages from another user's conversation", async () => {
|
||||||
const attackerReq = { user: { id: 'attacker123' } };
|
const attackerReq = { user: { id: 'attacker123' } };
|
||||||
const victimConversationId = 'victim-convo-123';
|
const victimConversationId = 'victim-convo-123';
|
||||||
const victimMessageId = 'victim-msg-123';
|
const victimMessageId = 'victim-msg-123';
|
||||||
|
|
@ -193,7 +208,7 @@ describe('Message Operations', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not allow inserting a new message into another user\'s conversation', async () => {
|
it("should not allow inserting a new message into another user's conversation", async () => {
|
||||||
const attackerReq = { user: { id: 'attacker123' } };
|
const attackerReq = { user: { id: 'attacker123' } };
|
||||||
const victimConversationId = uuidv4(); // Use a valid UUID
|
const victimConversationId = uuidv4(); // Use a valid UUID
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
const Preset = require('./schema/presetSchema');
|
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
const getPreset = async (user, presetId) => {
|
const getPreset = async (user, presetId) => {
|
||||||
try {
|
try {
|
||||||
return await Preset.findOne({ user, presetId }).lean();
|
return await db.models.Preset.findOne({ user, presetId }).lean();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[getPreset] Error getting single preset', error);
|
logger.error('[getPreset] Error getting single preset', error);
|
||||||
return { message: 'Error getting single preset' };
|
return { message: 'Error getting single preset' };
|
||||||
|
|
@ -11,11 +11,10 @@ const getPreset = async (user, presetId) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Preset,
|
|
||||||
getPreset,
|
getPreset,
|
||||||
getPresets: async (user, filter) => {
|
getPresets: async (user, filter) => {
|
||||||
try {
|
try {
|
||||||
const presets = await Preset.find({ ...filter, user }).lean();
|
const presets = await db.models.Preset.find({ ...filter, user }).lean();
|
||||||
const defaultValue = 10000;
|
const defaultValue = 10000;
|
||||||
|
|
||||||
presets.sort((a, b) => {
|
presets.sort((a, b) => {
|
||||||
|
|
@ -40,6 +39,7 @@ module.exports = {
|
||||||
const setter = { $set: {} };
|
const setter = { $set: {} };
|
||||||
const { user: _, ...cleanPreset } = preset;
|
const { user: _, ...cleanPreset } = preset;
|
||||||
const update = { presetId, ...cleanPreset };
|
const update = { presetId, ...cleanPreset };
|
||||||
|
const Preset = db.models.Preset;
|
||||||
if (preset.tools && Array.isArray(preset.tools)) {
|
if (preset.tools && Array.isArray(preset.tools)) {
|
||||||
update.tools =
|
update.tools =
|
||||||
preset.tools
|
preset.tools
|
||||||
|
|
@ -77,7 +77,7 @@ module.exports = {
|
||||||
deletePresets: async (user, filter) => {
|
deletePresets: async (user, filter) => {
|
||||||
// let toRemove = await Preset.find({ ...filter, user }).select('presetId');
|
// let toRemove = await Preset.find({ ...filter, user }).select('presetId');
|
||||||
// const ids = toRemove.map((instance) => instance.presetId);
|
// const ids = toRemove.map((instance) => instance.presetId);
|
||||||
let deleteCount = await Preset.deleteMany({ ...filter, user });
|
let deleteCount = await db.models.Preset.deleteMany({ ...filter, user });
|
||||||
return deleteCount;
|
return deleteCount;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
const { model } = require('mongoose');
|
|
||||||
const { GLOBAL_PROJECT_NAME } = require('librechat-data-provider').Constants;
|
const { GLOBAL_PROJECT_NAME } = require('librechat-data-provider').Constants;
|
||||||
const { projectSchema } = require('@librechat/data-schemas');
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
const Project = model('Project', projectSchema);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve a project by ID and convert the found project document to a plain object.
|
* Retrieve a project by ID and convert the found project document to a plain object.
|
||||||
|
|
@ -12,7 +9,7 @@ const Project = model('Project', projectSchema);
|
||||||
* @returns {Promise<IMongoProject>} A plain object representing the project document, or `null` if no project is found.
|
* @returns {Promise<IMongoProject>} A plain object representing the project document, or `null` if no project is found.
|
||||||
*/
|
*/
|
||||||
const getProjectById = async function (projectId, fieldsToSelect = null) {
|
const getProjectById = async function (projectId, fieldsToSelect = null) {
|
||||||
const query = Project.findById(projectId);
|
const query = db.models.Project.findById(projectId);
|
||||||
|
|
||||||
if (fieldsToSelect) {
|
if (fieldsToSelect) {
|
||||||
query.select(fieldsToSelect);
|
query.select(fieldsToSelect);
|
||||||
|
|
@ -39,7 +36,7 @@ const getProjectByName = async function (projectName, fieldsToSelect = null) {
|
||||||
select: fieldsToSelect,
|
select: fieldsToSelect,
|
||||||
};
|
};
|
||||||
|
|
||||||
return await Project.findOneAndUpdate(query, update, options);
|
return await db.models.Project.findOneAndUpdate(query, update, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -50,7 +47,7 @@ const getProjectByName = async function (projectName, fieldsToSelect = null) {
|
||||||
* @returns {Promise<IMongoProject>} The updated project document.
|
* @returns {Promise<IMongoProject>} The updated project document.
|
||||||
*/
|
*/
|
||||||
const addGroupIdsToProject = async function (projectId, promptGroupIds) {
|
const addGroupIdsToProject = async function (projectId, promptGroupIds) {
|
||||||
return await Project.findByIdAndUpdate(
|
return await db.models.Project.findByIdAndUpdate(
|
||||||
projectId,
|
projectId,
|
||||||
{ $addToSet: { promptGroupIds: { $each: promptGroupIds } } },
|
{ $addToSet: { promptGroupIds: { $each: promptGroupIds } } },
|
||||||
{ new: true },
|
{ new: true },
|
||||||
|
|
@ -65,7 +62,7 @@ const addGroupIdsToProject = async function (projectId, promptGroupIds) {
|
||||||
* @returns {Promise<IMongoProject>} The updated project document.
|
* @returns {Promise<IMongoProject>} The updated project document.
|
||||||
*/
|
*/
|
||||||
const removeGroupIdsFromProject = async function (projectId, promptGroupIds) {
|
const removeGroupIdsFromProject = async function (projectId, promptGroupIds) {
|
||||||
return await Project.findByIdAndUpdate(
|
return await db.models.Project.findByIdAndUpdate(
|
||||||
projectId,
|
projectId,
|
||||||
{ $pull: { promptGroupIds: { $in: promptGroupIds } } },
|
{ $pull: { promptGroupIds: { $in: promptGroupIds } } },
|
||||||
{ new: true },
|
{ new: true },
|
||||||
|
|
@ -79,7 +76,7 @@ const removeGroupIdsFromProject = async function (projectId, promptGroupIds) {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
const removeGroupFromAllProjects = async (promptGroupId) => {
|
const removeGroupFromAllProjects = async (promptGroupId) => {
|
||||||
await Project.updateMany({}, { $pull: { promptGroupIds: promptGroupId } });
|
await db.models.Project.updateMany({}, { $pull: { promptGroupIds: promptGroupId } });
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -90,7 +87,7 @@ const removeGroupFromAllProjects = async (promptGroupId) => {
|
||||||
* @returns {Promise<IMongoProject>} The updated project document.
|
* @returns {Promise<IMongoProject>} The updated project document.
|
||||||
*/
|
*/
|
||||||
const addAgentIdsToProject = async function (projectId, agentIds) {
|
const addAgentIdsToProject = async function (projectId, agentIds) {
|
||||||
return await Project.findByIdAndUpdate(
|
return await db.models.Project.findByIdAndUpdate(
|
||||||
projectId,
|
projectId,
|
||||||
{ $addToSet: { agentIds: { $each: agentIds } } },
|
{ $addToSet: { agentIds: { $each: agentIds } } },
|
||||||
{ new: true },
|
{ new: true },
|
||||||
|
|
@ -105,7 +102,7 @@ const addAgentIdsToProject = async function (projectId, agentIds) {
|
||||||
* @returns {Promise<IMongoProject>} The updated project document.
|
* @returns {Promise<IMongoProject>} The updated project document.
|
||||||
*/
|
*/
|
||||||
const removeAgentIdsFromProject = async function (projectId, agentIds) {
|
const removeAgentIdsFromProject = async function (projectId, agentIds) {
|
||||||
return await Project.findByIdAndUpdate(
|
return await db.models.Project.findByIdAndUpdate(
|
||||||
projectId,
|
projectId,
|
||||||
{ $pull: { agentIds: { $in: agentIds } } },
|
{ $pull: { agentIds: { $in: agentIds } } },
|
||||||
{ new: true },
|
{ new: true },
|
||||||
|
|
@ -119,7 +116,7 @@ const removeAgentIdsFromProject = async function (projectId, agentIds) {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
const removeAgentFromAllProjects = async (agentId) => {
|
const removeAgentFromAllProjects = async (agentId) => {
|
||||||
await Project.updateMany({}, { $pull: { agentIds: agentId } });
|
await db.models.Project.updateMany({}, { $pull: { agentIds: agentId } });
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
const mongoose = require('mongoose');
|
|
||||||
const { ObjectId } = require('mongodb');
|
const { ObjectId } = require('mongodb');
|
||||||
const { SystemRoles, SystemCategories, Constants } = require('librechat-data-provider');
|
const { SystemRoles, SystemCategories, Constants } = require('librechat-data-provider');
|
||||||
const {
|
const {
|
||||||
|
|
@ -7,12 +6,9 @@ const {
|
||||||
removeGroupIdsFromProject,
|
removeGroupIdsFromProject,
|
||||||
removeGroupFromAllProjects,
|
removeGroupFromAllProjects,
|
||||||
} = require('./Project');
|
} = require('./Project');
|
||||||
const { promptGroupSchema, promptSchema } = require('@librechat/data-schemas');
|
|
||||||
const { escapeRegExp } = require('~/server/utils');
|
const { escapeRegExp } = require('~/server/utils');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
const PromptGroup = mongoose.model('PromptGroup', promptGroupSchema);
|
|
||||||
const Prompt = mongoose.model('Prompt', promptSchema);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a pipeline for the aggregation to get prompt groups
|
* Create a pipeline for the aggregation to get prompt groups
|
||||||
|
|
@ -137,7 +133,7 @@ const getAllPromptGroups = async (req, filter) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const promptGroupsPipeline = createAllGroupsPipeline(combinedQuery);
|
const promptGroupsPipeline = createAllGroupsPipeline(combinedQuery);
|
||||||
return await PromptGroup.aggregate(promptGroupsPipeline).exec();
|
return await db.models.PromptGroup.aggregate(promptGroupsPipeline).exec();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error getting all prompt groups', error);
|
console.error('Error getting all prompt groups', error);
|
||||||
return { message: 'Error getting all prompt groups' };
|
return { message: 'Error getting all prompt groups' };
|
||||||
|
|
@ -237,7 +233,7 @@ const deletePromptGroup = async ({ _id, author, role }) => {
|
||||||
throw new Error('Prompt group not found');
|
throw new Error('Prompt group not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
await Prompt.deleteMany(groupQuery);
|
await db.models.Prompt.deleteMany(groupQuery);
|
||||||
await removeGroupFromAllProjects(_id);
|
await removeGroupFromAllProjects(_id);
|
||||||
return { message: 'Prompt group deleted successfully' };
|
return { message: 'Prompt group deleted successfully' };
|
||||||
};
|
};
|
||||||
|
|
@ -254,6 +250,7 @@ module.exports = {
|
||||||
createPromptGroup: async (saveData) => {
|
createPromptGroup: async (saveData) => {
|
||||||
try {
|
try {
|
||||||
const { prompt, group, author, authorName } = saveData;
|
const { prompt, group, author, authorName } = saveData;
|
||||||
|
const { Prompt, PromptGroup } = db.models;
|
||||||
|
|
||||||
let newPromptGroup = await PromptGroup.findOneAndUpdate(
|
let newPromptGroup = await PromptGroup.findOneAndUpdate(
|
||||||
{ ...group, author, authorName, productionId: null },
|
{ ...group, author, authorName, productionId: null },
|
||||||
|
|
@ -309,6 +306,7 @@ module.exports = {
|
||||||
|
|
||||||
/** @type {TPrompt} */
|
/** @type {TPrompt} */
|
||||||
let newPrompt;
|
let newPrompt;
|
||||||
|
const { Prompt } = db.models;
|
||||||
try {
|
try {
|
||||||
newPrompt = await Prompt.create(newPromptData);
|
newPrompt = await Prompt.create(newPromptData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -328,7 +326,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
getPrompts: async (filter) => {
|
getPrompts: async (filter) => {
|
||||||
try {
|
try {
|
||||||
return await Prompt.find(filter).sort({ createdAt: -1 }).lean();
|
return await db.models.Prompt.find(filter).sort({ createdAt: -1 }).lean();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error getting prompts', error);
|
logger.error('Error getting prompts', error);
|
||||||
return { message: 'Error getting prompts' };
|
return { message: 'Error getting prompts' };
|
||||||
|
|
@ -339,7 +337,7 @@ module.exports = {
|
||||||
if (filter.groupId) {
|
if (filter.groupId) {
|
||||||
filter.groupId = new ObjectId(filter.groupId);
|
filter.groupId = new ObjectId(filter.groupId);
|
||||||
}
|
}
|
||||||
return await Prompt.findOne(filter).lean();
|
return await db.models.Prompt.findOne(filter).lean();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error getting prompt', error);
|
logger.error('Error getting prompt', error);
|
||||||
return { message: 'Error getting prompt' };
|
return { message: 'Error getting prompt' };
|
||||||
|
|
@ -352,7 +350,7 @@ module.exports = {
|
||||||
*/
|
*/
|
||||||
getRandomPromptGroups: async (filter) => {
|
getRandomPromptGroups: async (filter) => {
|
||||||
try {
|
try {
|
||||||
const result = await PromptGroup.aggregate([
|
const result = await db.models.PromptGroup.aggregate([
|
||||||
{
|
{
|
||||||
$match: {
|
$match: {
|
||||||
category: { $ne: '' },
|
category: { $ne: '' },
|
||||||
|
|
@ -385,7 +383,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
getPromptGroupsWithPrompts: async (filter) => {
|
getPromptGroupsWithPrompts: async (filter) => {
|
||||||
try {
|
try {
|
||||||
return await PromptGroup.findOne(filter)
|
return await db.models.PromptGroup.findOne(filter)
|
||||||
.populate({
|
.populate({
|
||||||
path: 'prompts',
|
path: 'prompts',
|
||||||
select: '-_id -__v -user',
|
select: '-_id -__v -user',
|
||||||
|
|
@ -399,7 +397,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
getPromptGroup: async (filter) => {
|
getPromptGroup: async (filter) => {
|
||||||
try {
|
try {
|
||||||
return await PromptGroup.findOne(filter).lean();
|
return await db.models.PromptGroup.findOne(filter).lean();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error getting prompt group', error);
|
logger.error('Error getting prompt group', error);
|
||||||
return { message: 'Error getting prompt group' };
|
return { message: 'Error getting prompt group' };
|
||||||
|
|
@ -420,6 +418,7 @@ module.exports = {
|
||||||
*/
|
*/
|
||||||
deletePrompt: async ({ promptId, groupId, author, role }) => {
|
deletePrompt: async ({ promptId, groupId, author, role }) => {
|
||||||
const query = { _id: promptId, groupId, author };
|
const query = { _id: promptId, groupId, author };
|
||||||
|
const { Prompt, PromptGroup } = db.models;
|
||||||
if (role === SystemRoles.ADMIN) {
|
if (role === SystemRoles.ADMIN) {
|
||||||
delete query.author;
|
delete query.author;
|
||||||
}
|
}
|
||||||
|
|
@ -484,7 +483,7 @@ module.exports = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateData = { ...data, ...updateOps };
|
const updateData = { ...data, ...updateOps };
|
||||||
const updatedDoc = await PromptGroup.findOneAndUpdate(filter, updateData, {
|
const updatedDoc = await db.models.PromptGroup.findOneAndUpdate(filter, updateData, {
|
||||||
new: true,
|
new: true,
|
||||||
upsert: false,
|
upsert: false,
|
||||||
});
|
});
|
||||||
|
|
@ -506,6 +505,7 @@ module.exports = {
|
||||||
*/
|
*/
|
||||||
makePromptProduction: async (promptId) => {
|
makePromptProduction: async (promptId) => {
|
||||||
try {
|
try {
|
||||||
|
const { Prompt, PromptGroup } = db.models;
|
||||||
const prompt = await Prompt.findById(promptId).lean();
|
const prompt = await Prompt.findById(promptId).lean();
|
||||||
|
|
||||||
if (!prompt) {
|
if (!prompt) {
|
||||||
|
|
@ -530,7 +530,7 @@ module.exports = {
|
||||||
},
|
},
|
||||||
updatePromptLabels: async (_id, labels) => {
|
updatePromptLabels: async (_id, labels) => {
|
||||||
try {
|
try {
|
||||||
const response = await Prompt.updateOne({ _id }, { $set: { labels } });
|
const response = await db.models.Prompt.updateOne({ _id }, { $set: { labels } });
|
||||||
if (response.matchedCount === 0) {
|
if (response.matchedCount === 0) {
|
||||||
return { message: 'Prompt not found' };
|
return { message: 'Prompt not found' };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
const mongoose = require('mongoose');
|
|
||||||
const {
|
const {
|
||||||
CacheKeys,
|
CacheKeys,
|
||||||
SystemRoles,
|
SystemRoles,
|
||||||
|
|
@ -8,10 +7,8 @@ const {
|
||||||
removeNullishValues,
|
removeNullishValues,
|
||||||
} = require('librechat-data-provider');
|
} = require('librechat-data-provider');
|
||||||
const getLogStores = require('~/cache/getLogStores');
|
const getLogStores = require('~/cache/getLogStores');
|
||||||
const { roleSchema } = require('@librechat/data-schemas');
|
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
const Role = mongoose.model('Role', roleSchema);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve a role by name and convert the found role document to a plain object.
|
* Retrieve a role by name and convert the found role document to a plain object.
|
||||||
|
|
@ -24,6 +21,7 @@ const Role = mongoose.model('Role', roleSchema);
|
||||||
*/
|
*/
|
||||||
const getRoleByName = async function (roleName, fieldsToSelect = null) {
|
const getRoleByName = async function (roleName, fieldsToSelect = null) {
|
||||||
const cache = getLogStores(CacheKeys.ROLES);
|
const cache = getLogStores(CacheKeys.ROLES);
|
||||||
|
const { Role } = db.models;
|
||||||
try {
|
try {
|
||||||
const cachedRole = await cache.get(roleName);
|
const cachedRole = await cache.get(roleName);
|
||||||
if (cachedRole) {
|
if (cachedRole) {
|
||||||
|
|
@ -57,7 +55,7 @@ const getRoleByName = async function (roleName, fieldsToSelect = null) {
|
||||||
const updateRoleByName = async function (roleName, updates) {
|
const updateRoleByName = async function (roleName, updates) {
|
||||||
const cache = getLogStores(CacheKeys.ROLES);
|
const cache = getLogStores(CacheKeys.ROLES);
|
||||||
try {
|
try {
|
||||||
const role = await Role.findOneAndUpdate(
|
const role = await db.models.Role.findOneAndUpdate(
|
||||||
{ name: roleName },
|
{ name: roleName },
|
||||||
{ $set: updates },
|
{ $set: updates },
|
||||||
{ new: true, lean: true },
|
{ new: true, lean: true },
|
||||||
|
|
@ -78,6 +76,7 @@ const updateRoleByName = async function (roleName, updates) {
|
||||||
* @param {Object.<PermissionTypes, Object.<Permissions, boolean>>} permissionsUpdate - Permissions to update and their values.
|
* @param {Object.<PermissionTypes, Object.<Permissions, boolean>>} permissionsUpdate - Permissions to update and their values.
|
||||||
*/
|
*/
|
||||||
async function updateAccessPermissions(roleName, permissionsUpdate) {
|
async function updateAccessPermissions(roleName, permissionsUpdate) {
|
||||||
|
const { Role } = db.models;
|
||||||
// Filter and clean the permission updates based on our schema definition.
|
// Filter and clean the permission updates based on our schema definition.
|
||||||
const updates = {};
|
const updates = {};
|
||||||
for (const [permissionType, permissions] of Object.entries(permissionsUpdate)) {
|
for (const [permissionType, permissions] of Object.entries(permissionsUpdate)) {
|
||||||
|
|
@ -181,6 +180,7 @@ async function updateAccessPermissions(roleName, permissionsUpdate) {
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
const initializeRoles = async function () {
|
const initializeRoles = async function () {
|
||||||
|
const { Role } = db.models;
|
||||||
for (const roleName of [SystemRoles.ADMIN, SystemRoles.USER]) {
|
for (const roleName of [SystemRoles.ADMIN, SystemRoles.USER]) {
|
||||||
let role = await Role.findOne({ name: roleName });
|
let role = await Role.findOne({ name: roleName });
|
||||||
const defaultPerms = roleDefaults[roleName].permissions;
|
const defaultPerms = roleDefaults[roleName].permissions;
|
||||||
|
|
@ -210,6 +210,7 @@ const initializeRoles = async function () {
|
||||||
* @returns {Promise<number>} Number of roles migrated.
|
* @returns {Promise<number>} Number of roles migrated.
|
||||||
*/
|
*/
|
||||||
const migrateRoleSchema = async function (roleName) {
|
const migrateRoleSchema = async function (roleName) {
|
||||||
|
const { Role } = db.models;
|
||||||
try {
|
try {
|
||||||
// Get roles to migrate
|
// Get roles to migrate
|
||||||
let roles;
|
let roles;
|
||||||
|
|
@ -282,7 +283,6 @@ const migrateRoleSchema = async function (roleName) {
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Role,
|
|
||||||
getRoleByName,
|
getRoleByName,
|
||||||
initializeRoles,
|
initializeRoles,
|
||||||
updateRoleByName,
|
updateRoleByName,
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,11 @@ const {
|
||||||
roleDefaults,
|
roleDefaults,
|
||||||
PermissionTypes,
|
PermissionTypes,
|
||||||
} = require('librechat-data-provider');
|
} = require('librechat-data-provider');
|
||||||
const { Role, getRoleByName, updateAccessPermissions, initializeRoles } = require('~/models/Role');
|
const { getRoleByName, updateAccessPermissions, initializeRoles } = require('~/models/Role');
|
||||||
const getLogStores = require('~/cache/getLogStores');
|
const getLogStores = require('~/cache/getLogStores');
|
||||||
|
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
// Mock the cache
|
// Mock the cache
|
||||||
jest.mock('~/cache/getLogStores', () =>
|
jest.mock('~/cache/getLogStores', () =>
|
||||||
jest.fn().mockReturnValue({
|
jest.fn().mockReturnValue({
|
||||||
|
|
@ -19,11 +21,14 @@ jest.mock('~/cache/getLogStores', () =>
|
||||||
);
|
);
|
||||||
|
|
||||||
let mongoServer;
|
let mongoServer;
|
||||||
|
let Role;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
mongoServer = await MongoMemoryServer.create();
|
mongoServer = await MongoMemoryServer.create();
|
||||||
const mongoUri = mongoServer.getUri();
|
const mongoUri = mongoServer.getUri();
|
||||||
await mongoose.connect(mongoUri);
|
await db.connectDb(mongoUri);
|
||||||
|
|
||||||
|
Role = db.models.Role;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|
|
||||||
|
|
@ -1,275 +0,0 @@
|
||||||
const mongoose = require('mongoose');
|
|
||||||
const signPayload = require('~/server/services/signPayload');
|
|
||||||
const { hashToken } = require('~/server/utils/crypto');
|
|
||||||
const { sessionSchema } = require('@librechat/data-schemas');
|
|
||||||
const { logger } = require('~/config');
|
|
||||||
|
|
||||||
const Session = mongoose.model('Session', sessionSchema);
|
|
||||||
|
|
||||||
const { REFRESH_TOKEN_EXPIRY } = process.env ?? {};
|
|
||||||
const expires = eval(REFRESH_TOKEN_EXPIRY) ?? 1000 * 60 * 60 * 24 * 7; // 7 days default
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error class for Session-related errors
|
|
||||||
*/
|
|
||||||
class SessionError extends Error {
|
|
||||||
constructor(message, code = 'SESSION_ERROR') {
|
|
||||||
super(message);
|
|
||||||
this.name = 'SessionError';
|
|
||||||
this.code = code;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new session for a user
|
|
||||||
* @param {string} userId - The ID of the user
|
|
||||||
* @param {Object} options - Additional options for session creation
|
|
||||||
* @param {Date} options.expiration - Custom expiration date
|
|
||||||
* @returns {Promise<{session: Session, refreshToken: string}>}
|
|
||||||
* @throws {SessionError}
|
|
||||||
*/
|
|
||||||
const createSession = async (userId, options = {}) => {
|
|
||||||
if (!userId) {
|
|
||||||
throw new SessionError('User ID is required', 'INVALID_USER_ID');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const session = new Session({
|
|
||||||
user: userId,
|
|
||||||
expiration: options.expiration || new Date(Date.now() + expires),
|
|
||||||
});
|
|
||||||
const refreshToken = await generateRefreshToken(session);
|
|
||||||
return { session, refreshToken };
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('[createSession] Error creating session:', error);
|
|
||||||
throw new SessionError('Failed to create session', 'CREATE_SESSION_FAILED');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds a session by various parameters
|
|
||||||
* @param {Object} params - Search parameters
|
|
||||||
* @param {string} [params.refreshToken] - The refresh token to search by
|
|
||||||
* @param {string} [params.userId] - The user ID to search by
|
|
||||||
* @param {string} [params.sessionId] - The session ID to search by
|
|
||||||
* @param {Object} [options] - Additional options
|
|
||||||
* @param {boolean} [options.lean=true] - Whether to return plain objects instead of documents
|
|
||||||
* @returns {Promise<Session|null>}
|
|
||||||
* @throws {SessionError}
|
|
||||||
*/
|
|
||||||
const findSession = async (params, options = { lean: true }) => {
|
|
||||||
try {
|
|
||||||
const query = {};
|
|
||||||
|
|
||||||
if (!params.refreshToken && !params.userId && !params.sessionId) {
|
|
||||||
throw new SessionError('At least one search parameter is required', 'INVALID_SEARCH_PARAMS');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.refreshToken) {
|
|
||||||
const tokenHash = await hashToken(params.refreshToken);
|
|
||||||
query.refreshTokenHash = tokenHash;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.userId) {
|
|
||||||
query.user = params.userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.sessionId) {
|
|
||||||
const sessionId = params.sessionId.sessionId || params.sessionId;
|
|
||||||
if (!mongoose.Types.ObjectId.isValid(sessionId)) {
|
|
||||||
throw new SessionError('Invalid session ID format', 'INVALID_SESSION_ID');
|
|
||||||
}
|
|
||||||
query._id = sessionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add expiration check to only return valid sessions
|
|
||||||
query.expiration = { $gt: new Date() };
|
|
||||||
|
|
||||||
const sessionQuery = Session.findOne(query);
|
|
||||||
|
|
||||||
if (options.lean) {
|
|
||||||
return await sessionQuery.lean();
|
|
||||||
}
|
|
||||||
|
|
||||||
return await sessionQuery.exec();
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('[findSession] Error finding session:', error);
|
|
||||||
throw new SessionError('Failed to find session', 'FIND_SESSION_FAILED');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates session expiration
|
|
||||||
* @param {Session|string} session - The session or session ID to update
|
|
||||||
* @param {Date} [newExpiration] - Optional new expiration date
|
|
||||||
* @returns {Promise<Session>}
|
|
||||||
* @throws {SessionError}
|
|
||||||
*/
|
|
||||||
const updateExpiration = async (session, newExpiration) => {
|
|
||||||
try {
|
|
||||||
const sessionDoc = typeof session === 'string' ? await Session.findById(session) : session;
|
|
||||||
|
|
||||||
if (!sessionDoc) {
|
|
||||||
throw new SessionError('Session not found', 'SESSION_NOT_FOUND');
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionDoc.expiration = newExpiration || new Date(Date.now() + expires);
|
|
||||||
return await sessionDoc.save();
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('[updateExpiration] Error updating session:', error);
|
|
||||||
throw new SessionError('Failed to update session expiration', 'UPDATE_EXPIRATION_FAILED');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a session by refresh token or session ID
|
|
||||||
* @param {Object} params - Delete parameters
|
|
||||||
* @param {string} [params.refreshToken] - The refresh token of the session to delete
|
|
||||||
* @param {string} [params.sessionId] - The ID of the session to delete
|
|
||||||
* @returns {Promise<Object>}
|
|
||||||
* @throws {SessionError}
|
|
||||||
*/
|
|
||||||
const deleteSession = async (params) => {
|
|
||||||
try {
|
|
||||||
if (!params.refreshToken && !params.sessionId) {
|
|
||||||
throw new SessionError(
|
|
||||||
'Either refreshToken or sessionId is required',
|
|
||||||
'INVALID_DELETE_PARAMS',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = {};
|
|
||||||
|
|
||||||
if (params.refreshToken) {
|
|
||||||
query.refreshTokenHash = await hashToken(params.refreshToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.sessionId) {
|
|
||||||
query._id = params.sessionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await Session.deleteOne(query);
|
|
||||||
|
|
||||||
if (result.deletedCount === 0) {
|
|
||||||
logger.warn('[deleteSession] No session found to delete');
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('[deleteSession] Error deleting session:', error);
|
|
||||||
throw new SessionError('Failed to delete session', 'DELETE_SESSION_FAILED');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes all sessions for a user
|
|
||||||
* @param {string} userId - The ID of the user
|
|
||||||
* @param {Object} [options] - Additional options
|
|
||||||
* @param {boolean} [options.excludeCurrentSession] - Whether to exclude the current session
|
|
||||||
* @param {string} [options.currentSessionId] - The ID of the current session to exclude
|
|
||||||
* @returns {Promise<Object>}
|
|
||||||
* @throws {SessionError}
|
|
||||||
*/
|
|
||||||
const deleteAllUserSessions = async (userId, options = {}) => {
|
|
||||||
try {
|
|
||||||
if (!userId) {
|
|
||||||
throw new SessionError('User ID is required', 'INVALID_USER_ID');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract userId if it's passed as an object
|
|
||||||
const userIdString = userId.userId || userId;
|
|
||||||
|
|
||||||
if (!mongoose.Types.ObjectId.isValid(userIdString)) {
|
|
||||||
throw new SessionError('Invalid user ID format', 'INVALID_USER_ID_FORMAT');
|
|
||||||
}
|
|
||||||
|
|
||||||
const query = { user: userIdString };
|
|
||||||
|
|
||||||
if (options.excludeCurrentSession && options.currentSessionId) {
|
|
||||||
query._id = { $ne: options.currentSessionId };
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await Session.deleteMany(query);
|
|
||||||
|
|
||||||
if (result.deletedCount > 0) {
|
|
||||||
logger.debug(
|
|
||||||
`[deleteAllUserSessions] Deleted ${result.deletedCount} sessions for user ${userIdString}.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('[deleteAllUserSessions] Error deleting user sessions:', error);
|
|
||||||
throw new SessionError('Failed to delete user sessions', 'DELETE_ALL_SESSIONS_FAILED');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a refresh token for a session
|
|
||||||
* @param {Session} session - The session to generate a token for
|
|
||||||
* @returns {Promise<string>}
|
|
||||||
* @throws {SessionError}
|
|
||||||
*/
|
|
||||||
const generateRefreshToken = async (session) => {
|
|
||||||
if (!session || !session.user) {
|
|
||||||
throw new SessionError('Invalid session object', 'INVALID_SESSION');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const expiresIn = session.expiration ? session.expiration.getTime() : Date.now() + expires;
|
|
||||||
|
|
||||||
if (!session.expiration) {
|
|
||||||
session.expiration = new Date(expiresIn);
|
|
||||||
}
|
|
||||||
|
|
||||||
const refreshToken = await signPayload({
|
|
||||||
payload: {
|
|
||||||
id: session.user,
|
|
||||||
sessionId: session._id,
|
|
||||||
},
|
|
||||||
secret: process.env.JWT_REFRESH_SECRET,
|
|
||||||
expirationTime: Math.floor((expiresIn - Date.now()) / 1000),
|
|
||||||
});
|
|
||||||
|
|
||||||
session.refreshTokenHash = await hashToken(refreshToken);
|
|
||||||
await session.save();
|
|
||||||
|
|
||||||
return refreshToken;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('[generateRefreshToken] Error generating refresh token:', error);
|
|
||||||
throw new SessionError('Failed to generate refresh token', 'GENERATE_TOKEN_FAILED');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Counts active sessions for a user
|
|
||||||
* @param {string} userId - The ID of the user
|
|
||||||
* @returns {Promise<number>}
|
|
||||||
* @throws {SessionError}
|
|
||||||
*/
|
|
||||||
const countActiveSessions = async (userId) => {
|
|
||||||
try {
|
|
||||||
if (!userId) {
|
|
||||||
throw new SessionError('User ID is required', 'INVALID_USER_ID');
|
|
||||||
}
|
|
||||||
|
|
||||||
return await Session.countDocuments({
|
|
||||||
user: userId,
|
|
||||||
expiration: { $gt: new Date() },
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.error('[countActiveSessions] Error counting active sessions:', error);
|
|
||||||
throw new SessionError('Failed to count active sessions', 'COUNT_SESSIONS_FAILED');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
createSession,
|
|
||||||
findSession,
|
|
||||||
updateExpiration,
|
|
||||||
deleteSession,
|
|
||||||
deleteAllUserSessions,
|
|
||||||
generateRefreshToken,
|
|
||||||
countActiveSessions,
|
|
||||||
SessionError,
|
|
||||||
};
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
const mongoose = require('mongoose');
|
|
||||||
const { nanoid } = require('nanoid');
|
const { nanoid } = require('nanoid');
|
||||||
const { Constants } = require('librechat-data-provider');
|
const { Constants } = require('librechat-data-provider');
|
||||||
const { Conversation } = require('~/models/Conversation');
|
const db = require('~/lib/db/connectDb');
|
||||||
const { shareSchema } = require('@librechat/data-schemas');
|
|
||||||
const SharedLink = mongoose.model('SharedLink', shareSchema);
|
|
||||||
const { getMessages } = require('./Message');
|
const { getMessages } = require('./Message');
|
||||||
const logger = require('~/config/winston');
|
const logger = require('~/config/winston');
|
||||||
|
|
||||||
|
|
@ -76,7 +73,7 @@ function anonymizeMessages(messages, newConvoId) {
|
||||||
|
|
||||||
async function getSharedMessages(shareId) {
|
async function getSharedMessages(shareId) {
|
||||||
try {
|
try {
|
||||||
const share = await SharedLink.findOne({ shareId, isPublic: true })
|
const share = await db.models.SharedLink.findOne({ shareId, isPublic: true })
|
||||||
.populate({
|
.populate({
|
||||||
path: 'messages',
|
path: 'messages',
|
||||||
select: '-_id -__v -user',
|
select: '-_id -__v -user',
|
||||||
|
|
@ -151,7 +148,7 @@ async function getSharedLinks(user, pageParam, pageSize, isPublic, sortBy, sortD
|
||||||
query.conversationId = { $in: query.conversationId };
|
query.conversationId = { $in: query.conversationId };
|
||||||
}
|
}
|
||||||
|
|
||||||
const sharedLinks = await SharedLink.find(query)
|
const sharedLinks = await db.models.SharedLink.find(query)
|
||||||
.sort(sort)
|
.sort(sort)
|
||||||
.limit(pageSize + 1)
|
.limit(pageSize + 1)
|
||||||
.select('-__v -user')
|
.select('-__v -user')
|
||||||
|
|
@ -184,7 +181,7 @@ async function getSharedLinks(user, pageParam, pageSize, isPublic, sortBy, sortD
|
||||||
|
|
||||||
async function deleteAllSharedLinks(user) {
|
async function deleteAllSharedLinks(user) {
|
||||||
try {
|
try {
|
||||||
const result = await SharedLink.deleteMany({ user });
|
const result = await db.models.SharedLink.deleteMany({ user });
|
||||||
return {
|
return {
|
||||||
message: 'All shared links deleted successfully',
|
message: 'All shared links deleted successfully',
|
||||||
deletedCount: result.deletedCount,
|
deletedCount: result.deletedCount,
|
||||||
|
|
@ -202,7 +199,7 @@ async function createSharedLink(user, conversationId) {
|
||||||
if (!user || !conversationId) {
|
if (!user || !conversationId) {
|
||||||
throw new ShareServiceError('Missing required parameters', 'INVALID_PARAMS');
|
throw new ShareServiceError('Missing required parameters', 'INVALID_PARAMS');
|
||||||
}
|
}
|
||||||
|
const { SharedLink, Conversation } = db.models;
|
||||||
try {
|
try {
|
||||||
const [existingShare, conversationMessages] = await Promise.all([
|
const [existingShare, conversationMessages] = await Promise.all([
|
||||||
SharedLink.findOne({ conversationId, isPublic: true }).select('-_id -__v -user').lean(),
|
SharedLink.findOne({ conversationId, isPublic: true }).select('-_id -__v -user').lean(),
|
||||||
|
|
@ -244,7 +241,7 @@ async function getSharedLink(user, conversationId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const share = await SharedLink.findOne({ conversationId, user, isPublic: true })
|
const share = await db.models.SharedLink.findOne({ conversationId, user, isPublic: true })
|
||||||
.select('shareId -_id')
|
.select('shareId -_id')
|
||||||
.lean();
|
.lean();
|
||||||
|
|
||||||
|
|
@ -268,6 +265,7 @@ async function updateSharedLink(user, shareId) {
|
||||||
throw new ShareServiceError('Missing required parameters', 'INVALID_PARAMS');
|
throw new ShareServiceError('Missing required parameters', 'INVALID_PARAMS');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { SharedLink } = db.models;
|
||||||
try {
|
try {
|
||||||
const share = await SharedLink.findOne({ shareId }).select('-_id -__v -user').lean();
|
const share = await SharedLink.findOne({ shareId }).select('-_id -__v -user').lean();
|
||||||
|
|
||||||
|
|
@ -318,7 +316,7 @@ async function deleteSharedLink(user, shareId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await SharedLink.findOneAndDelete({ shareId, user }).lean();
|
const result = await db.models.SharedLink.findOneAndDelete({ shareId, user }).lean();
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -340,7 +338,6 @@ async function deleteSharedLink(user, shareId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
SharedLink,
|
|
||||||
getSharedLink,
|
getSharedLink,
|
||||||
getSharedLinks,
|
getSharedLinks,
|
||||||
createSharedLink,
|
createSharedLink,
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,6 @@
|
||||||
const mongoose = require('mongoose');
|
|
||||||
const { encryptV2 } = require('~/server/utils/crypto');
|
const { encryptV2 } = require('~/server/utils/crypto');
|
||||||
const { tokenSchema } = require('@librechat/data-schemas');
|
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
/**
|
|
||||||
* Token model.
|
|
||||||
* @type {mongoose.Model}
|
|
||||||
*/
|
|
||||||
const Token = mongoose.model('Token', tokenSchema);
|
|
||||||
/**
|
/**
|
||||||
* Fixes the indexes for the Token collection from legacy TTL indexes to the new expiresAt index.
|
* Fixes the indexes for the Token collection from legacy TTL indexes to the new expiresAt index.
|
||||||
*/
|
*/
|
||||||
|
|
@ -20,7 +13,7 @@ async function fixIndexes() {
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const indexes = await Token.collection.indexes();
|
const indexes = await db.models.Token.collection.indexes();
|
||||||
logger.debug('Existing Token Indexes:', JSON.stringify(indexes, null, 2));
|
logger.debug('Existing Token Indexes:', JSON.stringify(indexes, null, 2));
|
||||||
const unwantedTTLIndexes = indexes.filter(
|
const unwantedTTLIndexes = indexes.filter(
|
||||||
(index) => index.key.createdAt === 1 && index.expireAfterSeconds !== undefined,
|
(index) => index.key.createdAt === 1 && index.expireAfterSeconds !== undefined,
|
||||||
|
|
@ -31,7 +24,7 @@ async function fixIndexes() {
|
||||||
}
|
}
|
||||||
for (const index of unwantedTTLIndexes) {
|
for (const index of unwantedTTLIndexes) {
|
||||||
logger.debug(`Dropping unwanted Token index: ${index.name}`);
|
logger.debug(`Dropping unwanted Token index: ${index.name}`);
|
||||||
await Token.collection.dropIndex(index.name);
|
await db.models.Token.collection.dropIndex(index.name);
|
||||||
logger.debug(`Dropped Token index: ${index.name}`);
|
logger.debug(`Dropped Token index: ${index.name}`);
|
||||||
}
|
}
|
||||||
logger.debug('Token index cleanup completed successfully.');
|
logger.debug('Token index cleanup completed successfully.');
|
||||||
|
|
@ -42,118 +35,6 @@ async function fixIndexes() {
|
||||||
|
|
||||||
fixIndexes();
|
fixIndexes();
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Token instance.
|
|
||||||
* @param {Object} tokenData - The data for the new Token.
|
|
||||||
* @param {mongoose.Types.ObjectId} tokenData.userId - The user's ID. It is required.
|
|
||||||
* @param {String} tokenData.email - The user's email.
|
|
||||||
* @param {String} tokenData.token - The token. It is required.
|
|
||||||
* @param {Number} tokenData.expiresIn - The number of seconds until the token expires.
|
|
||||||
* @returns {Promise<mongoose.Document>} The new Token instance.
|
|
||||||
* @throws Will throw an error if token creation fails.
|
|
||||||
*/
|
|
||||||
async function createToken(tokenData) {
|
|
||||||
try {
|
|
||||||
const currentTime = new Date();
|
|
||||||
const expiresAt = new Date(currentTime.getTime() + tokenData.expiresIn * 1000);
|
|
||||||
|
|
||||||
const newTokenData = {
|
|
||||||
...tokenData,
|
|
||||||
createdAt: currentTime,
|
|
||||||
expiresAt,
|
|
||||||
};
|
|
||||||
|
|
||||||
return await Token.create(newTokenData);
|
|
||||||
} catch (error) {
|
|
||||||
logger.debug('An error occurred while creating token:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds a Token document that matches the provided query.
|
|
||||||
* @param {Object} query - The query to match against.
|
|
||||||
* @param {mongoose.Types.ObjectId|String} query.userId - The ID of the user.
|
|
||||||
* @param {String} query.token - The token value.
|
|
||||||
* @param {String} [query.email] - The email of the user.
|
|
||||||
* @param {String} [query.identifier] - Unique, alternative identifier for the token.
|
|
||||||
* @returns {Promise<Object|null>} The matched Token document, or null if not found.
|
|
||||||
* @throws Will throw an error if the find operation fails.
|
|
||||||
*/
|
|
||||||
async function findToken(query) {
|
|
||||||
try {
|
|
||||||
const conditions = [];
|
|
||||||
|
|
||||||
if (query.userId) {
|
|
||||||
conditions.push({ userId: query.userId });
|
|
||||||
}
|
|
||||||
if (query.token) {
|
|
||||||
conditions.push({ token: query.token });
|
|
||||||
}
|
|
||||||
if (query.email) {
|
|
||||||
conditions.push({ email: query.email });
|
|
||||||
}
|
|
||||||
if (query.identifier) {
|
|
||||||
conditions.push({ identifier: query.identifier });
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = await Token.findOne({
|
|
||||||
$and: conditions,
|
|
||||||
}).lean();
|
|
||||||
|
|
||||||
return token;
|
|
||||||
} catch (error) {
|
|
||||||
logger.debug('An error occurred while finding token:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates a Token document that matches the provided query.
|
|
||||||
* @param {Object} query - The query to match against.
|
|
||||||
* @param {mongoose.Types.ObjectId|String} query.userId - The ID of the user.
|
|
||||||
* @param {String} query.token - The token value.
|
|
||||||
* @param {String} [query.email] - The email of the user.
|
|
||||||
* @param {String} [query.identifier] - Unique, alternative identifier for the token.
|
|
||||||
* @param {Object} updateData - The data to update the Token with.
|
|
||||||
* @returns {Promise<mongoose.Document|null>} The updated Token document, or null if not found.
|
|
||||||
* @throws Will throw an error if the update operation fails.
|
|
||||||
*/
|
|
||||||
async function updateToken(query, updateData) {
|
|
||||||
try {
|
|
||||||
return await Token.findOneAndUpdate(query, updateData, { new: true });
|
|
||||||
} catch (error) {
|
|
||||||
logger.debug('An error occurred while updating token:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes all Token documents that match the provided token, user ID, or email.
|
|
||||||
* @param {Object} query - The query to match against.
|
|
||||||
* @param {mongoose.Types.ObjectId|String} query.userId - The ID of the user.
|
|
||||||
* @param {String} query.token - The token value.
|
|
||||||
* @param {String} [query.email] - The email of the user.
|
|
||||||
* @param {String} [query.identifier] - Unique, alternative identifier for the token.
|
|
||||||
* @returns {Promise<Object>} The result of the delete operation.
|
|
||||||
* @throws Will throw an error if the delete operation fails.
|
|
||||||
*/
|
|
||||||
async function deleteTokens(query) {
|
|
||||||
try {
|
|
||||||
return await Token.deleteMany({
|
|
||||||
$or: [
|
|
||||||
{ userId: query.userId },
|
|
||||||
{ token: query.token },
|
|
||||||
{ email: query.email },
|
|
||||||
{ identifier: query.identifier },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.debug('An error occurred while deleting tokens:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the OAuth token by creating or updating the token.
|
* Handles the OAuth token by creating or updating the token.
|
||||||
* @param {object} fields
|
* @param {object} fields
|
||||||
|
|
@ -182,18 +63,15 @@ async function handleOAuthToken({
|
||||||
expiresIn: parseInt(expiresIn, 10) || 3600,
|
expiresIn: parseInt(expiresIn, 10) || 3600,
|
||||||
};
|
};
|
||||||
|
|
||||||
const existingToken = await findToken({ userId, identifier });
|
const {Token} = db.models;
|
||||||
|
const existingToken = await Token.findToken({ userId, identifier });
|
||||||
if (existingToken) {
|
if (existingToken) {
|
||||||
return await updateToken({ identifier }, tokenData);
|
return await Token.updateToken({ identifier }, tokenData);
|
||||||
} else {
|
} else {
|
||||||
return await createToken(tokenData);
|
return await Token.createToken(tokenData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
findToken,
|
|
||||||
createToken,
|
|
||||||
updateToken,
|
|
||||||
deleteTokens,
|
|
||||||
handleOAuthToken,
|
handleOAuthToken,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
const mongoose = require('mongoose');
|
const db = require('~/lib/db/connectDb');
|
||||||
const { toolCallSchema } = require('@librechat/data-schemas');
|
|
||||||
const ToolCall = mongoose.model('ToolCall', toolCallSchema);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new tool call
|
* Create a new tool call
|
||||||
* @param {IToolCallData} toolCallData - The tool call data
|
* @param {IToolCallData} toolCallData - The tool call data
|
||||||
|
|
@ -9,7 +6,7 @@ const ToolCall = mongoose.model('ToolCall', toolCallSchema);
|
||||||
*/
|
*/
|
||||||
async function createToolCall(toolCallData) {
|
async function createToolCall(toolCallData) {
|
||||||
try {
|
try {
|
||||||
return await ToolCall.create(toolCallData);
|
return await db.models.ToolCall.create(toolCallData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Error creating tool call: ${error.message}`);
|
throw new Error(`Error creating tool call: ${error.message}`);
|
||||||
}
|
}
|
||||||
|
|
@ -22,7 +19,7 @@ async function createToolCall(toolCallData) {
|
||||||
*/
|
*/
|
||||||
async function getToolCallById(id) {
|
async function getToolCallById(id) {
|
||||||
try {
|
try {
|
||||||
return await ToolCall.findById(id).lean();
|
return await db.models.ToolCall.findById(id).lean();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Error fetching tool call: ${error.message}`);
|
throw new Error(`Error fetching tool call: ${error.message}`);
|
||||||
}
|
}
|
||||||
|
|
@ -36,7 +33,7 @@ async function getToolCallById(id) {
|
||||||
*/
|
*/
|
||||||
async function getToolCallsByMessage(messageId, userId) {
|
async function getToolCallsByMessage(messageId, userId) {
|
||||||
try {
|
try {
|
||||||
return await ToolCall.find({ messageId, user: userId }).lean();
|
return await db.models.ToolCall.find({ messageId, user: userId }).lean();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Error fetching tool calls: ${error.message}`);
|
throw new Error(`Error fetching tool calls: ${error.message}`);
|
||||||
}
|
}
|
||||||
|
|
@ -50,7 +47,7 @@ async function getToolCallsByMessage(messageId, userId) {
|
||||||
*/
|
*/
|
||||||
async function getToolCallsByConvo(conversationId, userId) {
|
async function getToolCallsByConvo(conversationId, userId) {
|
||||||
try {
|
try {
|
||||||
return await ToolCall.find({ conversationId, user: userId }).lean();
|
return await db.models.ToolCall.find({ conversationId, user: userId }).lean();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Error fetching tool calls: ${error.message}`);
|
throw new Error(`Error fetching tool calls: ${error.message}`);
|
||||||
}
|
}
|
||||||
|
|
@ -64,7 +61,7 @@ async function getToolCallsByConvo(conversationId, userId) {
|
||||||
*/
|
*/
|
||||||
async function updateToolCall(id, updateData) {
|
async function updateToolCall(id, updateData) {
|
||||||
try {
|
try {
|
||||||
return await ToolCall.findByIdAndUpdate(id, updateData, { new: true }).lean();
|
return await db.models.ToolCall.findByIdAndUpdate(id, updateData, { new: true }).lean();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Error updating tool call: ${error.message}`);
|
throw new Error(`Error updating tool call: ${error.message}`);
|
||||||
}
|
}
|
||||||
|
|
@ -82,7 +79,7 @@ async function deleteToolCalls(userId, conversationId) {
|
||||||
if (conversationId) {
|
if (conversationId) {
|
||||||
query.conversationId = conversationId;
|
query.conversationId = conversationId;
|
||||||
}
|
}
|
||||||
return await ToolCall.deleteMany(query);
|
return await db.models.ToolCall.deleteMany(query);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Error deleting tool call: ${error.message}`);
|
throw new Error(`Error deleting tool call: ${error.message}`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ const { transactionSchema } = require('@librechat/data-schemas');
|
||||||
const { getBalanceConfig } = require('~/server/services/Config');
|
const { getBalanceConfig } = require('~/server/services/Config');
|
||||||
const { getMultiplier, getCacheMultiplier } = require('./tx');
|
const { getMultiplier, getCacheMultiplier } = require('./tx');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
const Balance = require('./Balance');
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
const cancelRate = 1.15;
|
const cancelRate = 1.15;
|
||||||
|
|
||||||
|
|
@ -23,6 +23,7 @@ const updateBalance = async ({ user, incrementValue, setValues }) => {
|
||||||
let maxRetries = 10; // Number of times to retry on conflict
|
let maxRetries = 10; // Number of times to retry on conflict
|
||||||
let delay = 50; // Initial retry delay in ms
|
let delay = 50; // Initial retry delay in ms
|
||||||
let lastError = null;
|
let lastError = null;
|
||||||
|
const { Balance } = db.models;
|
||||||
|
|
||||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||||
let currentBalanceDoc;
|
let currentBalanceDoc;
|
||||||
|
|
@ -140,19 +141,19 @@ const updateBalance = async ({ user, incrementValue, setValues }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Method to calculate and set the tokenValue for a transaction */
|
/** Method to calculate and set the tokenValue for a transaction */
|
||||||
transactionSchema.methods.calculateTokenValue = function () {
|
function calculateTokenValue(txn) {
|
||||||
if (!this.valueKey || !this.tokenType) {
|
if (!txn.valueKey || !txn.tokenType) {
|
||||||
this.tokenValue = this.rawAmount;
|
txn.tokenValue = txn.rawAmount;
|
||||||
}
|
}
|
||||||
const { valueKey, tokenType, model, endpointTokenConfig } = this;
|
const { valueKey, tokenType, model, endpointTokenConfig } = txn;
|
||||||
const multiplier = Math.abs(getMultiplier({ valueKey, tokenType, model, endpointTokenConfig }));
|
const multiplier = Math.abs(getMultiplier({ valueKey, tokenType, model, endpointTokenConfig }));
|
||||||
this.rate = multiplier;
|
txn.rate = multiplier;
|
||||||
this.tokenValue = this.rawAmount * multiplier;
|
txn.tokenValue = txn.rawAmount * multiplier;
|
||||||
if (this.context && this.tokenType === 'completion' && this.context === 'incomplete') {
|
if (txn.context && txn.tokenType === 'completion' && txn.context === 'incomplete') {
|
||||||
this.tokenValue = Math.ceil(this.tokenValue * cancelRate);
|
txn.tokenValue = Math.ceil(txn.tokenValue * cancelRate);
|
||||||
this.rate *= cancelRate;
|
txn.rate *= cancelRate;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* New static method to create an auto-refill transaction that does NOT trigger a balance update.
|
* New static method to create an auto-refill transaction that does NOT trigger a balance update.
|
||||||
|
|
@ -163,13 +164,14 @@ transactionSchema.methods.calculateTokenValue = function () {
|
||||||
* @param {number} txData.rawAmount - The raw amount of tokens.
|
* @param {number} txData.rawAmount - The raw amount of tokens.
|
||||||
* @returns {Promise<object>} - The created transaction.
|
* @returns {Promise<object>} - The created transaction.
|
||||||
*/
|
*/
|
||||||
transactionSchema.statics.createAutoRefillTransaction = async function (txData) {
|
async function createAutoRefillTransaction(txData) {
|
||||||
|
const Transaction = db.models.Transaction;
|
||||||
if (txData.rawAmount != null && isNaN(txData.rawAmount)) {
|
if (txData.rawAmount != null && isNaN(txData.rawAmount)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const transaction = new this(txData);
|
const transaction = new Transaction(txData);
|
||||||
transaction.endpointTokenConfig = txData.endpointTokenConfig;
|
transaction.endpointTokenConfig = txData.endpointTokenConfig;
|
||||||
transaction.calculateTokenValue();
|
calculateTokenValue(transaction);
|
||||||
await transaction.save();
|
await transaction.save();
|
||||||
|
|
||||||
const balanceResponse = await updateBalance({
|
const balanceResponse = await updateBalance({
|
||||||
|
|
@ -185,21 +187,20 @@ transactionSchema.statics.createAutoRefillTransaction = async function (txData)
|
||||||
logger.debug('[Balance.check] Auto-refill performed', result);
|
logger.debug('[Balance.check] Auto-refill performed', result);
|
||||||
result.transaction = transaction;
|
result.transaction = transaction;
|
||||||
return result;
|
return result;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static method to create a transaction and update the balance
|
* Static method to create a transaction and update the balance
|
||||||
* @param {txData} txData - Transaction data.
|
* @param {txData} txData - Transaction data.
|
||||||
*/
|
*/
|
||||||
transactionSchema.statics.create = async function (txData) {
|
async function createTransaction(txData) {
|
||||||
const Transaction = this;
|
|
||||||
if (txData.rawAmount != null && isNaN(txData.rawAmount)) {
|
if (txData.rawAmount != null && isNaN(txData.rawAmount)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const transaction = new Transaction(txData);
|
const transaction = new db.models.Transaction(txData);
|
||||||
transaction.endpointTokenConfig = txData.endpointTokenConfig;
|
transaction.endpointTokenConfig = txData.endpointTokenConfig;
|
||||||
transaction.calculateTokenValue();
|
calculateTokenValue(transaction);
|
||||||
|
|
||||||
await transaction.save();
|
await transaction.save();
|
||||||
|
|
||||||
|
|
@ -209,7 +210,6 @@ transactionSchema.statics.create = async function (txData) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let incrementValue = transaction.tokenValue;
|
let incrementValue = transaction.tokenValue;
|
||||||
|
|
||||||
const balanceResponse = await updateBalance({
|
const balanceResponse = await updateBalance({
|
||||||
user: transaction.user,
|
user: transaction.user,
|
||||||
incrementValue,
|
incrementValue,
|
||||||
|
|
@ -221,21 +221,19 @@ transactionSchema.statics.create = async function (txData) {
|
||||||
balance: balanceResponse.tokenCredits,
|
balance: balanceResponse.tokenCredits,
|
||||||
[transaction.tokenType]: incrementValue,
|
[transaction.tokenType]: incrementValue,
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static method to create a structured transaction and update the balance
|
* Static method to create a structured transaction and update the balance
|
||||||
* @param {txData} txData - Transaction data.
|
* @param {txData} txData - Transaction data.
|
||||||
*/
|
*/
|
||||||
transactionSchema.statics.createStructured = async function (txData) {
|
async function createStructuredTransaction(txData) {
|
||||||
const Transaction = this;
|
const transaction = new db.models.Transaction({
|
||||||
|
|
||||||
const transaction = new Transaction({
|
|
||||||
...txData,
|
...txData,
|
||||||
endpointTokenConfig: txData.endpointTokenConfig,
|
endpointTokenConfig: txData.endpointTokenConfig,
|
||||||
});
|
});
|
||||||
|
|
||||||
transaction.calculateStructuredTokenValue();
|
calculateStructuredTokenValue(transaction);
|
||||||
|
|
||||||
await transaction.save();
|
await transaction.save();
|
||||||
|
|
||||||
|
|
@ -257,71 +255,69 @@ transactionSchema.statics.createStructured = async function (txData) {
|
||||||
balance: balanceResponse.tokenCredits,
|
balance: balanceResponse.tokenCredits,
|
||||||
[transaction.tokenType]: incrementValue,
|
[transaction.tokenType]: incrementValue,
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
/** Method to calculate token value for structured tokens */
|
/** Method to calculate token value for structured tokens */
|
||||||
transactionSchema.methods.calculateStructuredTokenValue = function () {
|
function calculateStructuredTokenValue(txn) {
|
||||||
if (!this.tokenType) {
|
if (!txn.tokenType) {
|
||||||
this.tokenValue = this.rawAmount;
|
txn.tokenValue = txn.rawAmount;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { model, endpointTokenConfig } = this;
|
const { model, endpointTokenConfig } = txn;
|
||||||
|
|
||||||
if (this.tokenType === 'prompt') {
|
if (txn.tokenType === 'prompt') {
|
||||||
const inputMultiplier = getMultiplier({ tokenType: 'prompt', model, endpointTokenConfig });
|
const inputMultiplier = getMultiplier({ tokenType: 'prompt', model, endpointTokenConfig });
|
||||||
const writeMultiplier =
|
const writeMultiplier =
|
||||||
getCacheMultiplier({ cacheType: 'write', model, endpointTokenConfig }) ?? inputMultiplier;
|
getCacheMultiplier({ cacheType: 'write', model, endpointTokenConfig }) ?? inputMultiplier;
|
||||||
const readMultiplier =
|
const readMultiplier =
|
||||||
getCacheMultiplier({ cacheType: 'read', model, endpointTokenConfig }) ?? inputMultiplier;
|
getCacheMultiplier({ cacheType: 'read', model, endpointTokenConfig }) ?? inputMultiplier;
|
||||||
|
|
||||||
this.rateDetail = {
|
txn.rateDetail = {
|
||||||
input: inputMultiplier,
|
input: inputMultiplier,
|
||||||
write: writeMultiplier,
|
write: writeMultiplier,
|
||||||
read: readMultiplier,
|
read: readMultiplier,
|
||||||
};
|
};
|
||||||
|
|
||||||
const totalPromptTokens =
|
const totalPromptTokens =
|
||||||
Math.abs(this.inputTokens || 0) +
|
Math.abs(txn.inputTokens || 0) +
|
||||||
Math.abs(this.writeTokens || 0) +
|
Math.abs(txn.writeTokens || 0) +
|
||||||
Math.abs(this.readTokens || 0);
|
Math.abs(txn.readTokens || 0);
|
||||||
|
|
||||||
if (totalPromptTokens > 0) {
|
if (totalPromptTokens > 0) {
|
||||||
this.rate =
|
txn.rate =
|
||||||
(Math.abs(inputMultiplier * (this.inputTokens || 0)) +
|
(Math.abs(inputMultiplier * (txn.inputTokens || 0)) +
|
||||||
Math.abs(writeMultiplier * (this.writeTokens || 0)) +
|
Math.abs(writeMultiplier * (txn.writeTokens || 0)) +
|
||||||
Math.abs(readMultiplier * (this.readTokens || 0))) /
|
Math.abs(readMultiplier * (txn.readTokens || 0))) /
|
||||||
totalPromptTokens;
|
totalPromptTokens;
|
||||||
} else {
|
} else {
|
||||||
this.rate = Math.abs(inputMultiplier); // Default to input rate if no tokens
|
txn.rate = Math.abs(inputMultiplier); // Default to input rate if no tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
this.tokenValue = -(
|
txn.tokenValue = -(
|
||||||
Math.abs(this.inputTokens || 0) * inputMultiplier +
|
Math.abs(txn.inputTokens || 0) * inputMultiplier +
|
||||||
Math.abs(this.writeTokens || 0) * writeMultiplier +
|
Math.abs(txn.writeTokens || 0) * writeMultiplier +
|
||||||
Math.abs(this.readTokens || 0) * readMultiplier
|
Math.abs(txn.readTokens || 0) * readMultiplier
|
||||||
);
|
);
|
||||||
|
|
||||||
this.rawAmount = -totalPromptTokens;
|
txn.rawAmount = -totalPromptTokens;
|
||||||
} else if (this.tokenType === 'completion') {
|
} else if (txn.tokenType === 'completion') {
|
||||||
const multiplier = getMultiplier({ tokenType: this.tokenType, model, endpointTokenConfig });
|
const multiplier = getMultiplier({ tokenType: txn.tokenType, model, endpointTokenConfig });
|
||||||
this.rate = Math.abs(multiplier);
|
txn.rate = Math.abs(multiplier);
|
||||||
this.tokenValue = -Math.abs(this.rawAmount) * multiplier;
|
txn.tokenValue = -Math.abs(txn.rawAmount) * multiplier;
|
||||||
this.rawAmount = -Math.abs(this.rawAmount);
|
txn.rawAmount = -Math.abs(txn.rawAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.context && this.tokenType === 'completion' && this.context === 'incomplete') {
|
if (txn.context && txn.tokenType === 'completion' && txn.context === 'incomplete') {
|
||||||
this.tokenValue = Math.ceil(this.tokenValue * cancelRate);
|
txn.tokenValue = Math.ceil(txn.tokenValue * cancelRate);
|
||||||
this.rate *= cancelRate;
|
txn.rate *= cancelRate;
|
||||||
if (this.rateDetail) {
|
if (txn.rateDetail) {
|
||||||
this.rateDetail = Object.fromEntries(
|
txn.rateDetail = Object.fromEntries(
|
||||||
Object.entries(this.rateDetail).map(([k, v]) => [k, v * cancelRate]),
|
Object.entries(txn.rateDetail).map(([k, v]) => [k, v * cancelRate]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const Transaction = mongoose.model('Transaction', transactionSchema);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queries and retrieves transactions based on a given filter.
|
* Queries and retrieves transactions based on a given filter.
|
||||||
|
|
@ -333,11 +329,16 @@ const Transaction = mongoose.model('Transaction', transactionSchema);
|
||||||
*/
|
*/
|
||||||
async function getTransactions(filter) {
|
async function getTransactions(filter) {
|
||||||
try {
|
try {
|
||||||
return await Transaction.find(filter).lean();
|
return await db.models.Transaction.find(filter).lean();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error querying transactions:', error);
|
logger.error('Error querying transactions:', error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { Transaction, getTransactions };
|
module.exports = {
|
||||||
|
getTransactions,
|
||||||
|
createTransaction,
|
||||||
|
createAutoRefillTransaction,
|
||||||
|
createStructuredTransaction,
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,22 @@ const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||||
const { spendTokens, spendStructuredTokens } = require('./spendTokens');
|
const { spendTokens, spendStructuredTokens } = require('./spendTokens');
|
||||||
const { getBalanceConfig } = require('~/server/services/Config');
|
const { getBalanceConfig } = require('~/server/services/Config');
|
||||||
const { getMultiplier, getCacheMultiplier } = require('./tx');
|
const { getMultiplier, getCacheMultiplier } = require('./tx');
|
||||||
const { Transaction } = require('./Transaction');
|
const db = require('~/lib/db/connectDb');
|
||||||
const Balance = require('./Balance');
|
const { createTransaction } = require('./Transaction');
|
||||||
|
|
||||||
// Mock the custom config module so we can control the balance flag.
|
// Mock the custom config module so we can control the balance flag.
|
||||||
jest.mock('~/server/services/Config');
|
jest.mock('~/server/services/Config');
|
||||||
|
|
||||||
let mongoServer;
|
let mongoServer;
|
||||||
|
let Balance;
|
||||||
|
let Transaction;
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
mongoServer = await MongoMemoryServer.create();
|
mongoServer = await MongoMemoryServer.create();
|
||||||
const mongoUri = mongoServer.getUri();
|
const mongoUri = mongoServer.getUri();
|
||||||
await mongoose.connect(mongoUri);
|
await db.connectDb(mongoUri);
|
||||||
|
|
||||||
|
Balance = db.models.Balance;
|
||||||
|
Transaction = db.models.Transaction;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|
@ -368,7 +372,7 @@ describe('NaN Handling Tests', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const result = await Transaction.create(txData);
|
const result = await createTransaction(txData);
|
||||||
|
|
||||||
// Assert: No transaction should be created and balance remains unchanged.
|
// Assert: No transaction should be created and balance remains unchanged.
|
||||||
expect(result).toBeUndefined();
|
expect(result).toBeUndefined();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
const mongoose = require('mongoose');
|
|
||||||
const { userSchema } = require('@librechat/data-schemas');
|
|
||||||
|
|
||||||
const User = mongoose.model('User', userSchema);
|
|
||||||
|
|
||||||
module.exports = User;
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
const { ViolationTypes } = require('librechat-data-provider');
|
const { ViolationTypes } = require('librechat-data-provider');
|
||||||
const { Transaction } = require('./Transaction');
|
const { createAutoRefillTransaction } = require('./Transaction');
|
||||||
const { logViolation } = require('~/cache');
|
const { logViolation } = require('~/cache');
|
||||||
const { getMultiplier } = require('./tx');
|
const { getMultiplier } = require('./tx');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
const Balance = require('./Balance');
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
function isInvalidDate(date) {
|
function isInvalidDate(date) {
|
||||||
return isNaN(date);
|
return isNaN(date);
|
||||||
|
|
@ -26,7 +26,7 @@ const checkBalanceRecord = async function ({
|
||||||
const tokenCost = amount * multiplier;
|
const tokenCost = amount * multiplier;
|
||||||
|
|
||||||
// Retrieve the balance record
|
// Retrieve the balance record
|
||||||
let record = await Balance.findOne({ user }).lean();
|
let record = await db.models.Balance.findOne({ user }).lean();
|
||||||
if (!record) {
|
if (!record) {
|
||||||
logger.debug('[Balance.check] No balance record found for user', { user });
|
logger.debug('[Balance.check] No balance record found for user', { user });
|
||||||
return {
|
return {
|
||||||
|
|
@ -60,7 +60,7 @@ const checkBalanceRecord = async function ({
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
/** @type {{ rate: number, user: string, balance: number, transaction: import('@librechat/data-schemas').ITransaction}} */
|
/** @type {{ rate: number, user: string, balance: number, transaction: import('@librechat/data-schemas').ITransaction}} */
|
||||||
const result = await Transaction.createAutoRefillTransaction({
|
const result = await createAutoRefillTransaction({
|
||||||
user: user,
|
user: user,
|
||||||
tokenType: 'credits',
|
tokenType: 'credits',
|
||||||
context: 'autoRefill',
|
context: 'autoRefill',
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||||
const { Message, getMessages, bulkSaveMessages } = require('./Message');
|
const { getMessages, bulkSaveMessages } = require('./Message');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
// Original version of buildTree function
|
// Original version of buildTree function
|
||||||
function buildTree({ messages, fileMap }) {
|
function buildTree({ messages, fileMap }) {
|
||||||
|
|
@ -42,11 +43,13 @@ function buildTree({ messages, fileMap }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mongod;
|
let mongod;
|
||||||
|
let Message;
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
mongod = await MongoMemoryServer.create();
|
mongod = await MongoMemoryServer.create();
|
||||||
const uri = mongod.getUri();
|
const uri = mongod.getUri();
|
||||||
await mongoose.connect(uri);
|
await db.connectDb(uri);
|
||||||
|
|
||||||
|
Message = db.models.Message;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,4 @@
|
||||||
const {
|
const { comparePassword } = require('./userMethods');
|
||||||
comparePassword,
|
|
||||||
deleteUserById,
|
|
||||||
generateToken,
|
|
||||||
getUserById,
|
|
||||||
updateUser,
|
|
||||||
createUser,
|
|
||||||
countUsers,
|
|
||||||
findUser,
|
|
||||||
} = require('./userMethods');
|
|
||||||
const {
|
const {
|
||||||
findFileById,
|
findFileById,
|
||||||
createFile,
|
createFile,
|
||||||
|
|
@ -26,32 +17,11 @@ const {
|
||||||
deleteMessagesSince,
|
deleteMessagesSince,
|
||||||
deleteMessages,
|
deleteMessages,
|
||||||
} = require('./Message');
|
} = require('./Message');
|
||||||
const {
|
|
||||||
createSession,
|
|
||||||
findSession,
|
|
||||||
updateExpiration,
|
|
||||||
deleteSession,
|
|
||||||
deleteAllUserSessions,
|
|
||||||
generateRefreshToken,
|
|
||||||
countActiveSessions,
|
|
||||||
} = require('./Session');
|
|
||||||
const { getConvoTitle, getConvo, saveConvo, deleteConvos } = require('./Conversation');
|
const { getConvoTitle, getConvo, saveConvo, deleteConvos } = require('./Conversation');
|
||||||
const { getPreset, getPresets, savePreset, deletePresets } = require('./Preset');
|
const { getPreset, getPresets, savePreset, deletePresets } = require('./Preset');
|
||||||
const { createToken, findToken, updateToken, deleteTokens } = require('./Token');
|
|
||||||
const Balance = require('./Balance');
|
|
||||||
const User = require('./User');
|
|
||||||
const Key = require('./Key');
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
comparePassword,
|
comparePassword,
|
||||||
deleteUserById,
|
|
||||||
generateToken,
|
|
||||||
getUserById,
|
|
||||||
updateUser,
|
|
||||||
createUser,
|
|
||||||
countUsers,
|
|
||||||
findUser,
|
|
||||||
|
|
||||||
findFileById,
|
findFileById,
|
||||||
createFile,
|
createFile,
|
||||||
updateFile,
|
updateFile,
|
||||||
|
|
@ -77,21 +47,4 @@ module.exports = {
|
||||||
getPresets,
|
getPresets,
|
||||||
savePreset,
|
savePreset,
|
||||||
deletePresets,
|
deletePresets,
|
||||||
|
|
||||||
createToken,
|
|
||||||
findToken,
|
|
||||||
updateToken,
|
|
||||||
deleteTokens,
|
|
||||||
|
|
||||||
createSession,
|
|
||||||
findSession,
|
|
||||||
updateExpiration,
|
|
||||||
deleteSession,
|
|
||||||
deleteAllUserSessions,
|
|
||||||
generateRefreshToken,
|
|
||||||
countActiveSessions,
|
|
||||||
|
|
||||||
User,
|
|
||||||
Key,
|
|
||||||
Balance,
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
const { getRandomValues, hashToken } = require('~/server/utils/crypto');
|
const { getRandomValues, hashToken } = require('~/server/utils/crypto');
|
||||||
const { createToken, findToken } = require('./Token');
|
|
||||||
const logger = require('~/config/winston');
|
const logger = require('~/config/winston');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @module inviteUser
|
* @module inviteUser
|
||||||
|
|
@ -23,7 +23,7 @@ const createInvite = async (email) => {
|
||||||
|
|
||||||
const fakeUserId = new mongoose.Types.ObjectId();
|
const fakeUserId = new mongoose.Types.ObjectId();
|
||||||
|
|
||||||
await createToken({
|
await db.models.Token.createToken({
|
||||||
userId: fakeUserId,
|
userId: fakeUserId,
|
||||||
email,
|
email,
|
||||||
token: hash,
|
token: hash,
|
||||||
|
|
@ -50,7 +50,7 @@ const getInvite = async (encodedToken, email) => {
|
||||||
try {
|
try {
|
||||||
const token = decodeURIComponent(encodedToken);
|
const token = decodeURIComponent(encodedToken);
|
||||||
const hash = await hashToken(token);
|
const hash = await hashToken(token);
|
||||||
const invite = await findToken({ token: hash, email });
|
const invite = await db.models.Token.findToken({ token: hash, email });
|
||||||
|
|
||||||
if (!invite) {
|
if (!invite) {
|
||||||
throw new Error('Invite not found or email does not match');
|
throw new Error('Invite not found or email does not match');
|
||||||
|
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
const mongoose = require('mongoose');
|
|
||||||
const mongoMeili = require('../plugins/mongoMeili');
|
|
||||||
|
|
||||||
const { convoSchema } = require('@librechat/data-schemas');
|
|
||||||
|
|
||||||
if (process.env.MEILI_HOST && process.env.MEILI_MASTER_KEY) {
|
|
||||||
convoSchema.plugin(mongoMeili, {
|
|
||||||
host: process.env.MEILI_HOST,
|
|
||||||
apiKey: process.env.MEILI_MASTER_KEY,
|
|
||||||
/** Note: Will get created automatically if it doesn't exist already */
|
|
||||||
indexName: 'convos',
|
|
||||||
primaryKey: 'conversationId',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const Conversation = mongoose.models.Conversation || mongoose.model('Conversation', convoSchema);
|
|
||||||
|
|
||||||
module.exports = Conversation;
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
const mongoose = require('mongoose');
|
|
||||||
const mongoMeili = require('~/models/plugins/mongoMeili');
|
const mongoMeili = require('~/models/plugins/mongoMeili');
|
||||||
const { messageSchema } = require('@librechat/data-schemas');
|
const { messageSchema } = require('@librechat/data-schemas');
|
||||||
|
|
||||||
|
|
@ -11,6 +10,6 @@ if (process.env.MEILI_HOST && process.env.MEILI_MASTER_KEY) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const Message = mongoose.models.Message || mongoose.model('Message', messageSchema);
|
// const Message = mongoose.models.Message || mongoose.model('Message', messageSchema);
|
||||||
|
|
||||||
module.exports = Message;
|
module.exports = Message;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
const mongoose = require('mongoose');
|
|
||||||
const { presetSchema } = require('@librechat/data-schemas');
|
|
||||||
|
|
||||||
const Preset = mongoose.models.Preset || mongoose.model('Preset', presetSchema);
|
|
||||||
|
|
||||||
module.exports = Preset;
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
const { Transaction } = require('./Transaction');
|
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
const { createTransaction, createStructuredTransaction } = require('./Transaction');
|
||||||
/**
|
/**
|
||||||
* Creates up to two transactions to record the spending of tokens.
|
* Creates up to two transactions to record the spending of tokens.
|
||||||
*
|
*
|
||||||
|
|
@ -33,7 +33,7 @@ const spendTokens = async (txData, tokenUsage) => {
|
||||||
let prompt, completion;
|
let prompt, completion;
|
||||||
try {
|
try {
|
||||||
if (promptTokens !== undefined) {
|
if (promptTokens !== undefined) {
|
||||||
prompt = await Transaction.create({
|
prompt = await createTransaction({
|
||||||
...txData,
|
...txData,
|
||||||
tokenType: 'prompt',
|
tokenType: 'prompt',
|
||||||
rawAmount: promptTokens === 0 ? 0 : -Math.max(promptTokens, 0),
|
rawAmount: promptTokens === 0 ? 0 : -Math.max(promptTokens, 0),
|
||||||
|
|
@ -41,7 +41,7 @@ const spendTokens = async (txData, tokenUsage) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (completionTokens !== undefined) {
|
if (completionTokens !== undefined) {
|
||||||
completion = await Transaction.create({
|
completion = await createTransaction({
|
||||||
...txData,
|
...txData,
|
||||||
tokenType: 'completion',
|
tokenType: 'completion',
|
||||||
rawAmount: completionTokens === 0 ? 0 : -Math.max(completionTokens, 0),
|
rawAmount: completionTokens === 0 ? 0 : -Math.max(completionTokens, 0),
|
||||||
|
|
@ -101,7 +101,7 @@ const spendStructuredTokens = async (txData, tokenUsage) => {
|
||||||
try {
|
try {
|
||||||
if (promptTokens) {
|
if (promptTokens) {
|
||||||
const { input = 0, write = 0, read = 0 } = promptTokens;
|
const { input = 0, write = 0, read = 0 } = promptTokens;
|
||||||
prompt = await Transaction.createStructured({
|
prompt = await createStructuredTransaction({
|
||||||
...txData,
|
...txData,
|
||||||
tokenType: 'prompt',
|
tokenType: 'prompt',
|
||||||
inputTokens: -input,
|
inputTokens: -input,
|
||||||
|
|
@ -111,7 +111,7 @@ const spendStructuredTokens = async (txData, tokenUsage) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (completionTokens) {
|
if (completionTokens) {
|
||||||
completion = await Transaction.create({
|
completion = await createTransaction({
|
||||||
...txData,
|
...txData,
|
||||||
tokenType: 'completion',
|
tokenType: 'completion',
|
||||||
rawAmount: -completionTokens,
|
rawAmount: -completionTokens,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||||
const { Transaction } = require('./Transaction');
|
|
||||||
const Balance = require('./Balance');
|
|
||||||
const { spendTokens, spendStructuredTokens } = require('./spendTokens');
|
const { spendTokens, spendStructuredTokens } = require('./spendTokens');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
const { createTransaction, createAutoRefillTransaction } = require('./Transaction');
|
||||||
|
|
||||||
// Mock the logger to prevent console output during tests
|
// Mock the logger to prevent console output during tests
|
||||||
jest.mock('~/config', () => ({
|
jest.mock('~/config', () => ({
|
||||||
|
|
@ -20,10 +20,15 @@ describe('spendTokens', () => {
|
||||||
let mongoServer;
|
let mongoServer;
|
||||||
let userId;
|
let userId;
|
||||||
|
|
||||||
|
let Transaction;
|
||||||
|
let Balance;
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
mongoServer = await MongoMemoryServer.create();
|
mongoServer = await MongoMemoryServer.create();
|
||||||
const mongoUri = mongoServer.getUri();
|
const mongoUri = mongoServer.getUri();
|
||||||
await mongoose.connect(mongoUri);
|
await db.connectDb(mongoUri);
|
||||||
|
|
||||||
|
Balance = db.models.Balance;
|
||||||
|
Transaction = db.models.Transaction;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|
@ -197,7 +202,7 @@ describe('spendTokens', () => {
|
||||||
// Check that the transaction records show the adjusted values
|
// Check that the transaction records show the adjusted values
|
||||||
const transactionResults = await Promise.all(
|
const transactionResults = await Promise.all(
|
||||||
transactions.map((t) =>
|
transactions.map((t) =>
|
||||||
Transaction.create({
|
createTransaction({
|
||||||
...txData,
|
...txData,
|
||||||
tokenType: t.tokenType,
|
tokenType: t.tokenType,
|
||||||
rawAmount: t.rawAmount,
|
rawAmount: t.rawAmount,
|
||||||
|
|
@ -280,7 +285,7 @@ describe('spendTokens', () => {
|
||||||
|
|
||||||
// Check the return values from Transaction.create directly
|
// Check the return values from Transaction.create directly
|
||||||
// This is to verify that the incrementValue is not becoming positive
|
// This is to verify that the incrementValue is not becoming positive
|
||||||
const directResult = await Transaction.create({
|
const directResult = await createTransaction({
|
||||||
user: userId,
|
user: userId,
|
||||||
conversationId: 'test-convo-3',
|
conversationId: 'test-convo-3',
|
||||||
model: 'gpt-4',
|
model: 'gpt-4',
|
||||||
|
|
@ -607,7 +612,7 @@ describe('spendTokens', () => {
|
||||||
const promises = [];
|
const promises = [];
|
||||||
for (let i = 0; i < numberOfRefills; i++) {
|
for (let i = 0; i < numberOfRefills; i++) {
|
||||||
promises.push(
|
promises.push(
|
||||||
Transaction.createAutoRefillTransaction({
|
createAutoRefillTransaction({
|
||||||
user: userId,
|
user: userId,
|
||||||
tokenType: 'credits',
|
tokenType: 'credits',
|
||||||
context: 'concurrent-refill-test',
|
context: 'concurrent-refill-test',
|
||||||
|
|
|
||||||
|
|
@ -1,159 +1,4 @@
|
||||||
const bcrypt = require('bcryptjs');
|
const bcrypt = require('bcryptjs');
|
||||||
const { getBalanceConfig } = require('~/server/services/Config');
|
|
||||||
const signPayload = require('~/server/services/signPayload');
|
|
||||||
const Balance = require('./Balance');
|
|
||||||
const User = require('./User');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve a user by ID and convert the found user document to a plain object.
|
|
||||||
*
|
|
||||||
* @param {string} userId - The ID of the user to find and return as a plain object.
|
|
||||||
* @param {string|string[]} [fieldsToSelect] - The fields to include or exclude in the returned document.
|
|
||||||
* @returns {Promise<MongoUser>} A plain object representing the user document, or `null` if no user is found.
|
|
||||||
*/
|
|
||||||
const getUserById = async function (userId, fieldsToSelect = null) {
|
|
||||||
const query = User.findById(userId);
|
|
||||||
if (fieldsToSelect) {
|
|
||||||
query.select(fieldsToSelect);
|
|
||||||
}
|
|
||||||
return await query.lean();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search for a single user based on partial data and return matching user document as plain object.
|
|
||||||
* @param {Partial<MongoUser>} searchCriteria - The partial data to use for searching the user.
|
|
||||||
* @param {string|string[]} [fieldsToSelect] - The fields to include or exclude in the returned document.
|
|
||||||
* @returns {Promise<MongoUser>} A plain object representing the user document, or `null` if no user is found.
|
|
||||||
*/
|
|
||||||
const findUser = async function (searchCriteria, fieldsToSelect = null) {
|
|
||||||
const query = User.findOne(searchCriteria);
|
|
||||||
if (fieldsToSelect) {
|
|
||||||
query.select(fieldsToSelect);
|
|
||||||
}
|
|
||||||
return await query.lean();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update a user with new data without overwriting existing properties.
|
|
||||||
*
|
|
||||||
* @param {string} userId - The ID of the user to update.
|
|
||||||
* @param {Object} updateData - An object containing the properties to update.
|
|
||||||
* @returns {Promise<MongoUser>} The updated user document as a plain object, or `null` if no user is found.
|
|
||||||
*/
|
|
||||||
const updateUser = async function (userId, updateData) {
|
|
||||||
const updateOperation = {
|
|
||||||
$set: updateData,
|
|
||||||
$unset: { expiresAt: '' }, // Remove the expiresAt field to prevent TTL
|
|
||||||
};
|
|
||||||
return await User.findByIdAndUpdate(userId, updateOperation, {
|
|
||||||
new: true,
|
|
||||||
runValidators: true,
|
|
||||||
}).lean();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new user, optionally with a TTL of 1 week.
|
|
||||||
* @param {MongoUser} data - The user data to be created, must contain user_id.
|
|
||||||
* @param {boolean} [disableTTL=true] - Whether to disable the TTL. Defaults to `true`.
|
|
||||||
* @param {boolean} [returnUser=false] - Whether to return the created user object.
|
|
||||||
* @returns {Promise<ObjectId|MongoUser>} A promise that resolves to the created user document ID or user object.
|
|
||||||
* @throws {Error} If a user with the same user_id already exists.
|
|
||||||
*/
|
|
||||||
const createUser = async (data, disableTTL = true, returnUser = false) => {
|
|
||||||
const balance = await getBalanceConfig();
|
|
||||||
const userData = {
|
|
||||||
...data,
|
|
||||||
expiresAt: disableTTL ? null : new Date(Date.now() + 604800 * 1000), // 1 week in milliseconds
|
|
||||||
};
|
|
||||||
|
|
||||||
if (disableTTL) {
|
|
||||||
delete userData.expiresAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await User.create(userData);
|
|
||||||
|
|
||||||
// If balance is enabled, create or update a balance record for the user using global.interfaceConfig.balance
|
|
||||||
if (balance?.enabled && balance?.startBalance) {
|
|
||||||
const update = {
|
|
||||||
$inc: { tokenCredits: balance.startBalance },
|
|
||||||
};
|
|
||||||
|
|
||||||
if (
|
|
||||||
balance.autoRefillEnabled &&
|
|
||||||
balance.refillIntervalValue != null &&
|
|
||||||
balance.refillIntervalUnit != null &&
|
|
||||||
balance.refillAmount != null
|
|
||||||
) {
|
|
||||||
update.$set = {
|
|
||||||
autoRefillEnabled: true,
|
|
||||||
refillIntervalValue: balance.refillIntervalValue,
|
|
||||||
refillIntervalUnit: balance.refillIntervalUnit,
|
|
||||||
refillAmount: balance.refillAmount,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
await Balance.findOneAndUpdate({ user: user._id }, update, { upsert: true, new: true }).lean();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (returnUser) {
|
|
||||||
return user.toObject();
|
|
||||||
}
|
|
||||||
return user._id;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Count the number of user documents in the collection based on the provided filter.
|
|
||||||
*
|
|
||||||
* @param {Object} [filter={}] - The filter to apply when counting the documents.
|
|
||||||
* @returns {Promise<number>} The count of documents that match the filter.
|
|
||||||
*/
|
|
||||||
const countUsers = async function (filter = {}) {
|
|
||||||
return await User.countDocuments(filter);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a user by their unique ID.
|
|
||||||
*
|
|
||||||
* @param {string} userId - The ID of the user to delete.
|
|
||||||
* @returns {Promise<{ deletedCount: number }>} An object indicating the number of deleted documents.
|
|
||||||
*/
|
|
||||||
const deleteUserById = async function (userId) {
|
|
||||||
try {
|
|
||||||
const result = await User.deleteOne({ _id: userId });
|
|
||||||
if (result.deletedCount === 0) {
|
|
||||||
return { deletedCount: 0, message: 'No user found with that ID.' };
|
|
||||||
}
|
|
||||||
return { deletedCount: result.deletedCount, message: 'User was deleted successfully.' };
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error('Error deleting user: ' + error.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const { SESSION_EXPIRY } = process.env ?? {};
|
|
||||||
const expires = eval(SESSION_EXPIRY) ?? 1000 * 60 * 15;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a JWT token for a given user.
|
|
||||||
*
|
|
||||||
* @param {MongoUser} user - The user for whom the token is being generated.
|
|
||||||
* @returns {Promise<string>} A promise that resolves to a JWT token.
|
|
||||||
*/
|
|
||||||
const generateToken = async (user) => {
|
|
||||||
if (!user) {
|
|
||||||
throw new Error('No user provided');
|
|
||||||
}
|
|
||||||
|
|
||||||
return await signPayload({
|
|
||||||
payload: {
|
|
||||||
id: user._id,
|
|
||||||
username: user.username,
|
|
||||||
provider: user.provider,
|
|
||||||
email: user.email,
|
|
||||||
},
|
|
||||||
secret: process.env.JWT_SECRET,
|
|
||||||
expirationTime: expires / 1000,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compares the provided password with the user's password.
|
* Compares the provided password with the user's password.
|
||||||
|
|
@ -179,11 +24,4 @@ const comparePassword = async (user, candidatePassword) => {
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
comparePassword,
|
comparePassword,
|
||||||
deleteUserById,
|
|
||||||
generateToken,
|
|
||||||
getUserById,
|
|
||||||
countUsers,
|
|
||||||
createUser,
|
|
||||||
updateUser,
|
|
||||||
findUser,
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@
|
||||||
"librechat-data-provider": "*",
|
"librechat-data-provider": "*",
|
||||||
"librechat-mcp": "*",
|
"librechat-mcp": "*",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"meilisearch": "^0.38.0",
|
"meilisearch": "^0.50.0",
|
||||||
"memorystore": "^1.6.7",
|
"memorystore": "^1.6.7",
|
||||||
"mime": "^3.0.0",
|
"mime": "^3.0.0",
|
||||||
"module-alias": "^2.2.3",
|
"module-alias": "^2.2.3",
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@ const {
|
||||||
requestPasswordReset,
|
requestPasswordReset,
|
||||||
setOpenIDAuthTokens,
|
setOpenIDAuthTokens,
|
||||||
} = require('~/server/services/AuthService');
|
} = require('~/server/services/AuthService');
|
||||||
const { findSession, getUserById, deleteAllUserSessions, findUser } = require('~/models');
|
|
||||||
const { getOpenIdConfig } = require('~/strategies');
|
const { getOpenIdConfig } = require('~/strategies');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
const { isEnabled } = require('~/server/utils');
|
const { isEnabled } = require('~/server/utils');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
const registrationController = async (req, res) => {
|
const registrationController = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -48,7 +48,7 @@ const resetPasswordController = async (req, res) => {
|
||||||
if (resetPasswordService instanceof Error) {
|
if (resetPasswordService instanceof Error) {
|
||||||
return res.status(400).json(resetPasswordService);
|
return res.status(400).json(resetPasswordService);
|
||||||
} else {
|
} else {
|
||||||
await deleteAllUserSessions({ userId: req.body.userId });
|
await db.models.Session.deleteAllUserSessions({ userId: req.body.userId });
|
||||||
return res.status(200).json(resetPasswordService);
|
return res.status(200).json(resetPasswordService);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -83,7 +83,7 @@ const refreshController = async (req, res) => {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const payload = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET);
|
const payload = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET);
|
||||||
const user = await getUserById(payload.id, '-password -__v -totpSecret');
|
const user = await db.models.User.getUserById(payload.id, '-password -__v -totpSecret');
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return res.status(401).redirect('/login');
|
return res.status(401).redirect('/login');
|
||||||
}
|
}
|
||||||
|
|
@ -96,7 +96,10 @@ const refreshController = async (req, res) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the session with the hashed refresh token
|
// Find the session with the hashed refresh token
|
||||||
const session = await findSession({ userId: userId, refreshToken: refreshToken });
|
const session = await db.models.Session.findSession({
|
||||||
|
userId: userId,
|
||||||
|
refreshToken: refreshToken,
|
||||||
|
});
|
||||||
|
|
||||||
if (session && session.expiration > new Date()) {
|
if (session && session.expiration > new Date()) {
|
||||||
const token = await setAuthTokens(userId, res, session._id);
|
const token = await setAuthTokens(userId, res, session._id);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
const Balance = require('~/models/Balance');
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
async function balanceController(req, res) {
|
async function balanceController(req, res) {
|
||||||
const balanceData = await Balance.findOne(
|
const balanceData = await db.models.Balance.findOne(
|
||||||
{ user: req.user.id },
|
{ user: req.user.id },
|
||||||
'-_id tokenCredits autoRefillEnabled refillIntervalValue refillIntervalUnit lastRefill refillAmount',
|
'-_id tokenCredits autoRefillEnabled refillIntervalValue refillIntervalUnit lastRefill refillAmount',
|
||||||
).lean();
|
).lean();
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,9 @@ const {
|
||||||
verifyBackupCode,
|
verifyBackupCode,
|
||||||
getTOTPSecret,
|
getTOTPSecret,
|
||||||
} = require('~/server/services/twoFactorService');
|
} = require('~/server/services/twoFactorService');
|
||||||
const { updateUser, getUserById } = require('~/models');
|
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
const { encryptV3 } = require('~/server/utils/crypto');
|
const { encryptV3 } = require('~/server/utils/crypto');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
const safeAppTitle = (process.env.APP_TITLE || 'LibreChat').replace(/\s+/g, '');
|
const safeAppTitle = (process.env.APP_TITLE || 'LibreChat').replace(/\s+/g, '');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -25,7 +24,7 @@ const enable2FA = async (req, res) => {
|
||||||
const encryptedSecret = encryptV3(secret);
|
const encryptedSecret = encryptV3(secret);
|
||||||
|
|
||||||
// Update the user record: store the secret & backup codes and set twoFactorEnabled to false.
|
// Update the user record: store the secret & backup codes and set twoFactorEnabled to false.
|
||||||
const user = await updateUser(userId, {
|
const user = await db.models.User.updateUser(userId, {
|
||||||
totpSecret: encryptedSecret,
|
totpSecret: encryptedSecret,
|
||||||
backupCodes: codeObjects,
|
backupCodes: codeObjects,
|
||||||
twoFactorEnabled: false,
|
twoFactorEnabled: false,
|
||||||
|
|
@ -47,7 +46,7 @@ const verify2FA = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const userId = req.user.id;
|
const userId = req.user.id;
|
||||||
const { token, backupCode } = req.body;
|
const { token, backupCode } = req.body;
|
||||||
const user = await getUserById(userId);
|
const user = await db.models.User.getUserById(userId);
|
||||||
|
|
||||||
if (!user || !user.totpSecret) {
|
if (!user || !user.totpSecret) {
|
||||||
return res.status(400).json({ message: '2FA not initiated' });
|
return res.status(400).json({ message: '2FA not initiated' });
|
||||||
|
|
@ -79,7 +78,8 @@ const confirm2FA = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const userId = req.user.id;
|
const userId = req.user.id;
|
||||||
const { token } = req.body;
|
const { token } = req.body;
|
||||||
const user = await getUserById(userId);
|
const { User } = db.models;
|
||||||
|
const user = await User.getUserById(userId);
|
||||||
|
|
||||||
if (!user || !user.totpSecret) {
|
if (!user || !user.totpSecret) {
|
||||||
return res.status(400).json({ message: '2FA not initiated' });
|
return res.status(400).json({ message: '2FA not initiated' });
|
||||||
|
|
@ -87,7 +87,7 @@ const confirm2FA = async (req, res) => {
|
||||||
|
|
||||||
const secret = await getTOTPSecret(user.totpSecret);
|
const secret = await getTOTPSecret(user.totpSecret);
|
||||||
if (await verifyTOTP(secret, token)) {
|
if (await verifyTOTP(secret, token)) {
|
||||||
await updateUser(userId, { twoFactorEnabled: true });
|
await User.updateUser(userId, { twoFactorEnabled: true });
|
||||||
return res.status(200).json();
|
return res.status(200).json();
|
||||||
}
|
}
|
||||||
return res.status(400).json({ message: 'Invalid token.' });
|
return res.status(400).json({ message: 'Invalid token.' });
|
||||||
|
|
@ -103,7 +103,7 @@ const confirm2FA = async (req, res) => {
|
||||||
const disable2FA = async (req, res) => {
|
const disable2FA = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const userId = req.user.id;
|
const userId = req.user.id;
|
||||||
await updateUser(userId, { totpSecret: null, backupCodes: [], twoFactorEnabled: false });
|
await db.models.User.updateUser(userId, { totpSecret: null, backupCodes: [], twoFactorEnabled: false });
|
||||||
return res.status(200).json();
|
return res.status(200).json();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('[disable2FA]', err);
|
logger.error('[disable2FA]', err);
|
||||||
|
|
@ -118,7 +118,7 @@ const regenerateBackupCodes = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const userId = req.user.id;
|
const userId = req.user.id;
|
||||||
const { plainCodes, codeObjects } = await generateBackupCodes();
|
const { plainCodes, codeObjects } = await generateBackupCodes();
|
||||||
await updateUser(userId, { backupCodes: codeObjects });
|
await db.models.User.updateUser(userId, { backupCodes: codeObjects });
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
backupCodes: plainCodes,
|
backupCodes: plainCodes,
|
||||||
backupCodesHash: codeObjects,
|
backupCodesHash: codeObjects,
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,11 @@ const {
|
||||||
const {
|
const {
|
||||||
Balance,
|
Balance,
|
||||||
getFiles,
|
getFiles,
|
||||||
updateUser,
|
|
||||||
deleteFiles,
|
deleteFiles,
|
||||||
deleteConvos,
|
deleteConvos,
|
||||||
deletePresets,
|
deletePresets,
|
||||||
deleteMessages,
|
deleteMessages,
|
||||||
deleteUserById,
|
|
||||||
deleteAllUserSessions,
|
|
||||||
} = require('~/models');
|
} = require('~/models');
|
||||||
const User = require('~/models/User');
|
|
||||||
const { updateUserPluginAuth, deleteUserPluginAuth } = require('~/server/services/PluginService');
|
const { updateUserPluginAuth, deleteUserPluginAuth } = require('~/server/services/PluginService');
|
||||||
const { updateUserPluginsService, deleteUserKey } = require('~/server/services/UserService');
|
const { updateUserPluginsService, deleteUserKey } = require('~/server/services/UserService');
|
||||||
const { verifyEmail, resendVerificationEmail } = require('~/server/services/AuthService');
|
const { verifyEmail, resendVerificationEmail } = require('~/server/services/AuthService');
|
||||||
|
|
@ -26,6 +22,7 @@ const { deleteAllSharedLinks } = require('~/models/Share');
|
||||||
const { deleteToolCalls } = require('~/models/ToolCall');
|
const { deleteToolCalls } = require('~/models/ToolCall');
|
||||||
const { Transaction } = require('~/models/Transaction');
|
const { Transaction } = require('~/models/Transaction');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
const getUserController = async (req, res) => {
|
const getUserController = async (req, res) => {
|
||||||
/** @type {MongoUser} */
|
/** @type {MongoUser} */
|
||||||
|
|
@ -39,7 +36,7 @@ const getUserController = async (req, res) => {
|
||||||
const originalAvatar = userData.avatar;
|
const originalAvatar = userData.avatar;
|
||||||
try {
|
try {
|
||||||
userData.avatar = await getNewS3URL(userData.avatar);
|
userData.avatar = await getNewS3URL(userData.avatar);
|
||||||
await updateUser(userData.id, { avatar: userData.avatar });
|
await db.models.User.updateUser(userData.id, { avatar: userData.avatar });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
userData.avatar = originalAvatar;
|
userData.avatar = originalAvatar;
|
||||||
logger.error('Error getting new S3 URL for avatar:', error);
|
logger.error('Error getting new S3 URL for avatar:', error);
|
||||||
|
|
@ -50,7 +47,7 @@ const getUserController = async (req, res) => {
|
||||||
|
|
||||||
const getTermsStatusController = async (req, res) => {
|
const getTermsStatusController = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const user = await User.findById(req.user.id);
|
const user = await db.models.User.findById(req.user.id);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return res.status(404).json({ message: 'User not found' });
|
return res.status(404).json({ message: 'User not found' });
|
||||||
}
|
}
|
||||||
|
|
@ -63,7 +60,7 @@ const getTermsStatusController = async (req, res) => {
|
||||||
|
|
||||||
const acceptTermsController = async (req, res) => {
|
const acceptTermsController = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const user = await User.findByIdAndUpdate(req.user.id, { termsAccepted: true }, { new: true });
|
const user = await db.models.User.findByIdAndUpdate(req.user.id, { termsAccepted: true }, { new: true });
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return res.status(404).json({ message: 'User not found' });
|
return res.status(404).json({ message: 'User not found' });
|
||||||
}
|
}
|
||||||
|
|
@ -160,7 +157,7 @@ const deleteUserController = async (req, res) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await deleteMessages({ user: user.id }); // delete user messages
|
await deleteMessages({ user: user.id }); // delete user messages
|
||||||
await deleteAllUserSessions({ userId: user.id }); // delete user sessions
|
await db.models.Session.deleteAllUserSessions({ userId: user.id }); // delete user sessions
|
||||||
await Transaction.deleteMany({ user: user.id }); // delete user transactions
|
await Transaction.deleteMany({ user: user.id }); // delete user transactions
|
||||||
await deleteUserKey({ userId: user.id, all: true }); // delete user keys
|
await deleteUserKey({ userId: user.id, all: true }); // delete user keys
|
||||||
await Balance.deleteMany({ user: user._id }); // delete user balances
|
await Balance.deleteMany({ user: user._id }); // delete user balances
|
||||||
|
|
@ -168,7 +165,7 @@ const deleteUserController = async (req, res) => {
|
||||||
/* TODO: Delete Assistant Threads */
|
/* TODO: Delete Assistant Threads */
|
||||||
await deleteConvos(user.id); // delete user convos
|
await deleteConvos(user.id); // delete user convos
|
||||||
await deleteUserPluginAuth(user.id, null, true); // delete user plugin auth
|
await deleteUserPluginAuth(user.id, null, true); // delete user plugin auth
|
||||||
await deleteUserById(user.id); // delete user
|
await db.models.User.deleteUserById(user.id); // delete user
|
||||||
await deleteAllSharedLinks(user.id); // delete user shared links
|
await deleteAllSharedLinks(user.id); // delete user shared links
|
||||||
await deleteUserFiles(req); // delete user files
|
await deleteUserFiles(req); // delete user files
|
||||||
await deleteFiles(null, user.id); // delete database files in case of orphaned files from previous steps
|
await deleteFiles(null, user.id); // delete database files in case of orphaned files from previous steps
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ const {
|
||||||
getTOTPSecret,
|
getTOTPSecret,
|
||||||
} = require('~/server/services/twoFactorService');
|
} = require('~/server/services/twoFactorService');
|
||||||
const { setAuthTokens } = require('~/server/services/AuthService');
|
const { setAuthTokens } = require('~/server/services/AuthService');
|
||||||
const { getUserById } = require('~/models/userMethods');
|
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies the 2FA code during login using a temporary token.
|
* Verifies the 2FA code during login using a temporary token.
|
||||||
|
|
@ -25,7 +25,7 @@ const verify2FAWithTempToken = async (req, res) => {
|
||||||
return res.status(401).json({ message: 'Invalid or expired temporary token' });
|
return res.status(401).json({ message: 'Invalid or expired temporary token' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await getUserById(payload.userId);
|
const user = await db.models.User.getUserById(payload.userId);
|
||||||
if (!user || !user.twoFactorEnabled) {
|
if (!user || !user.twoFactorEnabled) {
|
||||||
return res.status(400).json({ message: '2FA is not enabled for this user' });
|
return res.status(400).json({ message: '2FA is not enabled for this user' });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ const mongoSanitize = require('express-mongo-sanitize');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const cookieParser = require('cookie-parser');
|
const cookieParser = require('cookie-parser');
|
||||||
const { jwtLogin, passportLogin } = require('~/strategies');
|
const { jwtLogin, passportLogin } = require('~/strategies');
|
||||||
const { connectDb, indexSync } = require('~/lib/db');
|
const { connectDb, indexSync, getModels } = require('~/lib/db');
|
||||||
const { isEnabled } = require('~/server/utils');
|
const { isEnabled } = require('~/server/utils');
|
||||||
const { ldapLogin } = require('~/strategies');
|
const { ldapLogin } = require('~/strategies');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
|
@ -36,6 +36,7 @@ const startServer = async () => {
|
||||||
axios.defaults.headers.common['Accept-Encoding'] = 'gzip';
|
axios.defaults.headers.common['Accept-Encoding'] = 'gzip';
|
||||||
}
|
}
|
||||||
await connectDb();
|
await connectDb();
|
||||||
|
|
||||||
logger.info('Connected to MongoDB');
|
logger.info('Connected to MongoDB');
|
||||||
await indexSync();
|
await indexSync();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,9 @@ const { isEnabled, removePorts } = require('~/server/utils');
|
||||||
const keyvMongo = require('~/cache/keyvMongo');
|
const keyvMongo = require('~/cache/keyvMongo');
|
||||||
const denyRequest = require('./denyRequest');
|
const denyRequest = require('./denyRequest');
|
||||||
const { getLogStores } = require('~/cache');
|
const { getLogStores } = require('~/cache');
|
||||||
const { findUser } = require('~/models');
|
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
|
|
||||||
const banCache = new Keyv({ store: keyvMongo, namespace: ViolationTypes.BAN, ttl: 0 });
|
const banCache = new Keyv({ store: keyvMongo, namespace: ViolationTypes.BAN, ttl: 0 });
|
||||||
const message = 'Your account has been temporarily banned due to violations of our service.';
|
const message = 'Your account has been temporarily banned due to violations of our service.';
|
||||||
|
|
@ -57,7 +58,7 @@ const checkBan = async (req, res, next = () => {}) => {
|
||||||
let userId = req.user?.id ?? req.user?._id ?? null;
|
let userId = req.user?.id ?? req.user?._id ?? null;
|
||||||
|
|
||||||
if (!userId && req?.body?.email) {
|
if (!userId && req?.body?.email) {
|
||||||
const user = await findUser({ email: req.body.email }, '_id');
|
const user = await db.models.User.findUser({ email: req.body.email }, '_id');
|
||||||
userId = user?._id ? user._id.toString() : userId;
|
userId = user?._id ? user._id.toString() : userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const { getInvite } = require('~/models/inviteUser');
|
const { getInvite } = require('~/models/inviteUser');
|
||||||
const { deleteTokens } = require('~/models/Token');
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
async function checkInviteUser(req, res, next) {
|
async function checkInviteUser(req, res, next) {
|
||||||
const token = req.body.token;
|
const token = req.body.token;
|
||||||
|
|
@ -16,7 +16,7 @@ async function checkInviteUser(req, res, next) {
|
||||||
return res.status(400).json({ message: 'Invalid invite token' });
|
return res.status(400).json({ message: 'Invalid invite token' });
|
||||||
}
|
}
|
||||||
|
|
||||||
await deleteTokens({ token: invite.token });
|
await db.models.Token.deleteTokens({ token: invite.token });
|
||||||
req.invite = invite;
|
req.invite = invite;
|
||||||
next();
|
next();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
const { getBalanceConfig } = require('~/server/services/Config');
|
const { getBalanceConfig } = require('~/server/services/Config');
|
||||||
const Balance = require('~/models/Balance');
|
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Middleware to synchronize user balance settings with current balance configuration.
|
* Middleware to synchronize user balance settings with current balance configuration.
|
||||||
|
|
@ -20,14 +20,14 @@ const setBalanceConfig = async (req, res, next) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const userId = req.user._id;
|
const userId = req.user._id;
|
||||||
const userBalanceRecord = await Balance.findOne({ user: userId }).lean();
|
const userBalanceRecord = await db.models.Balance.findOne({ user: userId }).lean();
|
||||||
const updateFields = buildUpdateFields(balanceConfig, userBalanceRecord);
|
const updateFields = buildUpdateFields(balanceConfig, userBalanceRecord);
|
||||||
|
|
||||||
if (Object.keys(updateFields).length === 0) {
|
if (Object.keys(updateFields).length === 0) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
await Balance.findOneAndUpdate(
|
await db.models.Balance.findOneAndUpdate(
|
||||||
{ user: userId },
|
{ user: userId },
|
||||||
{ $set: updateFields },
|
{ $set: updateFields },
|
||||||
{ upsert: true, new: true },
|
{ upsert: true, new: true },
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ const { requireJwtAuth, validateMessageReq } = require('~/server/middleware');
|
||||||
const { cleanUpPrimaryKeyValue } = require('~/lib/utils/misc');
|
const { cleanUpPrimaryKeyValue } = require('~/lib/utils/misc');
|
||||||
const { getConvosQueried } = require('~/models/Conversation');
|
const { getConvosQueried } = require('~/models/Conversation');
|
||||||
const { countTokens } = require('~/server/utils');
|
const { countTokens } = require('~/server/utils');
|
||||||
const { Message } = require('~/models/Message');
|
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
router.use(requireJwtAuth);
|
router.use(requireJwtAuth);
|
||||||
|
|
@ -40,21 +40,25 @@ router.get('/', async (req, res) => {
|
||||||
const sortOrder = sortDirection === 'asc' ? 1 : -1;
|
const sortOrder = sortDirection === 'asc' ? 1 : -1;
|
||||||
|
|
||||||
if (conversationId && messageId) {
|
if (conversationId && messageId) {
|
||||||
const message = await Message.findOne({ conversationId, messageId, user: user }).lean();
|
const message = await db.models.Message.findOne({
|
||||||
|
conversationId,
|
||||||
|
messageId,
|
||||||
|
user: user,
|
||||||
|
}).lean();
|
||||||
response = { messages: message ? [message] : [], nextCursor: null };
|
response = { messages: message ? [message] : [], nextCursor: null };
|
||||||
} else if (conversationId) {
|
} else if (conversationId) {
|
||||||
const filter = { conversationId, user: user };
|
const filter = { conversationId, user: user };
|
||||||
if (cursor) {
|
if (cursor) {
|
||||||
filter[sortField] = sortOrder === 1 ? { $gt: cursor } : { $lt: cursor };
|
filter[sortField] = sortOrder === 1 ? { $gt: cursor } : { $lt: cursor };
|
||||||
}
|
}
|
||||||
const messages = await Message.find(filter)
|
const messages = await db.models.Message.find(filter)
|
||||||
.sort({ [sortField]: sortOrder })
|
.sort({ [sortField]: sortOrder })
|
||||||
.limit(pageSize + 1)
|
.limit(pageSize + 1)
|
||||||
.lean();
|
.lean();
|
||||||
const nextCursor = messages.length > pageSize ? messages.pop()[sortField] : null;
|
const nextCursor = messages.length > pageSize ? messages.pop()[sortField] : null;
|
||||||
response = { messages, nextCursor };
|
response = { messages, nextCursor };
|
||||||
} else if (search) {
|
} else if (search) {
|
||||||
const searchResults = await Message.meiliSearch(search, undefined, true);
|
const searchResults = await db.models.Message.meiliSearch(search, undefined, true);
|
||||||
|
|
||||||
const messages = searchResults.hits || [];
|
const messages = searchResults.hits || [];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,9 @@ const { logger, getFlowStateManager, sendEvent } = require('~/config');
|
||||||
const { encryptV2, decryptV2 } = require('~/server/utils/crypto');
|
const { encryptV2, decryptV2 } = require('~/server/utils/crypto');
|
||||||
const { getActions, deleteActions } = require('~/models/Action');
|
const { getActions, deleteActions } = require('~/models/Action');
|
||||||
const { deleteAssistant } = require('~/models/Assistant');
|
const { deleteAssistant } = require('~/models/Assistant');
|
||||||
const { findToken } = require('~/models/Token');
|
|
||||||
const { logAxiosError } = require('~/utils');
|
const { logAxiosError } = require('~/utils');
|
||||||
const { getLogStores } = require('~/cache');
|
const { getLogStores } = require('~/cache');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
const JWT_SECRET = process.env.JWT_SECRET;
|
const JWT_SECRET = process.env.JWT_SECRET;
|
||||||
const toolNameRegex = /^[a-zA-Z0-9_-]+$/;
|
const toolNameRegex = /^[a-zA-Z0-9_-]+$/;
|
||||||
|
|
@ -231,9 +231,10 @@ async function createActionTool({
|
||||||
};
|
};
|
||||||
|
|
||||||
const tokenPromises = [];
|
const tokenPromises = [];
|
||||||
tokenPromises.push(findToken({ userId, type: 'oauth', identifier }));
|
const { Token } = db.models;
|
||||||
|
tokenPromises.push(Token.findToken({ userId, type: 'oauth', identifier }));
|
||||||
tokenPromises.push(
|
tokenPromises.push(
|
||||||
findToken({
|
Token.findToken({
|
||||||
userId,
|
userId,
|
||||||
type: 'oauth_refresh',
|
type: 'oauth_refresh',
|
||||||
identifier: `${identifier}:refresh`,
|
identifier: `${identifier}:refresh`,
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,12 @@
|
||||||
const bcrypt = require('bcryptjs');
|
const bcrypt = require('bcryptjs');
|
||||||
const { webcrypto } = require('node:crypto');
|
const { webcrypto } = require('node:crypto');
|
||||||
const { SystemRoles, errorsToString } = require('librechat-data-provider');
|
const { SystemRoles, errorsToString } = require('librechat-data-provider');
|
||||||
const {
|
|
||||||
findUser,
|
|
||||||
countUsers,
|
|
||||||
createUser,
|
|
||||||
updateUser,
|
|
||||||
getUserById,
|
|
||||||
generateToken,
|
|
||||||
deleteUserById,
|
|
||||||
} = require('~/models/userMethods');
|
|
||||||
const {
|
|
||||||
createToken,
|
|
||||||
findToken,
|
|
||||||
deleteTokens,
|
|
||||||
findSession,
|
|
||||||
deleteSession,
|
|
||||||
createSession,
|
|
||||||
generateRefreshToken,
|
|
||||||
} = require('~/models');
|
|
||||||
const { isEnabled, checkEmailConfig, sendEmail } = require('~/server/utils');
|
const { isEnabled, checkEmailConfig, sendEmail } = require('~/server/utils');
|
||||||
const { isEmailDomainAllowed } = require('~/server/services/domains');
|
const { isEmailDomainAllowed } = require('~/server/services/domains');
|
||||||
const { registerSchema } = require('~/strategies/validators');
|
const { registerSchema } = require('~/strategies/validators');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
const { getBalanceConfig } = require('~/server/services/Config');
|
||||||
|
|
||||||
const domains = {
|
const domains = {
|
||||||
client: process.env.DOMAIN_CLIENT,
|
client: process.env.DOMAIN_CLIENT,
|
||||||
|
|
@ -40,13 +24,14 @@ const genericVerificationMessage = 'Please check your email to verify your email
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const logoutUser = async (req, refreshToken) => {
|
const logoutUser = async (req, refreshToken) => {
|
||||||
|
const { Session } = db.models;
|
||||||
try {
|
try {
|
||||||
const userId = req.user._id;
|
const userId = req.user._id;
|
||||||
const session = await findSession({ userId: userId, refreshToken });
|
const session = await Session.findSession({ userId: userId, refreshToken });
|
||||||
|
|
||||||
if (session) {
|
if (session) {
|
||||||
try {
|
try {
|
||||||
await deleteSession({ sessionId: session._id });
|
await Session.deleteSession({ sessionId: session._id });
|
||||||
} catch (deleteErr) {
|
} catch (deleteErr) {
|
||||||
logger.error('[logoutUser] Failed to delete session.', deleteErr);
|
logger.error('[logoutUser] Failed to delete session.', deleteErr);
|
||||||
return { status: 500, message: 'Failed to delete session.' };
|
return { status: 500, message: 'Failed to delete session.' };
|
||||||
|
|
@ -98,7 +83,7 @@ const sendVerificationEmail = async (user) => {
|
||||||
template: 'verifyEmail.handlebars',
|
template: 'verifyEmail.handlebars',
|
||||||
});
|
});
|
||||||
|
|
||||||
await createToken({
|
await db.models.Token.createToken({
|
||||||
userId: user._id,
|
userId: user._id,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
token: hash,
|
token: hash,
|
||||||
|
|
@ -117,7 +102,8 @@ const verifyEmail = async (req) => {
|
||||||
const { email, token } = req.body;
|
const { email, token } = req.body;
|
||||||
const decodedEmail = decodeURIComponent(email);
|
const decodedEmail = decodeURIComponent(email);
|
||||||
|
|
||||||
const user = await findUser({ email: decodedEmail }, 'email _id emailVerified');
|
const { User, Token } = db.models;
|
||||||
|
const user = await User.findUser({ email: decodedEmail }, 'email _id emailVerified');
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
logger.warn(`[verifyEmail] [User not found] [Email: ${decodedEmail}]`);
|
logger.warn(`[verifyEmail] [User not found] [Email: ${decodedEmail}]`);
|
||||||
|
|
@ -129,7 +115,7 @@ const verifyEmail = async (req) => {
|
||||||
return { message: 'Email already verified', status: 'success' };
|
return { message: 'Email already verified', status: 'success' };
|
||||||
}
|
}
|
||||||
|
|
||||||
let emailVerificationData = await findToken({ email: decodedEmail });
|
let emailVerificationData = await Token.findToken({ email: decodedEmail });
|
||||||
|
|
||||||
if (!emailVerificationData) {
|
if (!emailVerificationData) {
|
||||||
logger.warn(`[verifyEmail] [No email verification data found] [Email: ${decodedEmail}]`);
|
logger.warn(`[verifyEmail] [No email verification data found] [Email: ${decodedEmail}]`);
|
||||||
|
|
@ -145,13 +131,13 @@ const verifyEmail = async (req) => {
|
||||||
return new Error('Invalid or expired email verification token');
|
return new Error('Invalid or expired email verification token');
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedUser = await updateUser(emailVerificationData.userId, { emailVerified: true });
|
const updatedUser = await User.updateUser(emailVerificationData.userId, { emailVerified: true });
|
||||||
if (!updatedUser) {
|
if (!updatedUser) {
|
||||||
logger.warn(`[verifyEmail] [User update failed] [Email: ${decodedEmail}]`);
|
logger.warn(`[verifyEmail] [User update failed] [Email: ${decodedEmail}]`);
|
||||||
return new Error('Failed to update user verification status');
|
return new Error('Failed to update user verification status');
|
||||||
}
|
}
|
||||||
|
|
||||||
await deleteTokens({ token: emailVerificationData.token });
|
await Token.deleteTokens({ token: emailVerificationData.token });
|
||||||
logger.info(`[verifyEmail] Email verification successful [Email: ${decodedEmail}]`);
|
logger.info(`[verifyEmail] Email verification successful [Email: ${decodedEmail}]`);
|
||||||
return { message: 'Email verification was successful', status: 'success' };
|
return { message: 'Email verification was successful', status: 'success' };
|
||||||
};
|
};
|
||||||
|
|
@ -162,6 +148,7 @@ const verifyEmail = async (req) => {
|
||||||
* @returns {Promise<{status: number, message: string, user?: MongoUser}>}
|
* @returns {Promise<{status: number, message: string, user?: MongoUser}>}
|
||||||
*/
|
*/
|
||||||
const registerUser = async (user, additionalData = {}) => {
|
const registerUser = async (user, additionalData = {}) => {
|
||||||
|
const { User } = db.models;
|
||||||
const { error } = registerSchema.safeParse(user);
|
const { error } = registerSchema.safeParse(user);
|
||||||
if (error) {
|
if (error) {
|
||||||
const errorMessage = errorsToString(error.errors);
|
const errorMessage = errorsToString(error.errors);
|
||||||
|
|
@ -178,7 +165,7 @@ const registerUser = async (user, additionalData = {}) => {
|
||||||
|
|
||||||
let newUserId;
|
let newUserId;
|
||||||
try {
|
try {
|
||||||
const existingUser = await findUser({ email }, 'email _id');
|
const existingUser = await User.findUser({ email }, 'email _id');
|
||||||
|
|
||||||
if (existingUser) {
|
if (existingUser) {
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|
@ -200,7 +187,7 @@ const registerUser = async (user, additionalData = {}) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
//determine if this is the first registered user (not counting anonymous_user)
|
//determine if this is the first registered user (not counting anonymous_user)
|
||||||
const isFirstRegisteredUser = (await countUsers()) === 0;
|
const isFirstRegisteredUser = (await User.countUsers()) === 0;
|
||||||
|
|
||||||
const salt = bcrypt.genSaltSync(10);
|
const salt = bcrypt.genSaltSync(10);
|
||||||
const newUserData = {
|
const newUserData = {
|
||||||
|
|
@ -216,7 +203,9 @@ const registerUser = async (user, additionalData = {}) => {
|
||||||
|
|
||||||
const emailEnabled = checkEmailConfig();
|
const emailEnabled = checkEmailConfig();
|
||||||
const disableTTL = isEnabled(process.env.ALLOW_UNVERIFIED_EMAIL_LOGIN);
|
const disableTTL = isEnabled(process.env.ALLOW_UNVERIFIED_EMAIL_LOGIN);
|
||||||
const newUser = await createUser(newUserData, disableTTL, true);
|
const balanceConfig = await getBalanceConfig();
|
||||||
|
|
||||||
|
const newUser = await User.createUser(newUserData, balanceConfig, disableTTL, true);
|
||||||
newUserId = newUser._id;
|
newUserId = newUser._id;
|
||||||
if (emailEnabled && !newUser.emailVerified) {
|
if (emailEnabled && !newUser.emailVerified) {
|
||||||
await sendVerificationEmail({
|
await sendVerificationEmail({
|
||||||
|
|
@ -225,14 +214,14 @@ const registerUser = async (user, additionalData = {}) => {
|
||||||
name,
|
name,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await updateUser(newUserId, { emailVerified: true });
|
await User.updateUser(newUserId, { emailVerified: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
return { status: 200, message: genericVerificationMessage };
|
return { status: 200, message: genericVerificationMessage };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('[registerUser] Error in registering user:', err);
|
logger.error('[registerUser] Error in registering user:', err);
|
||||||
if (newUserId) {
|
if (newUserId) {
|
||||||
const result = await deleteUserById(newUserId);
|
const result = await User.deleteUserById(newUserId);
|
||||||
logger.warn(
|
logger.warn(
|
||||||
`[registerUser] [Email: ${email}] [Temporary User deleted: ${JSON.stringify(result)}]`,
|
`[registerUser] [Email: ${email}] [Temporary User deleted: ${JSON.stringify(result)}]`,
|
||||||
);
|
);
|
||||||
|
|
@ -247,7 +236,8 @@ const registerUser = async (user, additionalData = {}) => {
|
||||||
*/
|
*/
|
||||||
const requestPasswordReset = async (req) => {
|
const requestPasswordReset = async (req) => {
|
||||||
const { email } = req.body;
|
const { email } = req.body;
|
||||||
const user = await findUser({ email }, 'email _id');
|
const { User, Token } = db.models;
|
||||||
|
const user = await User.findUser({ email }, 'email _id');
|
||||||
const emailEnabled = checkEmailConfig();
|
const emailEnabled = checkEmailConfig();
|
||||||
|
|
||||||
logger.warn(`[requestPasswordReset] [Password reset request initiated] [Email: ${email}]`);
|
logger.warn(`[requestPasswordReset] [Password reset request initiated] [Email: ${email}]`);
|
||||||
|
|
@ -259,11 +249,11 @@ const requestPasswordReset = async (req) => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
await deleteTokens({ userId: user._id });
|
await Token.deleteTokens({ userId: user._id });
|
||||||
|
|
||||||
const [resetToken, hash] = createTokenHash();
|
const [resetToken, hash] = createTokenHash();
|
||||||
|
|
||||||
await createToken({
|
await Token.createToken({
|
||||||
userId: user._id,
|
userId: user._id,
|
||||||
token: hash,
|
token: hash,
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
|
|
@ -308,7 +298,8 @@ const requestPasswordReset = async (req) => {
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const resetPassword = async (userId, token, password) => {
|
const resetPassword = async (userId, token, password) => {
|
||||||
let passwordResetToken = await findToken({
|
const { User, Token } = db.models;
|
||||||
|
let passwordResetToken = await Token.findToken({
|
||||||
userId,
|
userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -323,7 +314,7 @@ const resetPassword = async (userId, token, password) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const hash = bcrypt.hashSync(password, 10);
|
const hash = bcrypt.hashSync(password, 10);
|
||||||
const user = await updateUser(userId, { password: hash });
|
const user = await User.updateUser(userId, { password: hash });
|
||||||
|
|
||||||
if (checkEmailConfig()) {
|
if (checkEmailConfig()) {
|
||||||
await sendEmail({
|
await sendEmail({
|
||||||
|
|
@ -338,7 +329,7 @@ const resetPassword = async (userId, token, password) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await deleteTokens({ token: passwordResetToken.token });
|
await Token.deleteTokens({ token: passwordResetToken.token });
|
||||||
logger.info(`[resetPassword] Password reset successful. [Email: ${user.email}]`);
|
logger.info(`[resetPassword] Password reset successful. [Email: ${user.email}]`);
|
||||||
return { message: 'Password reset was successful' };
|
return { message: 'Password reset was successful' };
|
||||||
};
|
};
|
||||||
|
|
@ -353,19 +344,20 @@ const resetPassword = async (userId, token, password) => {
|
||||||
*/
|
*/
|
||||||
const setAuthTokens = async (userId, res, sessionId = null) => {
|
const setAuthTokens = async (userId, res, sessionId = null) => {
|
||||||
try {
|
try {
|
||||||
const user = await getUserById(userId);
|
const { User, Session } = db.models;
|
||||||
const token = await generateToken(user);
|
const user = await User.getUserById(userId);
|
||||||
|
const token = await User.generateToken(user);
|
||||||
|
|
||||||
let session;
|
let session;
|
||||||
let refreshToken;
|
let refreshToken;
|
||||||
let refreshTokenExpires;
|
let refreshTokenExpires;
|
||||||
|
|
||||||
if (sessionId) {
|
if (sessionId) {
|
||||||
session = await findSession({ sessionId: sessionId }, { lean: false });
|
session = await Session.findSession({ sessionId: sessionId }, { lean: false });
|
||||||
refreshTokenExpires = session.expiration.getTime();
|
refreshTokenExpires = session.expiration.getTime();
|
||||||
refreshToken = await generateRefreshToken(session);
|
refreshToken = await Session.generateRefreshToken(session);
|
||||||
} else {
|
} else {
|
||||||
const result = await createSession(userId);
|
const result = await Session.createSession(userId);
|
||||||
session = result.session;
|
session = result.session;
|
||||||
refreshToken = result.refreshToken;
|
refreshToken = result.refreshToken;
|
||||||
refreshTokenExpires = session.expiration.getTime();
|
refreshTokenExpires = session.expiration.getTime();
|
||||||
|
|
@ -444,8 +436,9 @@ const setOpenIDAuthTokens = (tokenset, res) => {
|
||||||
const resendVerificationEmail = async (req) => {
|
const resendVerificationEmail = async (req) => {
|
||||||
try {
|
try {
|
||||||
const { email } = req.body;
|
const { email } = req.body;
|
||||||
await deleteTokens(email);
|
const { User, Token } = db.models;
|
||||||
const user = await findUser({ email }, 'email _id name');
|
await Token.deleteTokens(email);
|
||||||
|
const user = await User.findUser({ email }, 'email _id name');
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
logger.warn(`[resendVerificationEmail] [No user found] [Email: ${email}]`);
|
logger.warn(`[resendVerificationEmail] [No user found] [Email: ${email}]`);
|
||||||
|
|
@ -470,7 +463,7 @@ const resendVerificationEmail = async (req) => {
|
||||||
template: 'verifyEmail.handlebars',
|
template: 'verifyEmail.handlebars',
|
||||||
});
|
});
|
||||||
|
|
||||||
await createToken({
|
await Token.createToken({
|
||||||
userId: user._id,
|
userId: user._id,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
token: hash,
|
token: hash,
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,10 @@ const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const sharp = require('sharp');
|
const sharp = require('sharp');
|
||||||
const { resizeImageBuffer } = require('../images/resize');
|
const { resizeImageBuffer } = require('../images/resize');
|
||||||
const { updateUser } = require('~/models/userMethods');
|
|
||||||
const { updateFile } = require('~/models/File');
|
const { updateFile } = require('~/models/File');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
const { saveBufferToAzure } = require('./crud');
|
const { saveBufferToAzure } = require('./crud');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
/**
|
/**
|
||||||
* Uploads an image file to Azure Blob Storage.
|
* Uploads an image file to Azure Blob Storage.
|
||||||
* It resizes and converts the image similar to your Firebase implementation.
|
* It resizes and converts the image similar to your Firebase implementation.
|
||||||
|
|
@ -108,7 +107,7 @@ async function processAzureAvatar({ buffer, userId, manual, basePath = 'images',
|
||||||
const isManual = manual === 'true';
|
const isManual = manual === 'true';
|
||||||
const url = `${downloadURL}?manual=${isManual}`;
|
const url = `${downloadURL}?manual=${isManual}`;
|
||||||
if (isManual) {
|
if (isManual) {
|
||||||
await updateUser(userId, { avatar: url });
|
await db.models?.User.updateUser(userId, { avatar: url });
|
||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@ const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const sharp = require('sharp');
|
const sharp = require('sharp');
|
||||||
const { resizeImageBuffer } = require('../images/resize');
|
const { resizeImageBuffer } = require('../images/resize');
|
||||||
const { updateUser } = require('~/models/userMethods');
|
|
||||||
const { saveBufferToFirebase } = require('./crud');
|
const { saveBufferToFirebase } = require('./crud');
|
||||||
const { updateFile } = require('~/models/File');
|
const { updateFile } = require('~/models/File');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts an image file to the target format. The function first resizes the image based on the specified
|
* Converts an image file to the target format. The function first resizes the image based on the specified
|
||||||
|
|
@ -99,7 +99,7 @@ async function processFirebaseAvatar({ buffer, userId, manual }) {
|
||||||
const url = `${downloadURL}?manual=${isManual}`;
|
const url = `${downloadURL}?manual=${isManual}`;
|
||||||
|
|
||||||
if (isManual) {
|
if (isManual) {
|
||||||
await updateUser(userId, { avatar: url });
|
await db.models.User.updateUser(userId, { avatar: url });
|
||||||
}
|
}
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const sharp = require('sharp');
|
const sharp = require('sharp');
|
||||||
const { resizeImageBuffer } = require('../images/resize');
|
const { resizeImageBuffer } = require('../images/resize');
|
||||||
const { updateUser } = require('~/models/userMethods');
|
|
||||||
const { updateFile } = require('~/models/File');
|
const { updateFile } = require('~/models/File');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts an image file to the target format. The function first resizes the image based on the specified
|
* Converts an image file to the target format. The function first resizes the image based on the specified
|
||||||
|
|
@ -141,7 +141,7 @@ async function processLocalAvatar({ buffer, userId, manual }) {
|
||||||
let url = `${urlRoute}?manual=${isManual}`;
|
let url = `${urlRoute}?manual=${isManual}`;
|
||||||
|
|
||||||
if (isManual) {
|
if (isManual) {
|
||||||
await updateUser(userId, { avatar: url });
|
await db.models?.User.updateUser(userId, { avatar: url });
|
||||||
}
|
}
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@ const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const sharp = require('sharp');
|
const sharp = require('sharp');
|
||||||
const { resizeImageBuffer } = require('../images/resize');
|
const { resizeImageBuffer } = require('../images/resize');
|
||||||
const { updateUser } = require('~/models/userMethods');
|
|
||||||
const { saveBufferToS3 } = require('./crud');
|
const { saveBufferToS3 } = require('./crud');
|
||||||
const { updateFile } = require('~/models/File');
|
const { updateFile } = require('~/models/File');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
const defaultBasePath = 'images';
|
const defaultBasePath = 'images';
|
||||||
|
|
||||||
|
|
@ -102,7 +102,7 @@ async function processS3Avatar({ buffer, userId, manual, basePath = defaultBaseP
|
||||||
try {
|
try {
|
||||||
const downloadURL = await saveBufferToS3({ userId, buffer, fileName: 'avatar.png', basePath });
|
const downloadURL = await saveBufferToS3({ userId, buffer, fileName: 'avatar.png', basePath });
|
||||||
if (manual === 'true') {
|
if (manual === 'true') {
|
||||||
await updateUser(userId, { avatar: downloadURL });
|
await db.models?.User.updateUser(userId, { avatar: downloadURL });
|
||||||
}
|
}
|
||||||
return downloadURL;
|
return downloadURL;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
const { ErrorTypes } = require('librechat-data-provider');
|
const { ErrorTypes } = require('librechat-data-provider');
|
||||||
const { encrypt, decrypt } = require('~/server/utils');
|
const { encrypt, decrypt } = require('~/server/utils');
|
||||||
const { updateUser, Key } = require('~/models');
|
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
/**
|
/**
|
||||||
* Updates the plugins for a user based on the action specified (install/uninstall).
|
* Updates the plugins for a user based on the action specified (install/uninstall).
|
||||||
* @async
|
* @async
|
||||||
|
|
@ -17,10 +16,11 @@ const { logger } = require('~/config');
|
||||||
const updateUserPluginsService = async (user, pluginKey, action) => {
|
const updateUserPluginsService = async (user, pluginKey, action) => {
|
||||||
try {
|
try {
|
||||||
const userPlugins = user.plugins || [];
|
const userPlugins = user.plugins || [];
|
||||||
|
const { User } = db.models;
|
||||||
if (action === 'install') {
|
if (action === 'install') {
|
||||||
return await updateUser(user._id, { plugins: [...userPlugins, pluginKey] });
|
return await User.updateUser(user._id, { plugins: [...userPlugins, pluginKey] });
|
||||||
} else if (action === 'uninstall') {
|
} else if (action === 'uninstall') {
|
||||||
return await updateUser(user._id, {
|
return await User.updateUser(user._id, {
|
||||||
plugins: userPlugins.filter((plugin) => plugin !== pluginKey),
|
plugins: userPlugins.filter((plugin) => plugin !== pluginKey),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -42,7 +42,7 @@ const updateUserPluginsService = async (user, pluginKey, action) => {
|
||||||
* an error indicating that there is no user key available.
|
* an error indicating that there is no user key available.
|
||||||
*/
|
*/
|
||||||
const getUserKey = async ({ userId, name }) => {
|
const getUserKey = async ({ userId, name }) => {
|
||||||
const keyValue = await Key.findOne({ userId, name }).lean();
|
const keyValue = await db.models.Key.findOne({ userId, name }).lean();
|
||||||
if (!keyValue) {
|
if (!keyValue) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
|
@ -89,7 +89,7 @@ const getUserKeyValues = async ({ userId, name }) => {
|
||||||
* returns its expiry date. If the key is not found, it returns null for the expiry date.
|
* returns its expiry date. If the key is not found, it returns null for the expiry date.
|
||||||
*/
|
*/
|
||||||
const getUserKeyExpiry = async ({ userId, name }) => {
|
const getUserKeyExpiry = async ({ userId, name }) => {
|
||||||
const keyValue = await Key.findOne({ userId, name }).lean();
|
const keyValue = await db.models.Key.findOne({ userId, name }).lean();
|
||||||
if (!keyValue) {
|
if (!keyValue) {
|
||||||
return { expiresAt: null };
|
return { expiresAt: null };
|
||||||
}
|
}
|
||||||
|
|
@ -123,7 +123,7 @@ const updateUserKey = async ({ userId, name, value, expiresAt = null }) => {
|
||||||
// make sure to remove if already present
|
// make sure to remove if already present
|
||||||
updateQuery.$unset = { expiresAt };
|
updateQuery.$unset = { expiresAt };
|
||||||
}
|
}
|
||||||
return await Key.findOneAndUpdate({ userId, name }, updateQuery, {
|
return await db.models.Key.findOneAndUpdate({ userId, name }, updateQuery, {
|
||||||
upsert: true,
|
upsert: true,
|
||||||
new: true,
|
new: true,
|
||||||
}).lean();
|
}).lean();
|
||||||
|
|
@ -143,10 +143,10 @@ const updateUserKey = async ({ userId, name, value, expiresAt = null }) => {
|
||||||
*/
|
*/
|
||||||
const deleteUserKey = async ({ userId, name, all = false }) => {
|
const deleteUserKey = async ({ userId, name, all = false }) => {
|
||||||
if (all) {
|
if (all) {
|
||||||
return await Key.deleteMany({ userId });
|
return await db.models.Key.deleteMany({ userId });
|
||||||
}
|
}
|
||||||
|
|
||||||
await Key.findOneAndDelete({ userId, name }).lean();
|
await db.models.Key.findOneAndDelete({ userId, name }).lean();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
const { webcrypto } = require('node:crypto');
|
const { webcrypto } = require('node:crypto');
|
||||||
const { decryptV3, decryptV2 } = require('../utils/crypto');
|
const { decryptV3, decryptV2 } = require('../utils/crypto');
|
||||||
const { hashBackupCode } = require('~/server/utils/crypto');
|
const { hashBackupCode } = require('~/server/utils/crypto');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
// Base32 alphabet for TOTP secret encoding.
|
// Base32 alphabet for TOTP secret encoding.
|
||||||
const BASE32_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
const BASE32_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
||||||
|
|
@ -172,8 +173,7 @@ const verifyBackupCode = async ({ user, backupCode }) => {
|
||||||
: codeObj,
|
: codeObj,
|
||||||
);
|
);
|
||||||
// Update the user record with the marked backup code.
|
// Update the user record with the marked backup code.
|
||||||
const { updateUser } = require('~/models');
|
await db.models.User.updateUser(user._id, { backupCodes: updatedBackupCodes });
|
||||||
await updateUser(user._id, { backupCodes: updatedBackupCodes });
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ const crypto = require('node:crypto');
|
||||||
const { webcrypto } = crypto;
|
const { webcrypto } = crypto;
|
||||||
|
|
||||||
// Use hex decoding for both key and IV for legacy methods.
|
// Use hex decoding for both key and IV for legacy methods.
|
||||||
const key = Buffer.from(process.env.CREDS_KEY, 'hex');
|
const key = Buffer.from('f34be427ebb29de8d88c107a71546019685ed8b241d8f2ed00c3df97ad2566f0', 'hex');
|
||||||
const iv = Buffer.from(process.env.CREDS_IV, 'hex');
|
const iv = Buffer.from('e2341419ec3dd3d19b13a1a87fafcbfb', 'hex');
|
||||||
const algorithm = 'AES-CBC';
|
const algorithm = 'AES-CBC';
|
||||||
|
|
||||||
// --- Legacy v1/v2 Setup: AES-CBC with fixed key and IV ---
|
// --- Legacy v1/v2 Setup: AES-CBC with fixed key and IV ---
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,9 @@
|
||||||
|
jest.mock('~/models/Message', () => ({
|
||||||
|
Message: jest.fn(),
|
||||||
|
}));
|
||||||
|
jest.mock('~/models/Conversation', () => ({
|
||||||
|
Conversation: jest.fn(),
|
||||||
|
}));
|
||||||
const { isEnabled, sanitizeFilename } = require('./handleText');
|
const { isEnabled, sanitizeFilename } = require('./handleText');
|
||||||
|
|
||||||
describe('isEnabled', () => {
|
describe('isEnabled', () => {
|
||||||
|
|
|
||||||
|
|
@ -18,17 +18,13 @@ const getProfileDetails = ({ idToken, profile }) => {
|
||||||
|
|
||||||
const decoded = jwt.decode(idToken);
|
const decoded = jwt.decode(idToken);
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(`Decoded Apple JWT: ${JSON.stringify(decoded, null, 2)}`);
|
||||||
`Decoded Apple JWT: ${JSON.stringify(decoded, null, 2)}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
email: decoded.email,
|
email: decoded.email,
|
||||||
id: decoded.sub,
|
id: decoded.sub,
|
||||||
avatarUrl: null, // Apple does not provide an avatar URL
|
avatarUrl: null, // Apple does not provide an avatar URL
|
||||||
username: decoded.email
|
username: decoded.email ? decoded.email.split('@')[0].toLowerCase() : `user_${decoded.sub}`,
|
||||||
? decoded.email.split('@')[0].toLowerCase()
|
|
||||||
: `user_${decoded.sub}`,
|
|
||||||
name: decoded.name
|
name: decoded.name
|
||||||
? `${decoded.name.firstName} ${decoded.name.lastName}`
|
? `${decoded.name.firstName} ${decoded.name.lastName}`
|
||||||
: profile.displayName || null,
|
: profile.displayName || null,
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,10 @@ const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||||
const jwt = require('jsonwebtoken');
|
const jwt = require('jsonwebtoken');
|
||||||
const { Strategy: AppleStrategy } = require('passport-apple');
|
const { Strategy: AppleStrategy } = require('passport-apple');
|
||||||
const socialLogin = require('./socialLogin');
|
const socialLogin = require('./socialLogin');
|
||||||
const User = require('~/models/User');
|
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
const { createSocialUser, handleExistingUser } = require('./process');
|
const { createSocialUser, handleExistingUser } = require('./process');
|
||||||
const { isEnabled } = require('~/server/utils');
|
const { isEnabled } = require('~/server/utils');
|
||||||
const { findUser } = require('~/models');
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
// Mocking external dependencies
|
// Mocking external dependencies
|
||||||
jest.mock('jsonwebtoken');
|
jest.mock('jsonwebtoken');
|
||||||
|
|
@ -24,21 +23,20 @@ jest.mock('./process', () => ({
|
||||||
jest.mock('~/server/utils', () => ({
|
jest.mock('~/server/utils', () => ({
|
||||||
isEnabled: jest.fn(),
|
isEnabled: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('~/models', () => ({
|
|
||||||
findUser: jest.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('Apple Login Strategy', () => {
|
describe('Apple Login Strategy', () => {
|
||||||
let mongoServer;
|
let mongoServer;
|
||||||
let appleStrategyInstance;
|
let appleStrategyInstance;
|
||||||
const OLD_ENV = process.env;
|
const OLD_ENV = process.env;
|
||||||
let getProfileDetails;
|
let getProfileDetails;
|
||||||
|
let User;
|
||||||
// Start and stop in-memory MongoDB
|
// Start and stop in-memory MongoDB
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
mongoServer = await MongoMemoryServer.create();
|
mongoServer = await MongoMemoryServer.create();
|
||||||
const mongoUri = mongoServer.getUri();
|
const mongoUri = mongoServer.getUri();
|
||||||
await mongoose.connect(mongoUri);
|
await db.connectDb(mongoUri);
|
||||||
|
|
||||||
|
User = db.models.User;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|
@ -64,7 +62,6 @@ describe('Apple Login Strategy', () => {
|
||||||
|
|
||||||
// Define getProfileDetails within the test scope
|
// Define getProfileDetails within the test scope
|
||||||
getProfileDetails = ({ idToken, profile }) => {
|
getProfileDetails = ({ idToken, profile }) => {
|
||||||
console.log('getProfileDetails called with idToken:', idToken);
|
|
||||||
if (!idToken) {
|
if (!idToken) {
|
||||||
logger.error('idToken is missing');
|
logger.error('idToken is missing');
|
||||||
throw new Error('idToken is missing');
|
throw new Error('idToken is missing');
|
||||||
|
|
@ -84,9 +81,7 @@ describe('Apple Login Strategy', () => {
|
||||||
email: decoded.email,
|
email: decoded.email,
|
||||||
id: decoded.sub,
|
id: decoded.sub,
|
||||||
avatarUrl: null, // Apple does not provide an avatar URL
|
avatarUrl: null, // Apple does not provide an avatar URL
|
||||||
username: decoded.email
|
username: decoded.email ? decoded.email.split('@')[0].toLowerCase() : `user_${decoded.sub}`,
|
||||||
? decoded.email.split('@')[0].toLowerCase()
|
|
||||||
: `user_${decoded.sub}`,
|
|
||||||
name: decoded.name
|
name: decoded.name
|
||||||
? `${decoded.name.firstName} ${decoded.name.lastName}`
|
? `${decoded.name.firstName} ${decoded.name.lastName}`
|
||||||
: profile.displayName || null,
|
: profile.displayName || null,
|
||||||
|
|
@ -96,8 +91,12 @@ describe('Apple Login Strategy', () => {
|
||||||
|
|
||||||
// Mock isEnabled based on environment variable
|
// Mock isEnabled based on environment variable
|
||||||
isEnabled.mockImplementation((flag) => {
|
isEnabled.mockImplementation((flag) => {
|
||||||
if (flag === 'true') { return true; }
|
if (flag === 'true') {
|
||||||
if (flag === 'false') { return false; }
|
return true;
|
||||||
|
}
|
||||||
|
if (flag === 'false') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -154,9 +153,7 @@ describe('Apple Login Strategy', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(jwt.decode).toHaveBeenCalledWith('fake_id_token');
|
expect(jwt.decode).toHaveBeenCalledWith('fake_id_token');
|
||||||
expect(logger.debug).toHaveBeenCalledWith(
|
expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining('Decoded Apple JWT'));
|
||||||
expect.stringContaining('Decoded Apple JWT'),
|
|
||||||
);
|
|
||||||
expect(profileDetails).toEqual({
|
expect(profileDetails).toEqual({
|
||||||
email: 'john.doe@example.com',
|
email: 'john.doe@example.com',
|
||||||
id: 'apple-sub-1234',
|
id: 'apple-sub-1234',
|
||||||
|
|
@ -209,12 +206,13 @@ describe('Apple Login Strategy', () => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jwt.decode.mockReturnValue(decodedToken);
|
jwt.decode.mockReturnValue(decodedToken);
|
||||||
findUser.mockImplementation(({ email }) => User.findOne({ email }));
|
User.findUser = jest.fn();
|
||||||
|
User.findUser.mockImplementation(({ email }) => User.findOne({ email }));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a new user if one does not exist and registration is allowed', async () => {
|
it('should create a new user if one does not exist and registration is allowed', async () => {
|
||||||
// Mock findUser to return null (user does not exist)
|
// Mock findUser to return null (user does not exist)
|
||||||
findUser.mockResolvedValue(null);
|
User.findUser.mockResolvedValue(null);
|
||||||
|
|
||||||
// Mock createSocialUser to create a user
|
// Mock createSocialUser to create a user
|
||||||
createSocialUser.mockImplementation(async (userData) => {
|
createSocialUser.mockImplementation(async (userData) => {
|
||||||
|
|
@ -260,7 +258,7 @@ describe('Apple Login Strategy', () => {
|
||||||
await existingUser.save();
|
await existingUser.save();
|
||||||
|
|
||||||
// Mock findUser to return the existing user
|
// Mock findUser to return the existing user
|
||||||
findUser.mockResolvedValue(existingUser);
|
User.findUser.mockResolvedValue(existingUser);
|
||||||
|
|
||||||
// Mock handleExistingUser to update avatarUrl
|
// Mock handleExistingUser to update avatarUrl
|
||||||
handleExistingUser.mockImplementation(async (user, avatarUrl) => {
|
handleExistingUser.mockImplementation(async (user, avatarUrl) => {
|
||||||
|
|
@ -297,7 +295,7 @@ describe('Apple Login Strategy', () => {
|
||||||
appleStrategyInstance._verify(
|
appleStrategyInstance._verify(
|
||||||
fakeAccessToken,
|
fakeAccessToken,
|
||||||
fakeRefreshToken,
|
fakeRefreshToken,
|
||||||
null, // idToken is missing
|
null, // idToken is missing
|
||||||
mockProfile,
|
mockProfile,
|
||||||
(err, user) => {
|
(err, user) => {
|
||||||
mockVerifyCallback(err, user);
|
mockVerifyCallback(err, user);
|
||||||
|
|
@ -344,7 +342,7 @@ describe('Apple Login Strategy', () => {
|
||||||
|
|
||||||
it('should handle errors during user creation', async () => {
|
it('should handle errors during user creation', async () => {
|
||||||
// Mock findUser to return null (user does not exist)
|
// Mock findUser to return null (user does not exist)
|
||||||
findUser.mockResolvedValue(null);
|
User.findUser.mockResolvedValue(null);
|
||||||
|
|
||||||
// Mock createSocialUser to throw an error
|
// Mock createSocialUser to throw an error
|
||||||
createSocialUser.mockImplementation(() => {
|
createSocialUser.mockImplementation(() => {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
const { SystemRoles } = require('librechat-data-provider');
|
const { SystemRoles } = require('librechat-data-provider');
|
||||||
const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt');
|
const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt');
|
||||||
const { getUserById, updateUser } = require('~/models');
|
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
// JWT strategy
|
// JWT strategy
|
||||||
const jwtLogin = () =>
|
const jwtLogin = () =>
|
||||||
|
|
@ -12,12 +12,13 @@ const jwtLogin = () =>
|
||||||
},
|
},
|
||||||
async (payload, done) => {
|
async (payload, done) => {
|
||||||
try {
|
try {
|
||||||
const user = await getUserById(payload?.id, '-password -__v -totpSecret');
|
const {User} = db.models;
|
||||||
|
const user = await User.getUserById(payload?.id, '-password -__v -totpSecret');
|
||||||
if (user) {
|
if (user) {
|
||||||
user.id = user._id.toString();
|
user.id = user._id.toString();
|
||||||
if (!user.role) {
|
if (!user.role) {
|
||||||
user.role = SystemRoles.USER;
|
user.role = SystemRoles.USER;
|
||||||
await updateUser(user.id, { role: user.role });
|
await User.updateUser(user.id, { role: user.role });
|
||||||
}
|
}
|
||||||
done(null, user);
|
done(null, user);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const LdapStrategy = require('passport-ldapauth');
|
const LdapStrategy = require('passport-ldapauth');
|
||||||
const { SystemRoles } = require('librechat-data-provider');
|
const { SystemRoles } = require('librechat-data-provider');
|
||||||
const { findUser, createUser, updateUser } = require('~/models/userMethods');
|
|
||||||
const { countUsers } = require('~/models/userMethods');
|
|
||||||
const { isEnabled } = require('~/server/utils');
|
const { isEnabled } = require('~/server/utils');
|
||||||
const logger = require('~/utils/logger');
|
const logger = require('~/utils/logger');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
const { getBalanceConfig } = require('~/server/services/Config');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
LDAP_URL,
|
LDAP_URL,
|
||||||
|
|
@ -81,6 +81,7 @@ const ldapOptions = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const ldapLogin = new LdapStrategy(ldapOptions, async (userinfo, done) => {
|
const ldapLogin = new LdapStrategy(ldapOptions, async (userinfo, done) => {
|
||||||
|
const { User } = db.models;
|
||||||
if (!userinfo) {
|
if (!userinfo) {
|
||||||
return done(null, false, { message: 'Invalid credentials' });
|
return done(null, false, { message: 'Invalid credentials' });
|
||||||
}
|
}
|
||||||
|
|
@ -89,7 +90,7 @@ const ldapLogin = new LdapStrategy(ldapOptions, async (userinfo, done) => {
|
||||||
const ldapId =
|
const ldapId =
|
||||||
(LDAP_ID && userinfo[LDAP_ID]) || userinfo.uid || userinfo.sAMAccountName || userinfo.mail;
|
(LDAP_ID && userinfo[LDAP_ID]) || userinfo.uid || userinfo.sAMAccountName || userinfo.mail;
|
||||||
|
|
||||||
let user = await findUser({ ldapId });
|
let user = await User.findUser({ ldapId });
|
||||||
|
|
||||||
const fullNameAttributes = LDAP_FULL_NAME && LDAP_FULL_NAME.split(',');
|
const fullNameAttributes = LDAP_FULL_NAME && LDAP_FULL_NAME.split(',');
|
||||||
const fullName =
|
const fullName =
|
||||||
|
|
@ -114,7 +115,7 @@ const ldapLogin = new LdapStrategy(ldapOptions, async (userinfo, done) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
const isFirstRegisteredUser = (await countUsers()) === 0;
|
const isFirstRegisteredUser = (await User.countUsers()) === 0;
|
||||||
user = {
|
user = {
|
||||||
provider: 'ldap',
|
provider: 'ldap',
|
||||||
ldapId,
|
ldapId,
|
||||||
|
|
@ -124,7 +125,9 @@ const ldapLogin = new LdapStrategy(ldapOptions, async (userinfo, done) => {
|
||||||
name: fullName,
|
name: fullName,
|
||||||
role: isFirstRegisteredUser ? SystemRoles.ADMIN : SystemRoles.USER,
|
role: isFirstRegisteredUser ? SystemRoles.ADMIN : SystemRoles.USER,
|
||||||
};
|
};
|
||||||
const userId = await createUser(user);
|
const balanceConfig = await getBalanceConfig();
|
||||||
|
|
||||||
|
const userId = await User.createUser(user, balanceConfig);
|
||||||
user._id = userId;
|
user._id = userId;
|
||||||
} else {
|
} else {
|
||||||
// Users registered in LDAP are assumed to have their user information managed in LDAP,
|
// Users registered in LDAP are assumed to have their user information managed in LDAP,
|
||||||
|
|
@ -136,7 +139,7 @@ const ldapLogin = new LdapStrategy(ldapOptions, async (userinfo, done) => {
|
||||||
user.name = fullName;
|
user.name = fullName;
|
||||||
}
|
}
|
||||||
|
|
||||||
user = await updateUser(user._id, user);
|
user = await User.updateUser(user._id, user);
|
||||||
done(null, user);
|
done(null, user);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('[ldapStrategy]', err);
|
logger.error('[ldapStrategy]', err);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
const { errorsToString } = require('librechat-data-provider');
|
const { errorsToString } = require('librechat-data-provider');
|
||||||
const { Strategy: PassportLocalStrategy } = require('passport-local');
|
const { Strategy: PassportLocalStrategy } = require('passport-local');
|
||||||
const { findUser, comparePassword, updateUser } = require('~/models');
|
const { comparePassword } = require('~/models');
|
||||||
const { isEnabled, checkEmailConfig } = require('~/server/utils');
|
const { isEnabled, checkEmailConfig } = require('~/server/utils');
|
||||||
const { loginSchema } = require('./validators');
|
const { loginSchema } = require('./validators');
|
||||||
const logger = require('~/utils/logger');
|
const logger = require('~/utils/logger');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
// Unix timestamp for 2024-06-07 15:20:18 Eastern Time
|
// Unix timestamp for 2024-06-07 15:20:18 Eastern Time
|
||||||
const verificationEnabledTimestamp = 1717788018;
|
const verificationEnabledTimestamp = 1717788018;
|
||||||
|
|
@ -14,6 +15,7 @@ async function validateLoginRequest(req) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function passportLogin(req, email, password, done) {
|
async function passportLogin(req, email, password, done) {
|
||||||
|
const {User} = db.models;
|
||||||
try {
|
try {
|
||||||
const validationError = await validateLoginRequest(req);
|
const validationError = await validateLoginRequest(req);
|
||||||
if (validationError) {
|
if (validationError) {
|
||||||
|
|
@ -22,7 +24,7 @@ async function passportLogin(req, email, password, done) {
|
||||||
return done(null, false, { message: validationError });
|
return done(null, false, { message: validationError });
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await findUser({ email: email.trim() });
|
const user = await User.findUser({ email: email.trim() });
|
||||||
if (!user) {
|
if (!user) {
|
||||||
logError('Passport Local Strategy - User Not Found', { email });
|
logError('Passport Local Strategy - User Not Found', { email });
|
||||||
logger.error(`[Login] [Login failed] [Username: ${email}] [Request-IP: ${req.ip}]`);
|
logger.error(`[Login] [Login failed] [Username: ${email}] [Request-IP: ${req.ip}]`);
|
||||||
|
|
@ -44,13 +46,13 @@ async function passportLogin(req, email, password, done) {
|
||||||
!user.emailVerified &&
|
!user.emailVerified &&
|
||||||
userCreatedAtTimestamp < verificationEnabledTimestamp
|
userCreatedAtTimestamp < verificationEnabledTimestamp
|
||||||
) {
|
) {
|
||||||
await updateUser(user._id, { emailVerified: true });
|
await User.updateUser(user._id, { emailVerified: true });
|
||||||
user.emailVerified = true;
|
user.emailVerified = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const unverifiedAllowed = isEnabled(process.env.ALLOW_UNVERIFIED_EMAIL_LOGIN);
|
const unverifiedAllowed = isEnabled(process.env.ALLOW_UNVERIFIED_EMAIL_LOGIN);
|
||||||
if (user.expiresAt && unverifiedAllowed) {
|
if (user.expiresAt && unverifiedAllowed) {
|
||||||
await updateUser(user._id, {});
|
await User.updateUser(user._id, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.emailVerified && !unverifiedAllowed) {
|
if (!user.emailVerified && !unverifiedAllowed) {
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,6 @@ const { HttpsProxyAgent } = require('https-proxy-agent');
|
||||||
const client = require('openid-client');
|
const client = require('openid-client');
|
||||||
const { Strategy: OpenIDStrategy } = require('openid-client/passport');
|
const { Strategy: OpenIDStrategy } = require('openid-client/passport');
|
||||||
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
||||||
const { findUser, createUser, updateUser } = require('~/models/userMethods');
|
|
||||||
const { hashToken } = require('~/server/utils/crypto');
|
|
||||||
const { isEnabled } = require('~/server/utils');
|
const { isEnabled } = require('~/server/utils');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
const getLogStores = require('~/cache/getLogStores');
|
const getLogStores = require('~/cache/getLogStores');
|
||||||
|
|
@ -37,6 +35,24 @@ class CustomOpenIDStrategy extends OpenIDStrategy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
const { getBalanceConfig } = require('~/server/services/Config');
|
||||||
|
|
||||||
|
let crypto;
|
||||||
|
let webcrypto;
|
||||||
|
try {
|
||||||
|
crypto = require('node:crypto');
|
||||||
|
webcrypto = crypto;
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('[openidStrategy] crypto support is disabled!', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function hashToken(str) {
|
||||||
|
const data = new TextEncoder().encode(str);
|
||||||
|
const hashBuffer = await webcrypto.subtle.digest('SHA-256', data);
|
||||||
|
return Buffer.from(hashBuffer).toString('hex');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exchange the access token for a new access token using the on-behalf-of flow if required.
|
* Exchange the access token for a new access token using the on-behalf-of flow if required.
|
||||||
* @param {Configuration} config
|
* @param {Configuration} config
|
||||||
|
|
@ -196,6 +212,7 @@ function convertToUsername(input, defaultValue = '') {
|
||||||
* @throws {Error} If an error occurs during the setup process.
|
* @throws {Error} If an error occurs during the setup process.
|
||||||
*/
|
*/
|
||||||
async function setupOpenId() {
|
async function setupOpenId() {
|
||||||
|
const { User } = db.models;
|
||||||
try {
|
try {
|
||||||
/** @type {ClientMetadata} */
|
/** @type {ClientMetadata} */
|
||||||
const clientMetadata = {
|
const clientMetadata = {
|
||||||
|
|
@ -230,13 +247,13 @@ async function setupOpenId() {
|
||||||
async (tokenset, done) => {
|
async (tokenset, done) => {
|
||||||
try {
|
try {
|
||||||
const claims = tokenset.claims();
|
const claims = tokenset.claims();
|
||||||
let user = await findUser({ openidId: claims.sub });
|
let user = await User.findUser({ openidId: claims.sub });
|
||||||
logger.info(
|
logger.info(
|
||||||
`[openidStrategy] user ${user ? 'found' : 'not found'} with openidId: ${claims.sub}`,
|
`[openidStrategy] user ${user ? 'found' : 'not found'} with openidId: ${claims.sub}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
user = await findUser({ email: claims.email });
|
user = await User.findUser({ email: claims.email });
|
||||||
logger.info(
|
logger.info(
|
||||||
`[openidStrategy] user ${user ? 'found' : 'not found'} with email: ${
|
`[openidStrategy] user ${user ? 'found' : 'not found'} with email: ${
|
||||||
claims.email
|
claims.email
|
||||||
|
|
@ -297,7 +314,10 @@ async function setupOpenId() {
|
||||||
emailVerified: userinfo.email_verified || false,
|
emailVerified: userinfo.email_verified || false,
|
||||||
name: fullName,
|
name: fullName,
|
||||||
};
|
};
|
||||||
user = await createUser(user, true, true);
|
|
||||||
|
const balanceConfig = await getBalanceConfig();
|
||||||
|
|
||||||
|
user = await User.createUser(user, balanceConfig, true, true);
|
||||||
} else {
|
} else {
|
||||||
user.provider = 'openid';
|
user.provider = 'openid';
|
||||||
user.openidId = userinfo.sub;
|
user.openidId = userinfo.sub;
|
||||||
|
|
@ -333,7 +353,7 @@ async function setupOpenId() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
user = await updateUser(user._id, user);
|
user = await User.updateUser(user._id, user);
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
`[openidStrategy] login success openidId: ${user.openidId} | email: ${user.email} | username: ${user.username} `,
|
`[openidStrategy] login success openidId: ${user.openidId} | email: ${user.email} | username: ${user.username} `,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,48 @@
|
||||||
const fetch = require('node-fetch');
|
const fetch = require('node-fetch');
|
||||||
const jwtDecode = require('jsonwebtoken/decode');
|
const jwtDecode = require('jsonwebtoken/decode');
|
||||||
const { findUser, createUser, updateUser } = require('~/models/userMethods');
|
|
||||||
const { setupOpenId } = require('./openidStrategy');
|
const { setupOpenId } = require('./openidStrategy');
|
||||||
|
const { getBalanceConfig } = require('~/server/services/Config');
|
||||||
|
|
||||||
// --- Mocks ---
|
// --- Mocks ---
|
||||||
|
const mockCreateUser = jest.fn();
|
||||||
|
const mockFindUser = jest.fn();
|
||||||
|
const mockUpdateUser = jest.fn();
|
||||||
|
let User;
|
||||||
|
|
||||||
|
jest.mock('@librechat/data-schemas', () => {
|
||||||
|
return {
|
||||||
|
registerModels: jest.fn().mockReturnValue({
|
||||||
|
User: {
|
||||||
|
createUser: mockCreateUser,
|
||||||
|
findUser: mockFindUser,
|
||||||
|
updateUser: mockUpdateUser,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockModels = {
|
||||||
|
User: {
|
||||||
|
createUser: mockCreateUser,
|
||||||
|
findUser: mockFindUser,
|
||||||
|
updateUser: mockUpdateUser,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.mock('~/lib/db/connectDb', () => {
|
||||||
|
return {
|
||||||
|
getModels: jest.fn(() => mockModels),
|
||||||
|
connectDb: jest.fn(),
|
||||||
|
get models() {
|
||||||
|
return mockModels;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('~/server/services/Config', () => ({
|
||||||
|
getBalanceConfig: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
jest.mock('node-fetch');
|
jest.mock('node-fetch');
|
||||||
jest.mock('jsonwebtoken/decode');
|
jest.mock('jsonwebtoken/decode');
|
||||||
jest.mock('~/server/services/Files/strategies', () => ({
|
jest.mock('~/server/services/Files/strategies', () => ({
|
||||||
|
|
@ -11,11 +50,7 @@ jest.mock('~/server/services/Files/strategies', () => ({
|
||||||
saveBuffer: jest.fn().mockResolvedValue('/fake/path/to/avatar.png'),
|
saveBuffer: jest.fn().mockResolvedValue('/fake/path/to/avatar.png'),
|
||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
jest.mock('~/models/userMethods', () => ({
|
|
||||||
findUser: jest.fn(),
|
|
||||||
createUser: jest.fn(),
|
|
||||||
updateUser: jest.fn(),
|
|
||||||
}));
|
|
||||||
jest.mock('~/server/utils/crypto', () => ({
|
jest.mock('~/server/utils/crypto', () => ({
|
||||||
hashToken: jest.fn().mockResolvedValue('hashed-token'),
|
hashToken: jest.fn().mockResolvedValue('hashed-token'),
|
||||||
}));
|
}));
|
||||||
|
|
@ -109,7 +144,8 @@ describe('setupOpenId', () => {
|
||||||
picture: 'https://example.com/avatar.png',
|
picture: 'https://example.com/avatar.png',
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
const { registerModels } = require('@librechat/data-schemas');
|
||||||
|
User = registerModels().User;
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
// Clear previous mock calls and reset implementations
|
// Clear previous mock calls and reset implementations
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|
@ -134,13 +170,16 @@ describe('setupOpenId', () => {
|
||||||
roles: ['requiredRole'],
|
roles: ['requiredRole'],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
User.findUser = jest.fn();
|
||||||
// By default, assume that no user is found, so createUser will be called
|
// By default, assume that no user is found, so createUser will be called
|
||||||
findUser.mockResolvedValue(null);
|
|
||||||
createUser.mockImplementation(async (userData) => {
|
const balance = await getBalanceConfig.mockResolvedValue({ enabled: false });
|
||||||
|
User.createUser.mockImplementation(async (userData, balance) => {
|
||||||
// simulate created user with an _id property
|
// simulate created user with an _id property
|
||||||
return { _id: 'newUserId', ...userData };
|
return { _id: 'newUserId', ...userData };
|
||||||
});
|
});
|
||||||
updateUser.mockImplementation(async (id, userData) => {
|
// User.updateUser = jest.fn();
|
||||||
|
User.updateUser.mockImplementation(async (id, userData) => {
|
||||||
return { _id: id, ...userData };
|
return { _id: id, ...userData };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -166,7 +205,7 @@ describe('setupOpenId', () => {
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(user.username).toBe(userinfo.username);
|
expect(user.username).toBe(userinfo.username);
|
||||||
expect(createUser).toHaveBeenCalledWith(
|
expect(User.createUser).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
provider: 'openid',
|
provider: 'openid',
|
||||||
openidId: userinfo.sub,
|
openidId: userinfo.sub,
|
||||||
|
|
@ -174,6 +213,7 @@ describe('setupOpenId', () => {
|
||||||
email: userinfo.email,
|
email: userinfo.email,
|
||||||
name: `${userinfo.given_name} ${userinfo.family_name}`,
|
name: `${userinfo.given_name} ${userinfo.family_name}`,
|
||||||
}),
|
}),
|
||||||
|
{ enabled: false },
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
@ -191,8 +231,9 @@ describe('setupOpenId', () => {
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(user.username).toBe(expectUsername);
|
expect(user.username).toBe(expectUsername);
|
||||||
expect(createUser).toHaveBeenCalledWith(
|
expect(User.createUser).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({ username: expectUsername }),
|
expect.objectContaining({ username: expectUsername }),
|
||||||
|
{ enabled: false },
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
@ -210,8 +251,9 @@ describe('setupOpenId', () => {
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(user.username).toBe(expectUsername);
|
expect(user.username).toBe(expectUsername);
|
||||||
expect(createUser).toHaveBeenCalledWith(
|
expect(User.createUser).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({ username: expectUsername }),
|
expect.objectContaining({ username: expectUsername }),
|
||||||
|
{ enabled: false },
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
@ -227,8 +269,9 @@ describe('setupOpenId', () => {
|
||||||
|
|
||||||
// Assert – username should equal the sub (converted as-is)
|
// Assert – username should equal the sub (converted as-is)
|
||||||
expect(user.username).toBe(userinfo.sub);
|
expect(user.username).toBe(userinfo.sub);
|
||||||
expect(createUser).toHaveBeenCalledWith(
|
expect(User.createUser).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({ username: userinfo.sub }),
|
expect.objectContaining({ username: userinfo.sub }),
|
||||||
|
{ enabled: false },
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
@ -268,11 +311,8 @@ describe('setupOpenId', () => {
|
||||||
username: '',
|
username: '',
|
||||||
name: '',
|
name: '',
|
||||||
};
|
};
|
||||||
findUser.mockImplementation(async (query) => {
|
mockFindUser.mockImplementation(async (query) => {
|
||||||
if (query.openidId === tokenset.claims().sub || query.email === tokenset.claims().email) {
|
return existingUser;
|
||||||
return existingUser;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const userinfo = tokenset.claims();
|
const userinfo = tokenset.claims();
|
||||||
|
|
@ -281,7 +321,7 @@ describe('setupOpenId', () => {
|
||||||
await validate(tokenset);
|
await validate(tokenset);
|
||||||
|
|
||||||
// Assert – updateUser should be called and the user object updated
|
// Assert – updateUser should be called and the user object updated
|
||||||
expect(updateUser).toHaveBeenCalledWith(
|
expect(User.updateUser).toHaveBeenCalledWith(
|
||||||
existingUser._id,
|
existingUser._id,
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
provider: 'openid',
|
provider: 'openid',
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
const { FileSources } = require('librechat-data-provider');
|
const { FileSources } = require('librechat-data-provider');
|
||||||
const { createUser, updateUser, getUserById } = require('~/models/userMethods');
|
|
||||||
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
||||||
const { resizeAvatar } = require('~/server/services/Files/images/avatar');
|
const { resizeAvatar } = require('~/server/services/Files/images/avatar');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
const { getBalanceConfig } = require('~/server/services/Config');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the avatar URL of an existing user. If the user's avatar URL does not include the query parameter
|
* Updates the avatar URL of an existing user. If the user's avatar URL does not include the query parameter
|
||||||
|
|
@ -34,7 +35,7 @@ const handleExistingUser = async (oldUser, avatarUrl) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updatedAvatar) {
|
if (updatedAvatar) {
|
||||||
await updateUser(oldUser._id, { avatar: updatedAvatar });
|
await db.models.User.updateUser(oldUser._id, { avatar: updatedAvatar });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -78,7 +79,8 @@ const createSocialUser = async ({
|
||||||
emailVerified,
|
emailVerified,
|
||||||
};
|
};
|
||||||
|
|
||||||
const newUserId = await createUser(update);
|
const balanceConfig = await getBalanceConfig();
|
||||||
|
const newUserId = await db.models.User.createUser(update, balanceConfig);
|
||||||
const fileStrategy = process.env.CDN_PROVIDER;
|
const fileStrategy = process.env.CDN_PROVIDER;
|
||||||
const isLocal = fileStrategy === FileSources.local;
|
const isLocal = fileStrategy === FileSources.local;
|
||||||
|
|
||||||
|
|
@ -89,10 +91,10 @@ const createSocialUser = async ({
|
||||||
});
|
});
|
||||||
const { processAvatar } = getStrategyFunctions(fileStrategy);
|
const { processAvatar } = getStrategyFunctions(fileStrategy);
|
||||||
const avatar = await processAvatar({ buffer: resizedBuffer, userId: newUserId });
|
const avatar = await processAvatar({ buffer: resizedBuffer, userId: newUserId });
|
||||||
await updateUser(newUserId, { avatar });
|
await User.updateUser(newUserId, { avatar });
|
||||||
}
|
}
|
||||||
|
|
||||||
return await getUserById(newUserId);
|
return await User.getUserById(newUserId);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
const { createSocialUser, handleExistingUser } = require('./process');
|
const { createSocialUser, handleExistingUser } = require('./process');
|
||||||
const { isEnabled } = require('~/server/utils');
|
const { isEnabled } = require('~/server/utils');
|
||||||
const { findUser } = require('~/models');
|
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
const socialLogin =
|
const socialLogin =
|
||||||
(provider, getProfileDetails) => async (accessToken, refreshToken, idToken, profile, cb) => {
|
(provider, getProfileDetails) => async (accessToken, refreshToken, idToken, profile, cb) => {
|
||||||
|
|
@ -11,7 +11,7 @@ const socialLogin =
|
||||||
profile,
|
profile,
|
||||||
});
|
});
|
||||||
|
|
||||||
const oldUser = await findUser({ email: email.trim() });
|
const oldUser = await db.models.User.findUser({ email: email.trim() });
|
||||||
const ALLOW_SOCIAL_REGISTRATION = isEnabled(process.env.ALLOW_SOCIAL_REGISTRATION);
|
const ALLOW_SOCIAL_REGISTRATION = isEnabled(process.env.ALLOW_SOCIAL_REGISTRATION);
|
||||||
|
|
||||||
if (oldUser) {
|
if (oldUser) {
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ const path = require('path');
|
||||||
require('module-alias')({ base: path.resolve(__dirname, '..', 'api') });
|
require('module-alias')({ base: path.resolve(__dirname, '..', 'api') });
|
||||||
const { askQuestion, silentExit } = require('./helpers');
|
const { askQuestion, silentExit } = require('./helpers');
|
||||||
const { isEnabled } = require('~/server/utils/handleText');
|
const { isEnabled } = require('~/server/utils/handleText');
|
||||||
const { Transaction } = require('~/models/Transaction');
|
const { createTransaction } = require('~/models/Transaction');
|
||||||
const User = require('~/models/User');
|
const db = require('~/lib/db/connectDb');
|
||||||
const connect = require('./connect');
|
const connect = require('./connect');
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|
@ -65,7 +65,7 @@ const connect = require('./connect');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the user
|
// Validate the user
|
||||||
const user = await User.findOne({ email }).lean();
|
const user = await db.models.User.findOne({ email }).lean();
|
||||||
if (!user) {
|
if (!user) {
|
||||||
console.red('Error: No user with that email was found!');
|
console.red('Error: No user with that email was found!');
|
||||||
silentExit(1);
|
silentExit(1);
|
||||||
|
|
@ -78,7 +78,7 @@ const connect = require('./connect');
|
||||||
*/
|
*/
|
||||||
let result;
|
let result;
|
||||||
try {
|
try {
|
||||||
result = await Transaction.create({
|
result = await createTransaction({
|
||||||
user: user._id,
|
user: user._id,
|
||||||
tokenType: 'credits',
|
tokenType: 'credits',
|
||||||
context: 'admin',
|
context: 'admin',
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ const path = require('path');
|
||||||
require('module-alias')({ base: path.resolve(__dirname, '..', 'api') });
|
require('module-alias')({ base: path.resolve(__dirname, '..', 'api') });
|
||||||
const { askQuestion, silentExit } = require('./helpers');
|
const { askQuestion, silentExit } = require('./helpers');
|
||||||
const banViolation = require('~/cache/banViolation');
|
const banViolation = require('~/cache/banViolation');
|
||||||
const User = require('~/models/User');
|
const db = require('~/lib/db/connectDb');
|
||||||
const connect = require('./connect');
|
const connect = require('./connect');
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|
@ -44,7 +44,7 @@ const connect = require('./connect');
|
||||||
silentExit(1);
|
silentExit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await User.findOne({ email }).lean();
|
const user = await db.models.User.findOne({ email }).lean();
|
||||||
if (!user) {
|
if (!user) {
|
||||||
console.red('Error: No user with that email was found!');
|
console.red('Error: No user with that email was found!');
|
||||||
silentExit(1);
|
silentExit(1);
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ const moduleAlias = require('module-alias');
|
||||||
const basePath = path.resolve(__dirname, '..', 'api');
|
const basePath = path.resolve(__dirname, '..', 'api');
|
||||||
moduleAlias.addAlias('~', basePath);
|
moduleAlias.addAlias('~', basePath);
|
||||||
|
|
||||||
const connectDb = require('~/lib/db/connectDb');
|
const {connectDb }= require('~/lib/db/connectDb');
|
||||||
require('./helpers');
|
require('./helpers');
|
||||||
|
|
||||||
async function connect() {
|
async function connect() {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ const path = require('path');
|
||||||
require('module-alias')({ base: path.resolve(__dirname, '..', 'api') });
|
require('module-alias')({ base: path.resolve(__dirname, '..', 'api') });
|
||||||
const { registerUser } = require('~/server/services/AuthService');
|
const { registerUser } = require('~/server/services/AuthService');
|
||||||
const { askQuestion, silentExit } = require('./helpers');
|
const { askQuestion, silentExit } = require('./helpers');
|
||||||
const User = require('~/models/User');
|
const db = require('~/lib/db/connectDb');
|
||||||
const connect = require('./connect');
|
const connect = require('./connect');
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|
@ -92,7 +92,7 @@ or the user will need to attempt logging in to have a verification link sent to
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const userExists = await User.findOne({ $or: [{ email }, { username }] });
|
const userExists = await db.models.User.findOne({ $or: [{ email }, { username }] });
|
||||||
if (userExists) {
|
if (userExists) {
|
||||||
console.red('Error: A user with that email or username already exists!');
|
console.red('Error: A user with that email or username already exists!');
|
||||||
silentExit(1);
|
silentExit(1);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
require('module-alias')({ base: path.resolve(__dirname, '..', 'api') });
|
require('module-alias')({ base: path.resolve(__dirname, '..', 'api') });
|
||||||
const { askQuestion, silentExit } = require('./helpers');
|
const { askQuestion, silentExit } = require('./helpers');
|
||||||
const User = require('~/models/User');
|
const db = require('~/lib/db/connectDb');
|
||||||
const connect = require('./connect');
|
const connect = require('./connect');
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|
@ -20,10 +20,10 @@ const connect = require('./connect');
|
||||||
} else {
|
} else {
|
||||||
email = await askQuestion('Email:');
|
email = await askQuestion('Email:');
|
||||||
}
|
}
|
||||||
let user = await User.findOne({ email: email });
|
let user = await db.models.User.findOne({ email: email });
|
||||||
if (user !== null) {
|
if (user !== null) {
|
||||||
if ((await askQuestion(`Delete user ${user}?`)) === 'y') {
|
if ((await askQuestion(`Delete user ${user}?`)) === 'y') {
|
||||||
user = await User.findOneAndDelete({ _id: user._id });
|
user = await db.models.User.findOneAndDelete({ _id: user._id });
|
||||||
if (user !== null) {
|
if (user !== null) {
|
||||||
console.yellow(`Deleted user ${user}`);
|
console.yellow(`Deleted user ${user}`);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ require('module-alias')({ base: path.resolve(__dirname, '..', 'api') });
|
||||||
const { sendEmail, checkEmailConfig } = require('~/server/utils');
|
const { sendEmail, checkEmailConfig } = require('~/server/utils');
|
||||||
const { askQuestion, silentExit } = require('./helpers');
|
const { askQuestion, silentExit } = require('./helpers');
|
||||||
const { createInvite } = require('~/models/inviteUser');
|
const { createInvite } = require('~/models/inviteUser');
|
||||||
const User = require('~/models/User');
|
const db = require('~/lib/db/connectDb');
|
||||||
const connect = require('./connect');
|
const connect = require('./connect');
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|
@ -40,7 +40,7 @@ const connect = require('./connect');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the user already exists
|
// Check if the user already exists
|
||||||
const userExists = await User.findOne({ email });
|
const userExists = await db.models.User.findOne({ email });
|
||||||
if (userExists) {
|
if (userExists) {
|
||||||
console.red('Error: A user with that email already exists!');
|
console.red('Error: A user with that email already exists!');
|
||||||
silentExit(1);
|
silentExit(1);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ const path = require('path');
|
||||||
require('module-alias')({ base: path.resolve(__dirname, '..', 'api') });
|
require('module-alias')({ base: path.resolve(__dirname, '..', 'api') });
|
||||||
const { silentExit } = require('./helpers');
|
const { silentExit } = require('./helpers');
|
||||||
const Balance = require('~/models/Balance');
|
const Balance = require('~/models/Balance');
|
||||||
const User = require('~/models/User');
|
const db = require('~/lib/db/connectDb');
|
||||||
const connect = require('./connect');
|
const connect = require('./connect');
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|
@ -15,7 +15,7 @@ const connect = require('./connect');
|
||||||
console.purple('Show the balance of all users');
|
console.purple('Show the balance of all users');
|
||||||
console.purple('-----------------------------');
|
console.purple('-----------------------------');
|
||||||
|
|
||||||
let users = await User.find({});
|
let users = await db.models.User.find({});
|
||||||
for (const user of users) {
|
for (const user of users) {
|
||||||
let balance = await Balance.findOne({ user: user._id });
|
let balance = await Balance.findOne({ user: user._id });
|
||||||
if (balance !== null) {
|
if (balance !== null) {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
require('module-alias')({ base: path.resolve(__dirname, '..', 'api') });
|
require('module-alias')({ base: path.resolve(__dirname, '..', 'api') });
|
||||||
const User = require('../api/models/User');
|
|
||||||
const connect = require('./connect');
|
const connect = require('./connect');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
const listUsers = async () => {
|
const listUsers = async () => {
|
||||||
try {
|
try {
|
||||||
await connect();
|
await connect();
|
||||||
const users = await User.find({}, 'email provider avatar username name createdAt');
|
const users = await db.models.User.find({}, 'email provider avatar username name createdAt');
|
||||||
|
|
||||||
console.log('\nUser List:');
|
console.log('\nUser List:');
|
||||||
console.log('----------------------------------------');
|
console.log('----------------------------------------');
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
require('module-alias')({ base: path.resolve(__dirname, '..', 'api') });
|
require('module-alias')({ base: path.resolve(__dirname, '..', 'api') });
|
||||||
const User = require('~/models/User');
|
const db = require('~/lib/db/connectDb');
|
||||||
const connect = require('./connect');
|
const connect = require('./connect');
|
||||||
const { askQuestion, silentExit } = require('./helpers');
|
const { askQuestion, silentExit } = require('./helpers');
|
||||||
|
|
||||||
|
|
@ -20,7 +20,7 @@ const { askQuestion, silentExit } = require('./helpers');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await User.updateMany({}, { $set: { termsAccepted: false } });
|
const result = await db.models.User.updateMany({}, { $set: { termsAccepted: false } });
|
||||||
console.green(`Updated ${result.modifiedCount} user(s).`);
|
console.green(`Updated ${result.modifiedCount} user(s).`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.red('Error resetting terms acceptance:', error);
|
console.red('Error resetting terms acceptance:', error);
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,8 @@ const path = require('path');
|
||||||
require('module-alias')({ base: path.resolve(__dirname, '..', 'api') });
|
require('module-alias')({ base: path.resolve(__dirname, '..', 'api') });
|
||||||
const { askQuestion, silentExit } = require('./helpers');
|
const { askQuestion, silentExit } = require('./helpers');
|
||||||
const { isEnabled } = require('~/server/utils/handleText');
|
const { isEnabled } = require('~/server/utils/handleText');
|
||||||
const User = require('~/models/User');
|
const db = require('~/lib/db/connectDb');
|
||||||
const connect = require('./connect');
|
const connect = require('./connect');
|
||||||
const Balance = require('~/models/Balance');
|
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
await connect();
|
await connect();
|
||||||
|
|
@ -57,7 +56,7 @@ const Balance = require('~/models/Balance');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the user
|
// Validate the user
|
||||||
const user = await User.findOne({ email }).lean();
|
const user = await db.models.User.findOne({ email }).lean();
|
||||||
if (!user) {
|
if (!user) {
|
||||||
console.red('Error: No user with that email was found!');
|
console.red('Error: No user with that email was found!');
|
||||||
silentExit(1);
|
silentExit(1);
|
||||||
|
|
@ -65,7 +64,7 @@ const Balance = require('~/models/Balance');
|
||||||
console.purple(`Found user: ${user.email}`);
|
console.purple(`Found user: ${user.email}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let balance = await Balance.findOne({ user: user._id }).lean();
|
let balance = await db.models.Balance.findOne({ user: user._id }).lean();
|
||||||
if (!balance) {
|
if (!balance) {
|
||||||
console.purple('User has no balance!');
|
console.purple('User has no balance!');
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -86,7 +85,7 @@ const Balance = require('~/models/Balance');
|
||||||
*/
|
*/
|
||||||
let result;
|
let result;
|
||||||
try {
|
try {
|
||||||
result = await Balance.findOneAndUpdate(
|
result = await db.models.Balance.findOneAndUpdate(
|
||||||
{ user: user._id },
|
{ user: user._id },
|
||||||
{ tokenCredits: amount },
|
{ tokenCredits: amount },
|
||||||
{ upsert: true, new: true },
|
{ upsert: true, new: true },
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ const path = require('path');
|
||||||
const { v5: uuidv5 } = require('uuid');
|
const { v5: uuidv5 } = require('uuid');
|
||||||
require('module-alias')({ base: path.resolve(__dirname, '..', 'api') });
|
require('module-alias')({ base: path.resolve(__dirname, '..', 'api') });
|
||||||
const { askQuestion, askMultiLineQuestion, silentExit } = require('./helpers');
|
const { askQuestion, askMultiLineQuestion, silentExit } = require('./helpers');
|
||||||
const { Banner } = require('~/models/Banner');
|
|
||||||
const connect = require('./connect');
|
const connect = require('./connect');
|
||||||
|
const db = require('~/lib/db/connectDb');
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
await connect();
|
await connect();
|
||||||
|
|
@ -87,6 +87,7 @@ const connect = require('./connect');
|
||||||
|
|
||||||
let result;
|
let result;
|
||||||
try {
|
try {
|
||||||
|
const { Banner } = db.models;
|
||||||
// There is always only one Banner record in the DB.
|
// There is always only one Banner record in the DB.
|
||||||
// If a Banner exists in the DB, it will be updated.
|
// If a Banner exists in the DB, it will be updated.
|
||||||
// If it doesn't exist, a new one will be added.
|
// If it doesn't exist, a new one will be added.
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
require('module-alias')({ base: path.resolve(__dirname, '..', 'api') });
|
require('module-alias')({ base: path.resolve(__dirname, '..', 'api') });
|
||||||
const { silentExit } = require('./helpers');
|
const { silentExit } = require('./helpers');
|
||||||
const Conversation = require('~/models/schema/convoSchema');
|
const db = require('~/lib/db/connectDb');
|
||||||
const Message = require('~/models/schema/messageSchema');
|
|
||||||
const User = require('~/models/User');
|
|
||||||
const connect = require('./connect');
|
const connect = require('./connect');
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|
@ -16,6 +14,7 @@ const connect = require('./connect');
|
||||||
console.purple('Show the stats of all users');
|
console.purple('Show the stats of all users');
|
||||||
console.purple('-----------------------------');
|
console.purple('-----------------------------');
|
||||||
|
|
||||||
|
const { User, Conversation, Message } = db.models;
|
||||||
let users = await User.find({});
|
let users = await User.find({});
|
||||||
let userData = [];
|
let userData = [];
|
||||||
for (const user of users) {
|
for (const user of users) {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,5 @@
|
||||||
import connectDb from '@librechat/backend/lib/db/connectDb';
|
import { connectDb, getModels } from '@librechat/backend/lib/db/connectDb';
|
||||||
import {
|
import { deleteMessages, deleteConvos, User, Balance } from '@librechat/backend/models';
|
||||||
deleteMessages,
|
|
||||||
deleteConvos,
|
|
||||||
User,
|
|
||||||
deleteAllUserSessions,
|
|
||||||
Balance,
|
|
||||||
} from '@librechat/backend/models';
|
|
||||||
import { Transaction } from '@librechat/backend/models/Transaction';
|
import { Transaction } from '@librechat/backend/models/Transaction';
|
||||||
type TUser = { email: string; password: string };
|
type TUser = { email: string; password: string };
|
||||||
|
|
||||||
|
|
@ -33,7 +27,8 @@ export default async function cleanupUser(user: TUser) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: fix this to delete all user sessions with the user's email
|
// TODO: fix this to delete all user sessions with the user's email
|
||||||
await deleteAllUserSessions(user);
|
const { User, Session } = getModels();
|
||||||
|
await Session.deleteAllUserSessions(user);
|
||||||
|
|
||||||
await User.deleteMany({ _id: user });
|
await User.deleteMany({ _id: user });
|
||||||
await Balance.deleteMany({ user });
|
await Balance.deleteMany({ user });
|
||||||
|
|
|
||||||
355
package-lock.json
generated
355
package-lock.json
generated
|
|
@ -99,7 +99,7 @@
|
||||||
"librechat-data-provider": "*",
|
"librechat-data-provider": "*",
|
||||||
"librechat-mcp": "*",
|
"librechat-mcp": "*",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"meilisearch": "^0.38.0",
|
"meilisearch": "^0.50.0",
|
||||||
"memorystore": "^1.6.7",
|
"memorystore": "^1.6.7",
|
||||||
"mime": "^3.0.0",
|
"mime": "^3.0.0",
|
||||||
"module-alias": "^2.2.3",
|
"module-alias": "^2.2.3",
|
||||||
|
|
@ -2123,14 +2123,6 @@
|
||||||
"undici-types": "~5.26.4"
|
"undici-types": "~5.26.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"api/node_modules/@types/whatwg-url": {
|
|
||||||
"version": "11.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
|
|
||||||
"integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/webidl-conversions": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"api/node_modules/agent-base": {
|
"api/node_modules/agent-base": {
|
||||||
"version": "7.1.3",
|
"version": "7.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
|
||||||
|
|
@ -2140,14 +2132,6 @@
|
||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"api/node_modules/bson": {
|
|
||||||
"version": "6.10.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/bson/-/bson-6.10.3.tgz",
|
|
||||||
"integrity": "sha512-MTxGsqgYTwfshYWTRdmZRC+M7FnG1b4y7RO7p2k3X24Wq0yv1m77Wsj0BzlPzd/IowgESfsruQCUToa7vbOpPQ==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16.20.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"api/node_modules/cookie-parser": {
|
"api/node_modules/cookie-parser": {
|
||||||
"version": "1.4.7",
|
"version": "1.4.7",
|
||||||
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz",
|
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz",
|
||||||
|
|
@ -25022,6 +25006,12 @@
|
||||||
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
|
||||||
"integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="
|
"integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/traverse": {
|
||||||
|
"version": "0.6.37",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/traverse/-/traverse-0.6.37.tgz",
|
||||||
|
"integrity": "sha512-c90MVeDiUI1FhOZ6rLQ3kDWr50YE8+paDpM+5zbHjbmsqEp2DlMYkqnZnwbK9oI+NvDe8yRajup4jFwnVX6xsA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/triple-beam": {
|
"node_modules/@types/triple-beam": {
|
||||||
"version": "1.3.5",
|
"version": "1.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
|
||||||
|
|
@ -25048,6 +25038,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
|
||||||
"integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="
|
"integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/whatwg-url": {
|
||||||
|
"version": "11.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
|
||||||
|
"integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/webidl-conversions": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/winston": {
|
"node_modules/@types/winston": {
|
||||||
"version": "2.4.4",
|
"version": "2.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/winston/-/winston-2.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/winston/-/winston-2.4.4.tgz",
|
||||||
|
|
@ -25774,7 +25772,6 @@
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz",
|
||||||
"integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==",
|
"integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bound": "^1.0.3",
|
"call-bound": "^1.0.3",
|
||||||
|
|
@ -25922,7 +25919,6 @@
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz",
|
||||||
"integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==",
|
"integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"array-buffer-byte-length": "^1.0.1",
|
"array-buffer-byte-length": "^1.0.1",
|
||||||
|
|
@ -26008,7 +26004,6 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz",
|
||||||
"integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==",
|
"integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
|
|
@ -26078,7 +26073,6 @@
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
||||||
"integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
|
"integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"possible-typed-array-names": "^1.0.0"
|
"possible-typed-array-names": "^1.0.0"
|
||||||
},
|
},
|
||||||
|
|
@ -26632,6 +26626,14 @@
|
||||||
"node-int64": "^0.4.0"
|
"node-int64": "^0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bson": {
|
||||||
|
"version": "6.10.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/bson/-/bson-6.10.3.tgz",
|
||||||
|
"integrity": "sha512-MTxGsqgYTwfshYWTRdmZRC+M7FnG1b4y7RO7p2k3X24Wq0yv1m77Wsj0BzlPzd/IowgESfsruQCUToa7vbOpPQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.20.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/buffer": {
|
"node_modules/buffer": {
|
||||||
"version": "6.0.3",
|
"version": "6.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||||
|
|
@ -26737,7 +26739,6 @@
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
|
||||||
"integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
|
"integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind-apply-helpers": "^1.0.0",
|
"call-bind-apply-helpers": "^1.0.0",
|
||||||
|
|
@ -27945,7 +27946,6 @@
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
|
||||||
"integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==",
|
"integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bound": "^1.0.3",
|
"call-bound": "^1.0.3",
|
||||||
|
|
@ -27963,7 +27963,6 @@
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz",
|
||||||
"integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==",
|
"integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bound": "^1.0.3",
|
"call-bound": "^1.0.3",
|
||||||
|
|
@ -27981,7 +27980,6 @@
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz",
|
||||||
"integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==",
|
"integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bound": "^1.0.2",
|
"call-bound": "^1.0.2",
|
||||||
|
|
@ -28150,7 +28148,6 @@
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||||
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-define-property": "^1.0.0",
|
"es-define-property": "^1.0.0",
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
|
|
@ -28179,7 +28176,6 @@
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
|
||||||
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
|
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"define-data-property": "^1.0.1",
|
"define-data-property": "^1.0.1",
|
||||||
"has-property-descriptors": "^1.0.0",
|
"has-property-descriptors": "^1.0.0",
|
||||||
|
|
@ -28703,7 +28699,6 @@
|
||||||
"version": "1.23.9",
|
"version": "1.23.9",
|
||||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz",
|
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz",
|
||||||
"integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==",
|
"integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"array-buffer-byte-length": "^1.0.2",
|
"array-buffer-byte-length": "^1.0.2",
|
||||||
|
|
@ -28858,7 +28853,6 @@
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
|
|
@ -28883,7 +28877,6 @@
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz",
|
||||||
"integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==",
|
"integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-callable": "^1.2.7",
|
"is-callable": "^1.2.7",
|
||||||
|
|
@ -30410,7 +30403,6 @@
|
||||||
"version": "0.3.3",
|
"version": "0.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
|
||||||
"integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
|
"integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-callable": "^1.1.3"
|
"is-callable": "^1.1.3"
|
||||||
}
|
}
|
||||||
|
|
@ -30600,7 +30592,6 @@
|
||||||
"version": "1.1.8",
|
"version": "1.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz",
|
||||||
"integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==",
|
"integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.8",
|
"call-bind": "^1.0.8",
|
||||||
|
|
@ -30621,7 +30612,6 @@
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
|
||||||
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
|
"integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
|
||||||
"dev": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
|
|
@ -30806,7 +30796,6 @@
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz",
|
||||||
"integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==",
|
"integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bound": "^1.0.3",
|
"call-bound": "^1.0.3",
|
||||||
|
|
@ -30908,7 +30897,6 @@
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
|
||||||
"integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
|
"integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"define-properties": "^1.2.1",
|
"define-properties": "^1.2.1",
|
||||||
|
|
@ -31131,7 +31119,6 @@
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
|
||||||
"integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
|
"integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
|
||||||
"dev": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
|
|
@ -31148,7 +31135,6 @@
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||||
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-define-property": "^1.0.0"
|
"es-define-property": "^1.0.0"
|
||||||
},
|
},
|
||||||
|
|
@ -31160,7 +31146,6 @@
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz",
|
||||||
"integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==",
|
"integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dunder-proto": "^1.0.0"
|
"dunder-proto": "^1.0.0"
|
||||||
|
|
@ -31188,7 +31173,6 @@
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-symbols": "^1.0.3"
|
"has-symbols": "^1.0.3"
|
||||||
},
|
},
|
||||||
|
|
@ -31943,7 +31927,6 @@
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
|
||||||
"integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==",
|
"integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
|
|
@ -32061,7 +32044,6 @@
|
||||||
"version": "3.0.5",
|
"version": "3.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
|
||||||
"integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==",
|
"integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.8",
|
"call-bind": "^1.0.8",
|
||||||
|
|
@ -32085,7 +32067,6 @@
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz",
|
||||||
"integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==",
|
"integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async-function": "^1.0.0",
|
"async-function": "^1.0.0",
|
||||||
|
|
@ -32105,7 +32086,6 @@
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz",
|
||||||
"integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==",
|
"integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-bigints": "^1.0.2"
|
"has-bigints": "^1.0.2"
|
||||||
|
|
@ -32132,7 +32112,6 @@
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz",
|
||||||
"integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==",
|
"integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bound": "^1.0.3",
|
"call-bound": "^1.0.3",
|
||||||
|
|
@ -32174,7 +32153,6 @@
|
||||||
"version": "1.2.7",
|
"version": "1.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
|
||||||
"integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
|
"integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
},
|
},
|
||||||
|
|
@ -32201,7 +32179,6 @@
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz",
|
||||||
"integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==",
|
"integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bound": "^1.0.2",
|
"call-bound": "^1.0.2",
|
||||||
|
|
@ -32219,7 +32196,6 @@
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz",
|
||||||
"integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==",
|
"integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bound": "^1.0.2",
|
"call-bound": "^1.0.2",
|
||||||
|
|
@ -32268,7 +32244,6 @@
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz",
|
||||||
"integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==",
|
"integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bound": "^1.0.3"
|
"call-bound": "^1.0.3"
|
||||||
|
|
@ -32306,7 +32281,6 @@
|
||||||
"version": "1.0.10",
|
"version": "1.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
|
||||||
"integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==",
|
"integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-tostringtag": "^1.0.0"
|
"has-tostringtag": "^1.0.0"
|
||||||
},
|
},
|
||||||
|
|
@ -32359,7 +32333,6 @@
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
|
||||||
"integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
|
"integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
|
|
@ -32402,7 +32375,6 @@
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz",
|
||||||
"integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==",
|
"integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bound": "^1.0.3",
|
"call-bound": "^1.0.3",
|
||||||
|
|
@ -32468,7 +32440,6 @@
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
|
||||||
"integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
|
"integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bound": "^1.0.2",
|
"call-bound": "^1.0.2",
|
||||||
|
|
@ -32496,7 +32467,6 @@
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
|
||||||
"integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
|
"integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
|
|
@ -32509,7 +32479,6 @@
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz",
|
||||||
"integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==",
|
"integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bound": "^1.0.3"
|
"call-bound": "^1.0.3"
|
||||||
|
|
@ -32536,7 +32505,6 @@
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz",
|
||||||
"integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==",
|
"integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bound": "^1.0.3",
|
"call-bound": "^1.0.3",
|
||||||
|
|
@ -32553,7 +32521,6 @@
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz",
|
||||||
"integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==",
|
"integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bound": "^1.0.2",
|
"call-bound": "^1.0.2",
|
||||||
|
|
@ -32571,7 +32538,6 @@
|
||||||
"version": "1.1.15",
|
"version": "1.1.15",
|
||||||
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
|
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
|
||||||
"integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
|
"integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"which-typed-array": "^1.1.16"
|
"which-typed-array": "^1.1.16"
|
||||||
|
|
@ -32587,7 +32553,6 @@
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
|
||||||
"integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
|
"integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
|
|
@ -32600,7 +32565,6 @@
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz",
|
||||||
"integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==",
|
"integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bound": "^1.0.3"
|
"call-bound": "^1.0.3"
|
||||||
|
|
@ -32616,7 +32580,6 @@
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz",
|
||||||
"integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==",
|
"integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bound": "^1.0.3",
|
"call-bound": "^1.0.3",
|
||||||
|
|
@ -35595,12 +35558,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/meilisearch": {
|
"node_modules/meilisearch": {
|
||||||
"version": "0.38.0",
|
"version": "0.50.0",
|
||||||
"resolved": "https://registry.npmjs.org/meilisearch/-/meilisearch-0.38.0.tgz",
|
"resolved": "https://registry.npmjs.org/meilisearch/-/meilisearch-0.50.0.tgz",
|
||||||
"integrity": "sha512-bHaq8nYxSKw9/Qslq1Zes5g9tHgFkxy/I9o8942wv2PqlNOT0CzptIkh/x98N52GikoSZOXSQkgt6oMjtf5uZw==",
|
"integrity": "sha512-9IzIkobvnuS18Eg4dq/eJB9W+eXqeLZjNRgq/kKMswSmVYYSQsXqGgSuCA0JkF+o5RwJlwIsieQee6rh313VhA==",
|
||||||
"dependencies": {
|
"license": "MIT"
|
||||||
"cross-fetch": "^3.1.6"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"node_modules/memory-pager": {
|
"node_modules/memory-pager": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
|
|
@ -36395,6 +36356,38 @@
|
||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mongodb-connection-string-url": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/whatwg-url": "^11.0.2",
|
||||||
|
"whatwg-url": "^14.1.0 || ^13.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mongodb-connection-string-url/node_modules/tr46": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
|
||||||
|
"dependencies": {
|
||||||
|
"punycode": "^2.3.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mongodb-connection-string-url/node_modules/whatwg-url": {
|
||||||
|
"version": "14.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
|
||||||
|
"integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
|
||||||
|
"dependencies": {
|
||||||
|
"tr46": "^5.1.0",
|
||||||
|
"webidl-conversions": "^7.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/moo-color": {
|
"node_modules/moo-color": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/moo-color/-/moo-color-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/moo-color/-/moo-color-1.0.3.tgz",
|
||||||
|
|
@ -36915,7 +36908,6 @@
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
|
||||||
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
|
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
|
|
@ -36924,7 +36916,6 @@
|
||||||
"version": "4.1.7",
|
"version": "4.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz",
|
||||||
"integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==",
|
"integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.8",
|
"call-bind": "^1.0.8",
|
||||||
|
|
@ -37167,7 +37158,6 @@
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz",
|
||||||
"integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==",
|
"integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"get-intrinsic": "^1.2.6",
|
"get-intrinsic": "^1.2.6",
|
||||||
|
|
@ -37807,7 +37797,6 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
|
||||||
"integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==",
|
"integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
|
|
@ -40062,7 +40051,6 @@
|
||||||
"version": "1.0.10",
|
"version": "1.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
|
||||||
"integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==",
|
"integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.8",
|
"call-bind": "^1.0.8",
|
||||||
|
|
@ -40119,7 +40107,6 @@
|
||||||
"version": "1.5.4",
|
"version": "1.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
|
||||||
"integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
|
"integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.8",
|
"call-bind": "^1.0.8",
|
||||||
|
|
@ -41193,7 +41180,6 @@
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz",
|
||||||
"integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==",
|
"integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.8",
|
"call-bind": "^1.0.8",
|
||||||
|
|
@ -41213,7 +41199,6 @@
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
|
||||||
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
|
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/safe-buffer": {
|
"node_modules/safe-buffer": {
|
||||||
|
|
@ -41239,7 +41224,6 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
|
||||||
"integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==",
|
"integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
|
|
@ -41256,14 +41240,12 @@
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
|
||||||
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
|
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/safe-regex-test": {
|
"node_modules/safe-regex-test": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
|
||||||
"integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
|
"integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bound": "^1.0.2",
|
"call-bound": "^1.0.2",
|
||||||
|
|
@ -41427,7 +41409,6 @@
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||||
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
|
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"define-data-property": "^1.1.4",
|
"define-data-property": "^1.1.4",
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
|
|
@ -41444,7 +41425,6 @@
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
|
||||||
"integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
|
"integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"define-data-property": "^1.1.4",
|
"define-data-property": "^1.1.4",
|
||||||
|
|
@ -41460,7 +41440,6 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz",
|
||||||
"integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==",
|
"integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dunder-proto": "^1.0.1",
|
"dunder-proto": "^1.0.1",
|
||||||
|
|
@ -42130,7 +42109,6 @@
|
||||||
"version": "1.2.10",
|
"version": "1.2.10",
|
||||||
"resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz",
|
"resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz",
|
||||||
"integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==",
|
"integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.8",
|
"call-bind": "^1.0.8",
|
||||||
|
|
@ -42152,7 +42130,6 @@
|
||||||
"version": "1.0.9",
|
"version": "1.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz",
|
||||||
"integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==",
|
"integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.8",
|
"call-bind": "^1.0.8",
|
||||||
|
|
@ -42171,7 +42148,6 @@
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz",
|
||||||
"integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==",
|
"integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.7",
|
"call-bind": "^1.0.7",
|
||||||
"define-properties": "^1.2.1",
|
"define-properties": "^1.2.1",
|
||||||
|
|
@ -43172,7 +43148,6 @@
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
|
||||||
"integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==",
|
"integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bound": "^1.0.3",
|
"call-bound": "^1.0.3",
|
||||||
|
|
@ -43187,7 +43162,6 @@
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz",
|
||||||
"integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==",
|
"integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.8",
|
"call-bind": "^1.0.8",
|
||||||
|
|
@ -43207,7 +43181,6 @@
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz",
|
||||||
"integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==",
|
"integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"available-typed-arrays": "^1.0.7",
|
"available-typed-arrays": "^1.0.7",
|
||||||
|
|
@ -43229,7 +43202,6 @@
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz",
|
||||||
"integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==",
|
"integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind": "^1.0.7",
|
"call-bind": "^1.0.7",
|
||||||
|
|
@ -43251,6 +43223,27 @@
|
||||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||||
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
|
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/typedarray.prototype.slice": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/typedarray.prototype.slice/-/typedarray.prototype.slice-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-q7QNVDGTdl702bVFiI5eY4l/HkgCM6at9KhcFbgUAzezHFbOVy4+0O/lCjsABEQwbZPravVfBIiBVGo89yzHFg==",
|
||||||
|
"dependencies": {
|
||||||
|
"call-bind": "^1.0.8",
|
||||||
|
"define-properties": "^1.2.1",
|
||||||
|
"es-abstract": "^1.23.9",
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"get-proto": "^1.0.1",
|
||||||
|
"math-intrinsics": "^1.1.0",
|
||||||
|
"typed-array-buffer": "^1.0.3",
|
||||||
|
"typed-array-byte-offset": "^1.0.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.3.3",
|
"version": "5.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
|
||||||
|
|
@ -43341,7 +43334,6 @@
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
|
||||||
"integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==",
|
"integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bound": "^1.0.3",
|
"call-bound": "^1.0.3",
|
||||||
|
|
@ -44435,7 +44427,6 @@
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz",
|
||||||
"integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==",
|
"integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-bigint": "^1.1.0",
|
"is-bigint": "^1.1.0",
|
||||||
|
|
@ -44455,7 +44446,6 @@
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz",
|
||||||
"integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==",
|
"integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bound": "^1.0.2",
|
"call-bound": "^1.0.2",
|
||||||
|
|
@ -44483,14 +44473,12 @@
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
|
||||||
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
|
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/which-collection": {
|
"node_modules/which-collection": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz",
|
||||||
"integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==",
|
"integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-map": "^2.0.3",
|
"is-map": "^2.0.3",
|
||||||
|
|
@ -44509,7 +44497,6 @@
|
||||||
"version": "1.1.18",
|
"version": "1.1.18",
|
||||||
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz",
|
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz",
|
||||||
"integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==",
|
"integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"available-typed-arrays": "^1.0.7",
|
"available-typed-arrays": "^1.0.7",
|
||||||
|
|
@ -45431,6 +45418,46 @@
|
||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"packages/auth": {
|
||||||
|
"name": "@librechat/auth",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"extraneous": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"https-proxy-agent": "^7.0.6",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"mongoose": "^8.12.1",
|
||||||
|
"openid-client": "^6.5.0",
|
||||||
|
"passport": "^0.7.0",
|
||||||
|
"passport-facebook": "^3.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@librechat/data-schemas": "^0.0.7",
|
||||||
|
"@rollup/plugin-alias": "^5.1.0",
|
||||||
|
"@rollup/plugin-commonjs": "^25.0.2",
|
||||||
|
"@rollup/plugin-json": "^6.1.0",
|
||||||
|
"@rollup/plugin-node-resolve": "^15.1.0",
|
||||||
|
"@rollup/plugin-replace": "^5.0.5",
|
||||||
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
|
"@rollup/plugin-typescript": "^12.1.2",
|
||||||
|
"@types/diff": "^6.0.0",
|
||||||
|
"@types/express": "^5.0.0",
|
||||||
|
"@types/jest": "^29.5.2",
|
||||||
|
"@types/node": "^20.3.0",
|
||||||
|
"jest": "^29.5.0",
|
||||||
|
"jest-junit": "^16.0.0",
|
||||||
|
"rimraf": "^5.0.1",
|
||||||
|
"rollup": "^4.22.4",
|
||||||
|
"rollup-plugin-generate-package-json": "^3.2.0",
|
||||||
|
"rollup-plugin-peer-deps-external": "^2.2.4",
|
||||||
|
"rollup-plugin-typescript2": "^0.35.0",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
|
"typescript": "^5.0.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"keyv": "^5.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"packages/data-provider": {
|
"packages/data-provider": {
|
||||||
"name": "librechat-data-provider",
|
"name": "librechat-data-provider",
|
||||||
"version": "0.7.86",
|
"version": "0.7.86",
|
||||||
|
|
@ -45576,7 +45603,14 @@
|
||||||
"version": "0.0.7",
|
"version": "0.0.7",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mongoose": "^8.12.1"
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"klona": "^2.0.6",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"meilisearch": "^0.50.0",
|
||||||
|
"mongoose": "^8.12.1",
|
||||||
|
"traverse": "^0.6.11",
|
||||||
|
"winston": "^3.17.0",
|
||||||
|
"winston-daily-rotate-file": "^5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-alias": "^5.1.0",
|
"@rollup/plugin-alias": "^5.1.0",
|
||||||
|
|
@ -45590,6 +45624,7 @@
|
||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
"@types/jest": "^29.5.2",
|
"@types/jest": "^29.5.2",
|
||||||
"@types/node": "^20.3.0",
|
"@types/node": "^20.3.0",
|
||||||
|
"@types/traverse": "^0.6.37",
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"jest-junit": "^16.0.0",
|
"jest-junit": "^16.0.0",
|
||||||
"rimraf": "^5.0.1",
|
"rimraf": "^5.0.1",
|
||||||
|
|
@ -45604,14 +45639,6 @@
|
||||||
"keyv": "^5.3.2"
|
"keyv": "^5.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/data-schemas/node_modules/@types/whatwg-url": {
|
|
||||||
"version": "11.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
|
|
||||||
"integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/webidl-conversions": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/data-schemas/node_modules/brace-expansion": {
|
"packages/data-schemas/node_modules/brace-expansion": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||||
|
|
@ -45622,14 +45649,6 @@
|
||||||
"balanced-match": "^1.0.0"
|
"balanced-match": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/data-schemas/node_modules/bson": {
|
|
||||||
"version": "6.10.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/bson/-/bson-6.10.3.tgz",
|
|
||||||
"integrity": "sha512-MTxGsqgYTwfshYWTRdmZRC+M7FnG1b4y7RO7p2k3X24Wq0yv1m77Wsj0BzlPzd/IowgESfsruQCUToa7vbOpPQ==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=16.20.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/data-schemas/node_modules/glob": {
|
"packages/data-schemas/node_modules/glob": {
|
||||||
"version": "10.4.5",
|
"version": "10.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
||||||
|
|
@ -45667,6 +45686,22 @@
|
||||||
"@pkgjs/parseargs": "^0.11.0"
|
"@pkgjs/parseargs": "^0.11.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"packages/data-schemas/node_modules/logform": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@colors/colors": "1.6.0",
|
||||||
|
"@types/triple-beam": "^1.3.2",
|
||||||
|
"fecha": "^4.2.0",
|
||||||
|
"ms": "^2.1.1",
|
||||||
|
"safe-stable-stringify": "^2.3.1",
|
||||||
|
"triple-beam": "^1.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"packages/data-schemas/node_modules/minimatch": {
|
"packages/data-schemas/node_modules/minimatch": {
|
||||||
"version": "9.0.5",
|
"version": "9.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||||
|
|
@ -45728,15 +45763,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/data-schemas/node_modules/mongodb-connection-string-url": {
|
|
||||||
"version": "3.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz",
|
|
||||||
"integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/whatwg-url": "^11.0.2",
|
|
||||||
"whatwg-url": "^14.1.0 || ^13.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"packages/data-schemas/node_modules/mongoose": {
|
"packages/data-schemas/node_modules/mongoose": {
|
||||||
"version": "8.12.1",
|
"version": "8.12.1",
|
||||||
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.12.1.tgz",
|
||||||
|
|
@ -45758,6 +45784,27 @@
|
||||||
"url": "https://opencollective.com/mongoose"
|
"url": "https://opencollective.com/mongoose"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"packages/data-schemas/node_modules/object-hash": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"packages/data-schemas/node_modules/readable-stream": {
|
||||||
|
"version": "3.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||||
|
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||||
|
"dependencies": {
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"string_decoder": "^1.1.1",
|
||||||
|
"util-deprecate": "^1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"packages/data-schemas/node_modules/rimraf": {
|
"packages/data-schemas/node_modules/rimraf": {
|
||||||
"version": "5.0.10",
|
"version": "5.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz",
|
||||||
|
|
@ -45774,27 +45821,71 @@
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/data-schemas/node_modules/tr46": {
|
"packages/data-schemas/node_modules/traverse": {
|
||||||
"version": "5.0.0",
|
"version": "0.6.11",
|
||||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.11.tgz",
|
||||||
"integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==",
|
"integrity": "sha512-vxXDZg8/+p3gblxB6BhhG5yWVn1kGRlaL8O78UDXc3wRnPizB5g83dcvWV1jpDMIPnjZjOFuxlMmE82XJ4407w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"punycode": "^2.3.1"
|
"gopd": "^1.2.0",
|
||||||
|
"typedarray.prototype.slice": "^1.0.5",
|
||||||
|
"which-typed-array": "^1.1.18"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/data-schemas/node_modules/whatwg-url": {
|
"packages/data-schemas/node_modules/winston": {
|
||||||
"version": "14.1.1",
|
"version": "3.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz",
|
||||||
"integrity": "sha512-mDGf9diDad/giZ/Sm9Xi2YcyzaFpbdLpJPr+E9fSkyQ7KpQD4SdFcugkRQYzhmfI4KeV4Qpnn2sKPdo+kmsgRQ==",
|
"integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tr46": "^5.0.0",
|
"@colors/colors": "^1.6.0",
|
||||||
"webidl-conversions": "^7.0.0"
|
"@dabh/diagnostics": "^2.0.2",
|
||||||
|
"async": "^3.2.3",
|
||||||
|
"is-stream": "^2.0.0",
|
||||||
|
"logform": "^2.7.0",
|
||||||
|
"one-time": "^1.0.0",
|
||||||
|
"readable-stream": "^3.4.0",
|
||||||
|
"safe-stable-stringify": "^2.3.1",
|
||||||
|
"stack-trace": "0.0.x",
|
||||||
|
"triple-beam": "^1.3.0",
|
||||||
|
"winston-transport": "^4.9.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">= 12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"packages/data-schemas/node_modules/winston-daily-rotate-file": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==",
|
||||||
|
"dependencies": {
|
||||||
|
"file-stream-rotator": "^0.6.1",
|
||||||
|
"object-hash": "^3.0.0",
|
||||||
|
"triple-beam": "^1.4.1",
|
||||||
|
"winston-transport": "^4.7.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"winston": "^3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"packages/data-schemas/node_modules/winston-transport": {
|
||||||
|
"version": "4.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz",
|
||||||
|
"integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==",
|
||||||
|
"dependencies": {
|
||||||
|
"logform": "^2.7.0",
|
||||||
|
"readable-stream": "^3.6.2",
|
||||||
|
"triple-beam": "^1.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/mcp": {
|
"packages/mcp": {
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@
|
||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
"@types/jest": "^29.5.2",
|
"@types/jest": "^29.5.2",
|
||||||
"@types/node": "^20.3.0",
|
"@types/node": "^20.3.0",
|
||||||
|
"@types/traverse": "^0.6.37",
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"jest-junit": "^16.0.0",
|
"jest-junit": "^16.0.0",
|
||||||
"rimraf": "^5.0.1",
|
"rimraf": "^5.0.1",
|
||||||
|
|
@ -59,7 +60,14 @@
|
||||||
"typescript": "^5.0.4"
|
"typescript": "^5.0.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mongoose": "^8.12.1"
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"klona": "^2.0.6",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"meilisearch": "^0.50.0",
|
||||||
|
"mongoose": "^8.12.1",
|
||||||
|
"traverse": "^0.6.11",
|
||||||
|
"winston": "^3.17.0",
|
||||||
|
"winston-daily-rotate-file": "^5.0.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"keyv": "^5.3.2"
|
"keyv": "^5.3.2"
|
||||||
|
|
|
||||||
228
packages/data-schemas/src/config/parsers.ts
Normal file
228
packages/data-schemas/src/config/parsers.ts
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
import { klona } from 'klona';
|
||||||
|
import winston from 'winston';
|
||||||
|
import traverse from 'traverse';
|
||||||
|
|
||||||
|
const SPLAT_SYMBOL = Symbol.for('splat');
|
||||||
|
const MESSAGE_SYMBOL = Symbol.for('message');
|
||||||
|
const CONSOLE_JSON_STRING_LENGTH: number =
|
||||||
|
parseInt(process.env.CONSOLE_JSON_STRING_LENGTH || '', 10) || 255;
|
||||||
|
|
||||||
|
const sensitiveKeys: RegExp[] = [
|
||||||
|
/^(sk-)[^\s]+/, // OpenAI API key pattern
|
||||||
|
/(Bearer )[^\s]+/, // Header: Bearer token pattern
|
||||||
|
/(api-key:? )[^\s]+/, // Header: API key pattern
|
||||||
|
/(key=)[^\s]+/, // URL query param: sensitive key pattern (Google)
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if a given value string is sensitive and returns matching regex patterns.
|
||||||
|
*
|
||||||
|
* @param valueStr - The value string to check.
|
||||||
|
* @returns An array of regex patterns that match the value string.
|
||||||
|
*/
|
||||||
|
function getMatchingSensitivePatterns(valueStr: string): RegExp[] {
|
||||||
|
if (valueStr) {
|
||||||
|
// Filter and return all regex patterns that match the value string
|
||||||
|
return sensitiveKeys.filter((regex) => regex.test(valueStr));
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redacts sensitive information from a console message and trims it to a specified length if provided.
|
||||||
|
* @param str - The console message to be redacted.
|
||||||
|
* @param trimLength - The optional length at which to trim the redacted message.
|
||||||
|
* @returns The redacted and optionally trimmed console message.
|
||||||
|
*/
|
||||||
|
function redactMessage(str: string, trimLength?: number): string {
|
||||||
|
if (!str) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const patterns = getMatchingSensitivePatterns(str);
|
||||||
|
patterns.forEach((pattern) => {
|
||||||
|
str = str.replace(pattern, '$1[REDACTED]');
|
||||||
|
});
|
||||||
|
|
||||||
|
if (trimLength !== undefined && str.length > trimLength) {
|
||||||
|
return `${str.substring(0, trimLength)}...`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redacts sensitive information from log messages if the log level is 'error'.
|
||||||
|
* Note: Intentionally mutates the object.
|
||||||
|
* @param info - The log information object.
|
||||||
|
* @returns The modified log information object.
|
||||||
|
*/
|
||||||
|
const redactFormat = winston.format((info: winston.Logform.TransformableInfo) => {
|
||||||
|
if (info.level === 'error') {
|
||||||
|
info.message = redactMessage(info.message);
|
||||||
|
if (info[MESSAGE_SYMBOL]) {
|
||||||
|
info[MESSAGE_SYMBOL] = redactMessage(info[MESSAGE_SYMBOL]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncates long strings, especially base64 image data, within log messages.
|
||||||
|
*
|
||||||
|
* @param value - The value to be inspected and potentially truncated.
|
||||||
|
* @param length - The length at which to truncate the value. Default: 100.
|
||||||
|
* @returns The truncated or original value.
|
||||||
|
*/
|
||||||
|
const truncateLongStrings = (value: any, length = 100): any => {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return value.length > length ? value.substring(0, length) + '... [truncated]' : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array mapping function that truncates long strings (objects converted to JSON strings).
|
||||||
|
* @param item - The item to be condensed.
|
||||||
|
* @returns The condensed item.
|
||||||
|
*/
|
||||||
|
const condenseArray = (item: any): any => {
|
||||||
|
if (typeof item === 'string') {
|
||||||
|
return truncateLongStrings(JSON.stringify(item));
|
||||||
|
} else if (typeof item === 'object') {
|
||||||
|
return truncateLongStrings(JSON.stringify(item));
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats log messages for debugging purposes.
|
||||||
|
* - Truncates long strings within log messages.
|
||||||
|
* - Condenses arrays by truncating long strings and objects as strings within array items.
|
||||||
|
* - Redacts sensitive information from log messages if the log level is 'error'.
|
||||||
|
* - Converts log information object to a formatted string.
|
||||||
|
*
|
||||||
|
* @param options - The options for formatting log messages.
|
||||||
|
* @returns The formatted log message.
|
||||||
|
*/
|
||||||
|
const debugTraverse = winston.format.printf(
|
||||||
|
({ level, message, timestamp, ...metadata }: Record<string, any>) => {
|
||||||
|
if (!message) {
|
||||||
|
return `${timestamp} ${level}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!message?.trim || typeof message !== 'string') {
|
||||||
|
return `${timestamp} ${level}: ${JSON.stringify(message)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let msg = `${timestamp} ${level}: ${truncateLongStrings(message.trim(), 150)}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (level !== 'debug') {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!metadata) {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
const debugValue = metadata[SPLAT_SYMBOL]?.[0];
|
||||||
|
|
||||||
|
if (!debugValue) {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(debugValue)) {
|
||||||
|
msg += `\n${JSON.stringify(debugValue.map(condenseArray))}`;
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof debugValue !== 'object') {
|
||||||
|
return `${msg} ${debugValue}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg += '\n{';
|
||||||
|
|
||||||
|
const copy = klona(metadata);
|
||||||
|
|
||||||
|
traverse(copy).forEach(function (this: any, value: any) {
|
||||||
|
if (typeof this?.key === 'symbol') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _parentKey = '';
|
||||||
|
const parent = this.parent;
|
||||||
|
|
||||||
|
if (typeof parent?.key !== 'symbol' && parent?.key) {
|
||||||
|
_parentKey = parent.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentKey = `${parent && parent.notRoot ? _parentKey + '.' : ''}`;
|
||||||
|
const tabs = `${parent && parent.notRoot ? ' ' : ' '}`;
|
||||||
|
const currentKey = this?.key ?? 'unknown';
|
||||||
|
|
||||||
|
if (this.isLeaf && typeof value === 'string') {
|
||||||
|
const truncatedText = truncateLongStrings(value);
|
||||||
|
msg += `\n${tabs}${parentKey}${currentKey}: ${JSON.stringify(truncatedText)},`;
|
||||||
|
} else if (this.notLeaf && Array.isArray(value) && value.length > 0) {
|
||||||
|
const currentMessage = `\n${tabs}// ${value.length} ${currentKey.replace(/s$/, '')}(s)`;
|
||||||
|
this.update(currentMessage, true);
|
||||||
|
msg += currentMessage;
|
||||||
|
const stringifiedArray = value.map(condenseArray);
|
||||||
|
msg += `\n${tabs}${parentKey}${currentKey}: [${stringifiedArray}],`;
|
||||||
|
} else if (this.isLeaf && typeof value === 'function') {
|
||||||
|
msg += `\n${tabs}${parentKey}${currentKey}: function,`;
|
||||||
|
} else if (this.isLeaf) {
|
||||||
|
msg += `\n${tabs}${parentKey}${currentKey}: ${value},`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
msg += '\n}';
|
||||||
|
return msg;
|
||||||
|
} catch (e: any) {
|
||||||
|
return `${msg}\n[LOGGER PARSING ERROR] ${e.message}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Truncates long string values in JSON log objects.
|
||||||
|
* Prevents outputting extremely long values (e.g., base64, blobs).
|
||||||
|
*/
|
||||||
|
const jsonTruncateFormat = winston.format((info: any) => {
|
||||||
|
const truncateLongStrings = (str: string, maxLength: number): string =>
|
||||||
|
str.length > maxLength ? str.substring(0, maxLength) + '...' : str;
|
||||||
|
|
||||||
|
const seen = new WeakSet();
|
||||||
|
|
||||||
|
const truncateObject = (obj: any): any => {
|
||||||
|
if (typeof obj !== 'object' || obj === null) {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle circular references
|
||||||
|
if (seen.has(obj)) {
|
||||||
|
return '[Circular]';
|
||||||
|
}
|
||||||
|
seen.add(obj);
|
||||||
|
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
return obj.map((item) => truncateObject(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
const newObj: Record<string, any> = {};
|
||||||
|
Object.entries(obj).forEach(([key, value]) => {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
newObj[key] = truncateLongStrings(value, CONSOLE_JSON_STRING_LENGTH);
|
||||||
|
} else {
|
||||||
|
newObj[key] = truncateObject(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return newObj;
|
||||||
|
};
|
||||||
|
|
||||||
|
return truncateObject(info);
|
||||||
|
});
|
||||||
|
|
||||||
|
export { redactFormat, redactMessage, debugTraverse, jsonTruncateFormat };
|
||||||
129
packages/data-schemas/src/config/winston.ts
Normal file
129
packages/data-schemas/src/config/winston.ts
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
import path from 'path';
|
||||||
|
import winston from 'winston';
|
||||||
|
import 'winston-daily-rotate-file';
|
||||||
|
import { redactFormat, redactMessage, debugTraverse, jsonTruncateFormat } from './parsers';
|
||||||
|
|
||||||
|
// Define log directory
|
||||||
|
const logDir = path.join(__dirname, '..', 'logs');
|
||||||
|
|
||||||
|
// Type-safe environment variables
|
||||||
|
const { NODE_ENV, DEBUG_LOGGING, CONSOLE_JSON, DEBUG_CONSOLE } = process.env;
|
||||||
|
|
||||||
|
const useConsoleJson =
|
||||||
|
(typeof CONSOLE_JSON === 'string' && CONSOLE_JSON.toLowerCase() === 'true') ||
|
||||||
|
CONSOLE_JSON === true;
|
||||||
|
|
||||||
|
const useDebugConsole =
|
||||||
|
(typeof DEBUG_CONSOLE === 'string' && DEBUG_CONSOLE.toLowerCase() === 'true') ||
|
||||||
|
DEBUG_CONSOLE === true;
|
||||||
|
|
||||||
|
const useDebugLogging =
|
||||||
|
(typeof DEBUG_LOGGING === 'string' && DEBUG_LOGGING.toLowerCase() === 'true') ||
|
||||||
|
DEBUG_LOGGING === true;
|
||||||
|
|
||||||
|
// Define custom log levels
|
||||||
|
const levels: winston.config.AbstractConfigSetLevels = {
|
||||||
|
error: 0,
|
||||||
|
warn: 1,
|
||||||
|
info: 2,
|
||||||
|
http: 3,
|
||||||
|
verbose: 4,
|
||||||
|
debug: 5,
|
||||||
|
activity: 6,
|
||||||
|
silly: 7,
|
||||||
|
};
|
||||||
|
|
||||||
|
winston.addColors({
|
||||||
|
info: 'green',
|
||||||
|
warn: 'italic yellow',
|
||||||
|
error: 'red',
|
||||||
|
debug: 'blue',
|
||||||
|
});
|
||||||
|
|
||||||
|
const level = (): string => {
|
||||||
|
const env = NODE_ENV || 'development';
|
||||||
|
return env === 'development' ? 'debug' : 'warn';
|
||||||
|
};
|
||||||
|
|
||||||
|
const fileFormat = winston.format.combine(
|
||||||
|
redactFormat(),
|
||||||
|
winston.format.timestamp({ format: () => new Date().toISOString() }),
|
||||||
|
winston.format.errors({ stack: true }),
|
||||||
|
winston.format.splat(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const transports: winston.transport[] = [
|
||||||
|
new winston.transports.DailyRotateFile({
|
||||||
|
level: 'error',
|
||||||
|
filename: `${logDir}/error-%DATE%.log`,
|
||||||
|
datePattern: 'YYYY-MM-DD',
|
||||||
|
zippedArchive: true,
|
||||||
|
maxSize: '20m',
|
||||||
|
maxFiles: '14d',
|
||||||
|
format: fileFormat,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (useDebugLogging) {
|
||||||
|
transports.push(
|
||||||
|
new winston.transports.DailyRotateFile({
|
||||||
|
level: 'debug',
|
||||||
|
filename: `${logDir}/debug-%DATE%.log`,
|
||||||
|
datePattern: 'YYYY-MM-DD',
|
||||||
|
zippedArchive: true,
|
||||||
|
maxSize: '20m',
|
||||||
|
maxFiles: '14d',
|
||||||
|
format: winston.format.combine(fileFormat, debugTraverse),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const consoleFormat = winston.format.combine(
|
||||||
|
redactFormat(),
|
||||||
|
winston.format.colorize({ all: true }),
|
||||||
|
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
||||||
|
winston.format.printf((info) => {
|
||||||
|
const message = `${info.timestamp} ${info.level}: ${info.message}`;
|
||||||
|
return info.level.includes('error') ? redactMessage(message) : message;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
let consoleLogLevel: string = 'info';
|
||||||
|
if (useDebugConsole) {
|
||||||
|
consoleLogLevel = 'debug';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add console transport
|
||||||
|
if (useDebugConsole) {
|
||||||
|
transports.push(
|
||||||
|
new winston.transports.Console({
|
||||||
|
level: consoleLogLevel,
|
||||||
|
format: useConsoleJson
|
||||||
|
? winston.format.combine(fileFormat, jsonTruncateFormat(), winston.format.json())
|
||||||
|
: winston.format.combine(fileFormat, debugTraverse),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else if (useConsoleJson) {
|
||||||
|
transports.push(
|
||||||
|
new winston.transports.Console({
|
||||||
|
level: consoleLogLevel,
|
||||||
|
format: winston.format.combine(fileFormat, jsonTruncateFormat(), winston.format.json()),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
transports.push(
|
||||||
|
new winston.transports.Console({
|
||||||
|
level: consoleLogLevel,
|
||||||
|
format: consoleFormat,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create logger
|
||||||
|
const logger = winston.createLogger({
|
||||||
|
level: level(),
|
||||||
|
levels,
|
||||||
|
transports,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default logger;
|
||||||
|
|
@ -66,3 +66,4 @@ export type { ITransaction } from './schema/transaction';
|
||||||
|
|
||||||
export { default as userSchema } from './schema/user';
|
export { default as userSchema } from './schema/user';
|
||||||
export type { IUser } from './schema/user';
|
export type { IUser } from './schema/user';
|
||||||
|
export { registerModels } from './models';
|
||||||
|
|
|
||||||
173
packages/data-schemas/src/models/index.ts
Normal file
173
packages/data-schemas/src/models/index.ts
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
import type { Mongoose } from 'mongoose';
|
||||||
|
import {
|
||||||
|
agentSchema,
|
||||||
|
assistantSchema,
|
||||||
|
balanceSchema,
|
||||||
|
categoriesSchema,
|
||||||
|
messageSchema,
|
||||||
|
sessionSchema,
|
||||||
|
tokenSchema,
|
||||||
|
userSchema,
|
||||||
|
conversationTagSchema,
|
||||||
|
convoSchema,
|
||||||
|
fileSchema,
|
||||||
|
keySchema,
|
||||||
|
presetSchema,
|
||||||
|
projectSchema,
|
||||||
|
promptSchema,
|
||||||
|
roleSchema,
|
||||||
|
shareSchema,
|
||||||
|
toolCallSchema,
|
||||||
|
transactionSchema,
|
||||||
|
bannerSchema,
|
||||||
|
promptGroupSchema,
|
||||||
|
} from '..';
|
||||||
|
import mongoMeili from './plugins/mongoMeili';
|
||||||
|
|
||||||
|
export const registerModels = (mongoose: Mongoose) => {
|
||||||
|
const User = registerUserModel(mongoose);
|
||||||
|
const Session = registerSessionModel(mongoose);
|
||||||
|
const Token = registerTokenModel(mongoose);
|
||||||
|
const Message = registerMessageModel(mongoose);
|
||||||
|
const Agent = registerAgentModel(mongoose);
|
||||||
|
const Assistant = registerAssistantModel(mongoose);
|
||||||
|
const Balance = registerBalanceModel(mongoose);
|
||||||
|
const Banner = registerBannerModel(mongoose);
|
||||||
|
const Categories = registerCategoriesModel(mongoose);
|
||||||
|
const ConversationTag = registerConversationTagModel(mongoose);
|
||||||
|
const File = registerFileModel(mongoose);
|
||||||
|
const Key = registerKeyModel(mongoose);
|
||||||
|
const Preset = registerPresetModel(mongoose);
|
||||||
|
const Project = registerProjectModel(mongoose);
|
||||||
|
const Prompt = registerPromptModel(mongoose);
|
||||||
|
const PromptGroup = registerPromptGroupModel(mongoose);
|
||||||
|
const Role = registerRoleModel(mongoose);
|
||||||
|
const SharedLink = registerShareModel(mongoose);
|
||||||
|
const ToolCall = registerToolCallModel(mongoose);
|
||||||
|
const Transaction = registerTransactionModel(mongoose);
|
||||||
|
const Conversation = registerConversationModel(mongoose);
|
||||||
|
|
||||||
|
return {
|
||||||
|
User,
|
||||||
|
Session,
|
||||||
|
Token,
|
||||||
|
Message,
|
||||||
|
Agent,
|
||||||
|
Assistant,
|
||||||
|
Balance,
|
||||||
|
Banner,
|
||||||
|
Categories,
|
||||||
|
ConversationTag,
|
||||||
|
File,
|
||||||
|
Key,
|
||||||
|
Preset,
|
||||||
|
Project,
|
||||||
|
Prompt,
|
||||||
|
PromptGroup
|
||||||
|
Role,
|
||||||
|
SharedLink,
|
||||||
|
ToolCall,
|
||||||
|
Transaction,
|
||||||
|
Conversation,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const registerSessionModel = (mongoose: Mongoose) => {
|
||||||
|
return mongoose.models.Session || mongoose.model('Session', sessionSchema);
|
||||||
|
};
|
||||||
|
|
||||||
|
const registerUserModel = (mongoose: Mongoose) => {
|
||||||
|
return mongoose.models.User || mongoose.model('User', userSchema);
|
||||||
|
};
|
||||||
|
|
||||||
|
const registerTokenModel = (mongoose: Mongoose) => {
|
||||||
|
return mongoose.models.Token || mongoose.model('Token', tokenSchema);
|
||||||
|
};
|
||||||
|
|
||||||
|
const registerMessageModel = (mongoose: Mongoose) => {
|
||||||
|
|
||||||
|
if (process.env.MEILI_HOST && process.env.MEILI_MASTER_KEY) {
|
||||||
|
messageSchema.plugin(mongoMeili, {
|
||||||
|
host: process.env.MEILI_HOST,
|
||||||
|
apiKey: process.env.MEILI_MASTER_KEY,
|
||||||
|
indexName: 'messages',
|
||||||
|
primaryKey: 'messageId',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return mongoose.models.Message || mongoose.model('Message', messageSchema);
|
||||||
|
};
|
||||||
|
|
||||||
|
const registerAgentModel = (mongoose: Mongoose) => {
|
||||||
|
return mongoose.models.Agent || mongoose.model('Agent', agentSchema);
|
||||||
|
};
|
||||||
|
|
||||||
|
const registerAssistantModel = (mongoose: Mongoose) => {
|
||||||
|
return mongoose.models.Assistant || mongoose.model('Assistant', assistantSchema);
|
||||||
|
};
|
||||||
|
|
||||||
|
const registerBalanceModel = (mongoose: Mongoose) => {
|
||||||
|
return mongoose.models.Balance || mongoose.model('Balance', balanceSchema);
|
||||||
|
};
|
||||||
|
|
||||||
|
const registerBannerModel = (mongoose: Mongoose) => {
|
||||||
|
return mongoose.models.Banner || mongoose.model('Banner', bannerSchema);
|
||||||
|
};
|
||||||
|
|
||||||
|
const registerCategoriesModel = (mongoose: Mongoose) => {
|
||||||
|
return mongoose.models.Categories || mongoose.model('Categories', categoriesSchema);
|
||||||
|
};
|
||||||
|
|
||||||
|
const registerConversationTagModel = (mongoose: Mongoose) => {
|
||||||
|
return (
|
||||||
|
mongoose.models.ConversationTag || mongoose.model('ConversationTag', conversationTagSchema)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const registerFileModel = (mongoose: Mongoose) => {
|
||||||
|
return mongoose.models.File || mongoose.model('File', fileSchema);
|
||||||
|
};
|
||||||
|
|
||||||
|
const registerKeyModel = (mongoose: Mongoose) => {
|
||||||
|
return mongoose.models.Key || mongoose.model('Key', keySchema);
|
||||||
|
};
|
||||||
|
const registerPresetModel = (mongoose: Mongoose) => {
|
||||||
|
return mongoose.models.Preset || mongoose.model('Preset', presetSchema);
|
||||||
|
};
|
||||||
|
|
||||||
|
const registerProjectModel = (mongoose: Mongoose) => {
|
||||||
|
return mongoose.models.Project || mongoose.model('Project', projectSchema);
|
||||||
|
};
|
||||||
|
const registerPromptModel = (mongoose: Mongoose) => {
|
||||||
|
return mongoose.models.Prompt || mongoose.model('Prompt', promptSchema);
|
||||||
|
};
|
||||||
|
const registerPromptGroupModel = (mongoose: Mongoose) => {
|
||||||
|
return mongoose.models.PromptGroup || mongoose.model('PromptGroup', promptGroupSchema);
|
||||||
|
};
|
||||||
|
|
||||||
|
const registerRoleModel = (mongoose: Mongoose) => {
|
||||||
|
return mongoose.models.Role || mongoose.model('Role', roleSchema);
|
||||||
|
};
|
||||||
|
const registerShareModel = (mongoose: Mongoose) => {
|
||||||
|
return mongoose.models.SharedLink || mongoose.model('SharedLink', shareSchema);
|
||||||
|
};
|
||||||
|
|
||||||
|
const registerToolCallModel = (mongoose: Mongoose) => {
|
||||||
|
return mongoose.models.ToolCall || mongoose.model('ToolCall', toolCallSchema);
|
||||||
|
};
|
||||||
|
|
||||||
|
const registerTransactionModel = (mongoose: Mongoose) => {
|
||||||
|
return mongoose.models.Transaction || mongoose.model('Trasaction', transactionSchema);
|
||||||
|
};
|
||||||
|
const registerConversationModel = (mongoose: Mongoose) => {
|
||||||
|
if (process.env.MEILI_HOST && process.env.MEILI_MASTER_KEY) {
|
||||||
|
convoSchema.plugin(mongoMeili, {
|
||||||
|
host: process.env.MEILI_HOST,
|
||||||
|
apiKey: process.env.MEILI_MASTER_KEY,
|
||||||
|
/** Note: Will get created automatically if it doesn't exist already */
|
||||||
|
indexName: 'convos',
|
||||||
|
primaryKey: 'conversationId',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return mongoose.models.Conversation || mongoose.model('Conversation', convoSchema);
|
||||||
|
};
|
||||||
494
packages/data-schemas/src/models/plugins/mongoMeili.ts
Normal file
494
packages/data-schemas/src/models/plugins/mongoMeili.ts
Normal file
|
|
@ -0,0 +1,494 @@
|
||||||
|
import _ from 'lodash';
|
||||||
|
import mongoose, { Schema, Document, Model } from 'mongoose';
|
||||||
|
import { MeiliSearch, Index } from 'meilisearch';
|
||||||
|
const { parseTextParts } = require('librechat-data-provider');
|
||||||
|
const logger = require('~/config/meiliLogger');
|
||||||
|
|
||||||
|
interface MongoMeiliOptions {
|
||||||
|
host: string;
|
||||||
|
apiKey: string;
|
||||||
|
indexName: string;
|
||||||
|
primaryKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MeiliIndexable {
|
||||||
|
[key: string]: any;
|
||||||
|
_meiliIndex?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Environment flags
|
||||||
|
/**
|
||||||
|
* Flag to indicate if search is enabled based on environment variables.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
const searchEnabled = process.env.SEARCH && process.env.SEARCH.toLowerCase() === 'true';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag to indicate if MeiliSearch is enabled based on required environment variables.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
const meiliEnabled = process.env.MEILI_HOST && process.env.MEILI_MASTER_KEY && searchEnabled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the required options for configuring the mongoMeili plugin.
|
||||||
|
*
|
||||||
|
* @param {Object} options - The configuration options.
|
||||||
|
* @param {string} options.host - The MeiliSearch host.
|
||||||
|
* @param {string} options.apiKey - The MeiliSearch API key.
|
||||||
|
* @param {string} options.indexName - The name of the index.
|
||||||
|
* @throws {Error} Throws an error if any required option is missing.
|
||||||
|
*/
|
||||||
|
const validateOptions = function (options: any) {
|
||||||
|
const requiredKeys = ['host', 'apiKey', 'indexName'];
|
||||||
|
requiredKeys.forEach((key) => {
|
||||||
|
if (!options[key]) {
|
||||||
|
throw new Error(`Missing mongoMeili Option: ${key}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory function to create a MeiliMongooseModel class which extends a Mongoose model.
|
||||||
|
* This class contains static and instance methods to synchronize and manage the MeiliSearch index
|
||||||
|
* corresponding to the MongoDB collection.
|
||||||
|
*
|
||||||
|
* @param {Object} config - Configuration object.
|
||||||
|
* @param {Object} config.index - The MeiliSearch index object.
|
||||||
|
* @param {Array<string>} config.attributesToIndex - List of attributes to index.
|
||||||
|
* @returns {Function} A class definition that will be loaded into the Mongoose schema.
|
||||||
|
*/
|
||||||
|
const createMeiliMongooseModel = function ({
|
||||||
|
index,
|
||||||
|
attributesToIndex,
|
||||||
|
}: {
|
||||||
|
index: Index<MeiliIndexable>;
|
||||||
|
attributesToIndex: string[];
|
||||||
|
}) {
|
||||||
|
// The primary key is assumed to be the first attribute in the attributesToIndex array.
|
||||||
|
const primaryKey = attributesToIndex[0];
|
||||||
|
|
||||||
|
class MeiliMongooseModel {
|
||||||
|
/**
|
||||||
|
* Synchronizes the data between the MongoDB collection and the MeiliSearch index.
|
||||||
|
*
|
||||||
|
* The synchronization process involves:
|
||||||
|
* 1. Fetching all documents from the MongoDB collection and MeiliSearch index.
|
||||||
|
* 2. Comparing documents from both sources.
|
||||||
|
* 3. Deleting documents from MeiliSearch that no longer exist in MongoDB.
|
||||||
|
* 4. Adding documents to MeiliSearch that exist in MongoDB but not in the index.
|
||||||
|
* 5. Updating documents in MeiliSearch if key fields (such as `text` or `title`) differ.
|
||||||
|
* 6. Updating the `_meiliIndex` field in MongoDB to indicate the indexing status.
|
||||||
|
*
|
||||||
|
* Note: The function processes documents in batches because MeiliSearch's
|
||||||
|
* `index.getDocuments` requires an exact limit and `index.addDocuments` does not handle
|
||||||
|
* partial failures in a batch.
|
||||||
|
*
|
||||||
|
* @returns {Promise<void>} Resolves when the synchronization is complete.
|
||||||
|
*/
|
||||||
|
static async syncWithMeili(this: Model<any>) {
|
||||||
|
try {
|
||||||
|
let moreDocuments = true;
|
||||||
|
// Retrieve all MongoDB documents from the collection as plain JavaScript objects.
|
||||||
|
const mongoDocuments = await this.find().lean();
|
||||||
|
|
||||||
|
// Helper function to format a document by selecting only the attributes to index
|
||||||
|
// and omitting keys starting with '$'.
|
||||||
|
const format = (doc: Record<string, any>) =>
|
||||||
|
_.omitBy(_.pick(doc, attributesToIndex), (v, k) => k.startsWith('$'));
|
||||||
|
|
||||||
|
// Build a map of MongoDB documents for quick lookup based on the primary key.
|
||||||
|
const mongoMap = new Map(mongoDocuments.map((doc) => [doc[primaryKey], format(doc)]));
|
||||||
|
const indexMap = new Map();
|
||||||
|
let offset = 0;
|
||||||
|
const batchSize = 1000;
|
||||||
|
|
||||||
|
// Fetch documents from the MeiliSearch index in batches.
|
||||||
|
while (moreDocuments) {
|
||||||
|
const batch = await index.getDocuments({ limit: batchSize, offset });
|
||||||
|
if (batch.results.length === 0) {
|
||||||
|
moreDocuments = false;
|
||||||
|
}
|
||||||
|
for (const doc of batch.results) {
|
||||||
|
indexMap.set(doc[primaryKey], format(doc));
|
||||||
|
}
|
||||||
|
offset += batchSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug('[syncWithMeili]', { indexMap: indexMap.size, mongoMap: mongoMap.size });
|
||||||
|
|
||||||
|
const updateOps = [];
|
||||||
|
|
||||||
|
// Process documents present in the MeiliSearch index.
|
||||||
|
for (const [id, doc] of indexMap) {
|
||||||
|
const update: any = {};
|
||||||
|
update[primaryKey] = id;
|
||||||
|
if (mongoMap.has(id)) {
|
||||||
|
// If document exists in MongoDB, check for discrepancies in key fields.
|
||||||
|
if (
|
||||||
|
(doc.text && doc.text !== mongoMap.get(id)?.text) ||
|
||||||
|
(doc.title && doc.title !== mongoMap.get(id)?.title)
|
||||||
|
) {
|
||||||
|
logger.debug(
|
||||||
|
`[syncWithMeili] ${id} had document discrepancy in ${
|
||||||
|
doc.text ? 'text' : 'title'
|
||||||
|
} field`,
|
||||||
|
);
|
||||||
|
updateOps.push({
|
||||||
|
updateOne: { filter: update, update: { $set: { _meiliIndex: true } } },
|
||||||
|
});
|
||||||
|
await index.addDocuments([doc]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If the document does not exist in MongoDB, delete it from MeiliSearch.
|
||||||
|
await index.deleteDocument(id);
|
||||||
|
updateOps.push({
|
||||||
|
updateOne: { filter: update, update: { $set: { _meiliIndex: false } } },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process documents present in MongoDB.
|
||||||
|
for (const [id, doc] of mongoMap) {
|
||||||
|
const update: any = {};
|
||||||
|
update[primaryKey] = id;
|
||||||
|
// If the document is missing in the Meili index, add it.
|
||||||
|
if (!indexMap.has(id)) {
|
||||||
|
await index.addDocuments([doc]);
|
||||||
|
updateOps.push({
|
||||||
|
updateOne: { filter: update, update: { $set: { _meiliIndex: true } } },
|
||||||
|
});
|
||||||
|
} else if (doc._meiliIndex === false) {
|
||||||
|
// If the document exists but is marked as not indexed, update the flag.
|
||||||
|
updateOps.push({
|
||||||
|
updateOne: { filter: update, update: { $set: { _meiliIndex: true } } },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute bulk update operations in MongoDB to update the _meiliIndex flags.
|
||||||
|
if (updateOps.length > 0) {
|
||||||
|
await this.collection.bulkWrite(updateOps);
|
||||||
|
logger.debug(
|
||||||
|
`[syncWithMeili] Finished indexing ${
|
||||||
|
primaryKey === 'messageId' ? 'messages' : 'conversations'
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[syncWithMeili] Error adding document to Meili', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates settings for the MeiliSearch index.
|
||||||
|
*
|
||||||
|
* @param {Object} settings - The settings to update on the MeiliSearch index.
|
||||||
|
* @returns {Promise<Object>} Promise resolving to the update result.
|
||||||
|
*/
|
||||||
|
static async setMeiliIndexSettings(settings: any) {
|
||||||
|
return await index.updateSettings(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches the MeiliSearch index and optionally populates the results with data from MongoDB.
|
||||||
|
*
|
||||||
|
* @param {string} q - The search query.
|
||||||
|
* @param {Object} params - Additional search parameters for MeiliSearch.
|
||||||
|
* @param {boolean} populate - Whether to populate search hits with full MongoDB documents.
|
||||||
|
* @returns {Promise<Object>} The search results with populated hits if requested.
|
||||||
|
*/
|
||||||
|
static async meiliSearch(this: Model<any>, q: string, params: any, populate: boolean) {
|
||||||
|
const data = await index.search(q, params);
|
||||||
|
|
||||||
|
if (populate) {
|
||||||
|
// Build a query using the primary key values from the search hits.
|
||||||
|
const query: Record<string, any> = {};
|
||||||
|
query[primaryKey] = _.map(data.hits, (hit) => cleanUpPrimaryKeyValue(hit[primaryKey]));
|
||||||
|
|
||||||
|
// Build a projection object, including only keys that do not start with '$'.
|
||||||
|
const projection = Object.keys(this.schema.obj).reduce<Record<string, number>>(
|
||||||
|
(acc, key) => {
|
||||||
|
if (!key.startsWith('$')) acc[key] = 1;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ _id: 1, __v: 1 },
|
||||||
|
);
|
||||||
|
|
||||||
|
// Retrieve the full documents from MongoDB.
|
||||||
|
const hitsFromMongoose = await this.find(query, projection).lean();
|
||||||
|
|
||||||
|
// Merge the MongoDB documents with the search hits.
|
||||||
|
const populatedHits = data.hits.map(function (hit) {
|
||||||
|
const query = {};
|
||||||
|
query[primaryKey] = hit[primaryKey];
|
||||||
|
const originalHit = _.find(hitsFromMongoose, query);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...(originalHit ?? {}),
|
||||||
|
...hit,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
data.hits = populatedHits;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preprocesses the current document for indexing.
|
||||||
|
*
|
||||||
|
* This method:
|
||||||
|
* - Picks only the defined attributes to index.
|
||||||
|
* - Omits any keys starting with '$'.
|
||||||
|
* - Replaces pipe characters ('|') in `conversationId` with '--'.
|
||||||
|
* - Extracts and concatenates text from an array of content items.
|
||||||
|
*
|
||||||
|
* @returns {Object} The preprocessed object ready for indexing.
|
||||||
|
*/
|
||||||
|
preprocessObjectForIndex(this: Document) {
|
||||||
|
const object = _.omitBy(_.pick(this.toJSON(), attributesToIndex), (v, k) =>
|
||||||
|
k.startsWith('$'),
|
||||||
|
);
|
||||||
|
if (object.conversationId && object.conversationId.includes('|')) {
|
||||||
|
object.conversationId = object.conversationId.replace(/\|/g, '--');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (object.content && Array.isArray(object.content)) {
|
||||||
|
object.text = parseTextParts(object.content);
|
||||||
|
delete object.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the current document to the MeiliSearch index.
|
||||||
|
*
|
||||||
|
* The method preprocesses the document, adds it to MeiliSearch, and then updates
|
||||||
|
* the MongoDB document's `_meiliIndex` flag to true.
|
||||||
|
*
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async addObjectToMeili(this: Document) {
|
||||||
|
const object = this.preprocessObjectForIndex();
|
||||||
|
try {
|
||||||
|
await index.addDocuments([object]);
|
||||||
|
} catch (error) {
|
||||||
|
// Error handling can be enhanced as needed.
|
||||||
|
logger.error('[addObjectToMeili] Error adding document to Meili', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.collection.updateMany({ _id: this._id }, { $set: { _meiliIndex: true } });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current document in the MeiliSearch index.
|
||||||
|
*
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async updateObjectToMeili(this: Document) {
|
||||||
|
const object = _.omitBy(_.pick(this.toJSON(), attributesToIndex), (v, k) =>
|
||||||
|
k.startsWith('$'),
|
||||||
|
);
|
||||||
|
await index.updateDocuments([object]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the current document from the MeiliSearch index.
|
||||||
|
*
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async deleteObjectFromMeili(this: Document) {
|
||||||
|
await index.deleteDocument(this._id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post-save hook to synchronize the document with MeiliSearch.
|
||||||
|
*
|
||||||
|
* If the document is already indexed (i.e. `_meiliIndex` is true), it updates it;
|
||||||
|
* otherwise, it adds the document to the index.
|
||||||
|
*/
|
||||||
|
postSaveHook(this: Document) {
|
||||||
|
if (this._meiliIndex) {
|
||||||
|
this.updateObjectToMeili();
|
||||||
|
} else {
|
||||||
|
this.addObjectToMeili();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post-update hook to update the document in MeiliSearch.
|
||||||
|
*
|
||||||
|
* This hook is triggered after a document update, ensuring that changes are
|
||||||
|
* propagated to the MeiliSearch index if the document is indexed.
|
||||||
|
*/
|
||||||
|
postUpdateHook() {
|
||||||
|
if (this._meiliIndex) {
|
||||||
|
this.updateObjectToMeili();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post-remove hook to delete the document from MeiliSearch.
|
||||||
|
*
|
||||||
|
* This hook is triggered after a document is removed, ensuring that the document
|
||||||
|
* is also removed from the MeiliSearch index if it was previously indexed.
|
||||||
|
*/
|
||||||
|
postRemoveHook(this: Document) {
|
||||||
|
if (this._meiliIndex) {
|
||||||
|
this.deleteObjectFromMeili();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return MeiliMongooseModel;
|
||||||
|
};
|
||||||
|
|
||||||
|
const cleanUpPrimaryKeyValue = (value) => {
|
||||||
|
// For Bing convoId handling
|
||||||
|
return value.replace(/--/g, '|');
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Mongoose plugin to synchronize MongoDB collections with a MeiliSearch index.
|
||||||
|
*
|
||||||
|
* This plugin:
|
||||||
|
* - Validates the provided options.
|
||||||
|
* - Adds a `_meiliIndex` field to the schema to track indexing status.
|
||||||
|
* - Sets up a MeiliSearch client and creates an index if it doesn't already exist.
|
||||||
|
* - Loads class methods for syncing, searching, and managing documents in MeiliSearch.
|
||||||
|
* - Registers Mongoose hooks (post-save, post-update, post-remove, etc.) to maintain index consistency.
|
||||||
|
*
|
||||||
|
* @param {mongoose.Schema} schema - The Mongoose schema to which the plugin is applied.
|
||||||
|
* @param {Object} options - Configuration options.
|
||||||
|
* @param {string} options.host - The MeiliSearch host.
|
||||||
|
* @param {string} options.apiKey - The MeiliSearch API key.
|
||||||
|
* @param {string} options.indexName - The name of the MeiliSearch index.
|
||||||
|
* @param {string} options.primaryKey - The primary key field for indexing.
|
||||||
|
*/
|
||||||
|
export default function mongoMeili(schema: Schema, options: MongoMeiliOptions) {
|
||||||
|
validateOptions(options);
|
||||||
|
|
||||||
|
// Add _meiliIndex field to the schema to track if a document has been indexed in MeiliSearch.
|
||||||
|
schema.add({
|
||||||
|
_meiliIndex: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
select: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { host, apiKey, indexName, primaryKey } = options;
|
||||||
|
|
||||||
|
// Setup the MeiliSearch client.
|
||||||
|
const client = new MeiliSearch({ host, apiKey });
|
||||||
|
|
||||||
|
// Create the index asynchronously if it doesn't exist.
|
||||||
|
client.createIndex(indexName, { primaryKey });
|
||||||
|
|
||||||
|
// Setup the MeiliSearch index for this schema.
|
||||||
|
const index = client.index<MeiliIndexable>(indexName);
|
||||||
|
|
||||||
|
// Collect attributes from the schema that should be indexed.
|
||||||
|
const attributesToIndex = [
|
||||||
|
..._.reduce(
|
||||||
|
schema.obj,
|
||||||
|
function (results, value, key) {
|
||||||
|
return value.meiliIndex ? [...results, key] : results;
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Load the class methods into the schema.
|
||||||
|
schema.loadClass(createMeiliMongooseModel({ index, client, attributesToIndex }));
|
||||||
|
|
||||||
|
// Register Mongoose hooks to synchronize with MeiliSearch.
|
||||||
|
|
||||||
|
// Post-save: synchronize after a document is saved.
|
||||||
|
schema.post('save', function (doc) {
|
||||||
|
doc.postSaveHook();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Post-update: synchronize after a document is updated.
|
||||||
|
schema.post('update', function (doc) {
|
||||||
|
doc.postUpdateHook();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Post-remove: synchronize after a document is removed.
|
||||||
|
schema.post('remove', function (doc) {
|
||||||
|
doc.postRemoveHook();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pre-deleteMany hook: remove corresponding documents from MeiliSearch when multiple documents are deleted.
|
||||||
|
schema.pre('deleteMany', async function (next) {
|
||||||
|
if (!meiliEnabled) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check if the schema has a "messages" field to determine if it's a conversation schema.
|
||||||
|
if (Object.prototype.hasOwnProperty.call(schema.obj, 'messages')) {
|
||||||
|
const convoIndex = client.index('convos');
|
||||||
|
const deletedConvos = await mongoose.model('Conversation').find(this._conditions).lean();
|
||||||
|
const promises = deletedConvos.map((convo) =>
|
||||||
|
convoIndex.deleteDocument(convo.conversationId),
|
||||||
|
);
|
||||||
|
await Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the schema has a "messageId" field to determine if it's a message schema.
|
||||||
|
if (Object.prototype.hasOwnProperty.call(schema.obj, 'messageId')) {
|
||||||
|
const messageIndex = client.index('messages');
|
||||||
|
const deletedMessages = await mongoose.model('Message').find(this._conditions).lean();
|
||||||
|
const promises = deletedMessages.map((message) =>
|
||||||
|
messageIndex.deleteDocument(message.messageId),
|
||||||
|
);
|
||||||
|
await Promise.all(promises);
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
} catch (error) {
|
||||||
|
if (meiliEnabled) {
|
||||||
|
logger.error(
|
||||||
|
'[MeiliMongooseModel.deleteMany] There was an issue deleting conversation indexes upon deletion. Next startup may be slow due to syncing.',
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Post-findOneAndUpdate hook: update MeiliSearch index after a document is updated via findOneAndUpdate.
|
||||||
|
schema.post('findOneAndUpdate', async function (doc) {
|
||||||
|
if (!meiliEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the document is unfinished, do not update the index.
|
||||||
|
if (doc.unfinished) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let meiliDoc;
|
||||||
|
// For conversation documents, try to fetch the document from the "convos" index.
|
||||||
|
if (doc.messages) {
|
||||||
|
try {
|
||||||
|
meiliDoc = await client.index('convos').getDocument(doc.conversationId);
|
||||||
|
} catch (error) {
|
||||||
|
logger.debug(
|
||||||
|
'[MeiliMongooseModel.findOneAndUpdate] Convo not found in MeiliSearch and will index ' +
|
||||||
|
doc.conversationId,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the MeiliSearch document exists and the title is unchanged, do nothing.
|
||||||
|
if (meiliDoc && meiliDoc.title === doc.title) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, trigger a post-save hook to synchronize the document.
|
||||||
|
doc.postSaveHook();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
import mongoose, { Schema, Document, Types } from 'mongoose';
|
import mongoose, { Schema, Document, Types } from 'mongoose';
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
import logger from '../config/winston';
|
||||||
|
const { webcrypto } = require('node:crypto');
|
||||||
|
|
||||||
export interface ISession extends Document {
|
export interface ISession extends Document {
|
||||||
refreshTokenHash: string;
|
refreshTokenHash: string;
|
||||||
|
|
@ -23,4 +26,224 @@ const sessionSchema: Schema<ISession> = new Schema({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error class for Session-related errors
|
||||||
|
*/
|
||||||
|
class SessionError extends Error {
|
||||||
|
constructor(message, code = 'SESSION_ERROR') {
|
||||||
|
super(message);
|
||||||
|
this.name = 'SessionError';
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const { REFRESH_TOKEN_EXPIRY } = process.env ?? {};
|
||||||
|
const expires = eval(REFRESH_TOKEN_EXPIRY) ?? 1000 * 60 * 60 * 24 * 7; // 7 days default
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new session for a user
|
||||||
|
* @param {string} userId - The ID of the user
|
||||||
|
* @param {Object} options - Additional options for session creation
|
||||||
|
* @param {Date} options.expiration - Custom expiration date
|
||||||
|
* @returns {Promise<{session: Session, refreshToken: string}>}
|
||||||
|
* @throws {SessionError}
|
||||||
|
*/
|
||||||
|
sessionSchema.statics.createSession = async function (userId, options = {}) {
|
||||||
|
if (!userId) {
|
||||||
|
throw new SessionError('User ID is required', 'INVALID_USER_ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const session = {
|
||||||
|
_id: new Types.ObjectId(),
|
||||||
|
user: userId,
|
||||||
|
expiration: options.expiration || new Date(Date.now() + expires),
|
||||||
|
};
|
||||||
|
const refreshToken = await this.generateRefreshToken(session);
|
||||||
|
|
||||||
|
return { session, refreshToken };
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[createSession] Error creating session:', error);
|
||||||
|
throw new SessionError('Failed to create session', 'CREATE_SESSION_FAILED');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a session by various parameters
|
||||||
|
* @param {Object} params - Search parameters
|
||||||
|
* @param {string} [params.refreshToken] - The refresh token to search by
|
||||||
|
* @param {string} [params.userId] - The user ID to search by
|
||||||
|
* @param {string} [params.sessionId] - The session ID to search by
|
||||||
|
* @param {Object} [options] - Additional options
|
||||||
|
* @param {boolean} [options.lean=true] - Whether to return plain objects instead of documents
|
||||||
|
* @returns {Promise<Session|null>}
|
||||||
|
* @throws {SessionError}
|
||||||
|
*/
|
||||||
|
sessionSchema.statics.findSession = async function (params, options = { lean: true }) {
|
||||||
|
try {
|
||||||
|
const query = {};
|
||||||
|
|
||||||
|
if (!params.refreshToken && !params.userId && !params.sessionId) {
|
||||||
|
throw new SessionError('At least one search parameter is required', 'INVALID_SEARCH_PARAMS');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.refreshToken) {
|
||||||
|
const tokenHash = await hashToken(params.refreshToken);
|
||||||
|
query.refreshTokenHash = tokenHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.userId) {
|
||||||
|
query.user = params.userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.sessionId) {
|
||||||
|
const sessionId = params.sessionId.sessionId || params.sessionId;
|
||||||
|
if (!mongoose.Types.ObjectId.isValid(sessionId)) {
|
||||||
|
throw new SessionError('Invalid session ID format', 'INVALID_SESSION_ID');
|
||||||
|
}
|
||||||
|
query._id = sessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add expiration check to only return valid sessions
|
||||||
|
query.expiration = { $gt: new Date() };
|
||||||
|
|
||||||
|
const sessionQuery = this.findOne(query);
|
||||||
|
|
||||||
|
if (options.lean) {
|
||||||
|
return await sessionQuery.lean();
|
||||||
|
}
|
||||||
|
|
||||||
|
return await sessionQuery.exec();
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[findSession] Error finding session:', error);
|
||||||
|
throw new SessionError('Failed to find session', 'FIND_SESSION_FAILED');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a session by refresh token or session ID
|
||||||
|
* @param {Object} params - Delete parameters
|
||||||
|
* @param {string} [params.refreshToken] - The refresh token of the session to delete
|
||||||
|
* @param {string} [params.sessionId] - The ID of the session to delete
|
||||||
|
* @returns {Promise<Object>}
|
||||||
|
* @throws {SessionError}
|
||||||
|
*/
|
||||||
|
sessionSchema.statics.deleteSession = async function (params) {
|
||||||
|
try {
|
||||||
|
if (!params.refreshToken && !params.sessionId) {
|
||||||
|
throw new SessionError(
|
||||||
|
'Either refreshToken or sessionId is required',
|
||||||
|
'INVALID_DELETE_PARAMS',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = {};
|
||||||
|
|
||||||
|
if (params.refreshToken) {
|
||||||
|
query.refreshTokenHash = await hashToken(params.refreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.sessionId) {
|
||||||
|
query._id = params.sessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.deleteOne(query);
|
||||||
|
|
||||||
|
if (result.deletedCount === 0) {
|
||||||
|
logger.warn('[deleteSession] No session found to delete');
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[deleteSession] Error deleting session:', error);
|
||||||
|
throw new SessionError('Failed to delete session', 'DELETE_SESSION_FAILED');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a refresh token for a session
|
||||||
|
* @param {Session} session - The session to generate a token for
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
* @throws {SessionError}
|
||||||
|
*/
|
||||||
|
sessionSchema.statics.generateRefreshToken = async function (session) {
|
||||||
|
if (!session || !session.user) {
|
||||||
|
throw new SessionError('Invalid session object', 'INVALID_SESSION');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const expiresIn = session.expiration ? session.expiration.getTime() : Date.now() + expires;
|
||||||
|
if (!session.expiration) {
|
||||||
|
session.expiration = new Date(expiresIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshToken = await signPayload({
|
||||||
|
payload: {
|
||||||
|
id: session.user,
|
||||||
|
sessionId: session._id,
|
||||||
|
},
|
||||||
|
secret: process.env.JWT_REFRESH_SECRET,
|
||||||
|
expirationTime: Math.floor((expiresIn - Date.now()) / 1000),
|
||||||
|
});
|
||||||
|
|
||||||
|
session.refreshTokenHash = await hashToken(refreshToken);
|
||||||
|
await this.create(session);
|
||||||
|
return refreshToken;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[generateRefreshToken] Error generating refresh token:', error);
|
||||||
|
throw new SessionError('Failed to generate refresh token', 'GENERATE_TOKEN_FAILED');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes all sessions for a user
|
||||||
|
* @param {string} userId - The ID of the user
|
||||||
|
* @param {Object} [options] - Additional options
|
||||||
|
* @param {boolean} [options.excludeCurrentSession] - Whether to exclude the current session
|
||||||
|
* @param {string} [options.currentSessionId] - The ID of the current session to exclude
|
||||||
|
* @returns {Promise<Object>}
|
||||||
|
* @throws {SessionError}
|
||||||
|
*/
|
||||||
|
sessionSchema.statics.deleteAllUserSessions = async function (userId, options = {}) {
|
||||||
|
try {
|
||||||
|
if (!userId) {
|
||||||
|
throw new SessionError('User ID is required', 'INVALID_USER_ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract userId if it's passed as an object
|
||||||
|
const userIdString = userId.userId || userId;
|
||||||
|
|
||||||
|
if (!mongoose.Types.ObjectId.isValid(userIdString)) {
|
||||||
|
throw new SessionError('Invalid user ID format', 'INVALID_USER_ID_FORMAT');
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = { user: userIdString };
|
||||||
|
|
||||||
|
if (options.excludeCurrentSession && options.currentSessionId) {
|
||||||
|
query._id = { $ne: options.currentSessionId };
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.deleteMany(query);
|
||||||
|
|
||||||
|
if (result.deletedCount > 0) {
|
||||||
|
logger.debug(
|
||||||
|
`[deleteAllUserSessions] Deleted ${result.deletedCount} sessions for user ${userIdString}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('[deleteAllUserSessions] Error deleting user sessions:', error);
|
||||||
|
throw new SessionError('Failed to delete user sessions', 'DELETE_ALL_SESSIONS_FAILED');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function signPayload({ payload, secret, expirationTime }) {
|
||||||
|
return jwt.sign(payload, secret, { expiresIn: expirationTime });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function hashToken(str) {
|
||||||
|
const data = new TextEncoder().encode(str);
|
||||||
|
const hashBuffer = await webcrypto.subtle.digest('SHA-256', data);
|
||||||
|
return Buffer.from(hashBuffer).toString('hex');
|
||||||
|
}
|
||||||
export default sessionSchema;
|
export default sessionSchema;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { Schema, Document, Types } from 'mongoose';
|
import { Schema, Document, Types } from 'mongoose';
|
||||||
|
import { logger } from '~/config';
|
||||||
|
|
||||||
export interface IToken extends Document {
|
export interface IToken extends Document {
|
||||||
userId: Types.ObjectId;
|
userId: Types.ObjectId;
|
||||||
|
|
@ -47,4 +48,116 @@ const tokenSchema: Schema<IToken> = new Schema({
|
||||||
|
|
||||||
tokenSchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 });
|
tokenSchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Token instance.
|
||||||
|
* @param {Object} tokenData - The data for the new Token.
|
||||||
|
* @param {mongoose.Types.ObjectId} tokenData.userId - The user's ID. It is required.
|
||||||
|
* @param {String} tokenData.email - The user's email.
|
||||||
|
* @param {String} tokenData.token - The token. It is required.
|
||||||
|
* @param {Number} tokenData.expiresIn - The number of seconds until the token expires.
|
||||||
|
* @returns {Promise<mongoose.Document>} The new Token instance.
|
||||||
|
* @throws Will throw an error if token creation fails.
|
||||||
|
*/
|
||||||
|
tokenSchema.statics.createToken = async function (tokenData) {
|
||||||
|
try {
|
||||||
|
const currentTime = new Date();
|
||||||
|
const expiresAt = new Date(currentTime.getTime() + tokenData.expiresIn * 1000);
|
||||||
|
|
||||||
|
const newTokenData = {
|
||||||
|
...tokenData,
|
||||||
|
createdAt: currentTime,
|
||||||
|
expiresAt,
|
||||||
|
};
|
||||||
|
|
||||||
|
return await this.create(newTokenData);
|
||||||
|
} catch (error) {
|
||||||
|
logger.debug('An error occurred while creating token:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a Token document that matches the provided query.
|
||||||
|
* @param {Object} query - The query to match against.
|
||||||
|
* @param {mongoose.Types.ObjectId|String} query.userId - The ID of the user.
|
||||||
|
* @param {String} query.token - The token value.
|
||||||
|
* @param {String} [query.email] - The email of the user.
|
||||||
|
* @param {String} [query.identifier] - Unique, alternative identifier for the token.
|
||||||
|
* @param {Object} updateData - The data to update the Token with.
|
||||||
|
* @returns {Promise<mongoose.Document|null>} The updated Token document, or null if not found.
|
||||||
|
* @throws Will throw an error if the update operation fails.
|
||||||
|
*/
|
||||||
|
tokenSchema.statics.updateToken = async function (query, updateData) {
|
||||||
|
try {
|
||||||
|
return await this.findOneAndUpdate(query, updateData, { new: true });
|
||||||
|
} catch (error) {
|
||||||
|
logger.debug('An error occurred while updating token:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes all Token documents that match the provided token, user ID, or email.
|
||||||
|
* @param {Object} query - The query to match against.
|
||||||
|
* @param {mongoose.Types.ObjectId|String} query.userId - The ID of the user.
|
||||||
|
* @param {String} query.token - The token value.
|
||||||
|
* @param {String} [query.email] - The email of the user.
|
||||||
|
* @param {String} [query.identifier] - Unique, alternative identifier for the token.
|
||||||
|
* @returns {Promise<Object>} The result of the delete operation.
|
||||||
|
* @throws Will throw an error if the delete operation fails.
|
||||||
|
*/
|
||||||
|
tokenSchema.statics.deleteTokens = async function (query) {
|
||||||
|
try {
|
||||||
|
return await Token.deleteMany({
|
||||||
|
$or: [
|
||||||
|
{ userId: query.userId },
|
||||||
|
{ token: query.token },
|
||||||
|
{ email: query.email },
|
||||||
|
{ identifier: query.identifier },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.debug('An error occurred while deleting tokens:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a Token document that matches the provided query.
|
||||||
|
* @param {Object} query - The query to match against.
|
||||||
|
* @param {mongoose.Types.ObjectId|String} query.userId - The ID of the user.
|
||||||
|
* @param {String} query.token - The token value.
|
||||||
|
* @param {String} [query.email] - The email of the user.
|
||||||
|
* @param {String} [query.identifier] - Unique, alternative identifier for the token.
|
||||||
|
* @returns {Promise<Object|null>} The matched Token document, or null if not found.
|
||||||
|
* @throws Will throw an error if the find operation fails.
|
||||||
|
*/
|
||||||
|
tokenSchema.statics.findToken = async function (query) {
|
||||||
|
try {
|
||||||
|
const conditions = [];
|
||||||
|
|
||||||
|
if (query.userId) {
|
||||||
|
conditions.push({ userId: query.userId });
|
||||||
|
}
|
||||||
|
if (query.token) {
|
||||||
|
conditions.push({ token: query.token });
|
||||||
|
}
|
||||||
|
if (query.email) {
|
||||||
|
conditions.push({ email: query.email });
|
||||||
|
}
|
||||||
|
if (query.identifier) {
|
||||||
|
conditions.push({ identifier: query.identifier });
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = await this.findOne({
|
||||||
|
$and: conditions,
|
||||||
|
}).lean();
|
||||||
|
|
||||||
|
return token;
|
||||||
|
} catch (error) {
|
||||||
|
logger.debug('An error occurred while finding token:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default tokenSchema;
|
export default tokenSchema;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { Schema, Document } from 'mongoose';
|
import mongoose, { Schema, Document, Model, Types } from 'mongoose';
|
||||||
import { SystemRoles } from 'librechat-data-provider';
|
import { SystemRoles } from 'librechat-data-provider';
|
||||||
|
import { default as balanceSchema } from './balance';
|
||||||
|
import { signPayload } from './session';
|
||||||
export interface IUser extends Document {
|
export interface IUser extends Document {
|
||||||
name?: string;
|
name?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
|
|
@ -56,7 +57,7 @@ const BackupCodeSchema = new Schema(
|
||||||
{ _id: false },
|
{ _id: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
const User = new Schema<IUser>(
|
const userSchema = new Schema<IUser>(
|
||||||
{
|
{
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
@ -166,4 +167,165 @@ const User = new Schema<IUser>(
|
||||||
{ timestamps: true },
|
{ timestamps: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
export default User;
|
/**
|
||||||
|
* Search for a single user based on partial data and return matching user document as plain object.
|
||||||
|
* @param {Partial<MongoUser>} searchCriteria - The partial data to use for searching the user.
|
||||||
|
* @param {string|string[]} [fieldsToSelect] - The fields to include or exclude in the returned document.
|
||||||
|
* @returns {Promise<MongoUser>} A plain object representing the user document, or `null` if no user is found.
|
||||||
|
*/
|
||||||
|
userSchema.statics.findUser = async function (
|
||||||
|
searchCriteria: Partial<IUser>,
|
||||||
|
fieldsToSelect: string | string[] | null = null,
|
||||||
|
) {
|
||||||
|
const query = this.findOne(searchCriteria);
|
||||||
|
if (fieldsToSelect) {
|
||||||
|
query.select(fieldsToSelect);
|
||||||
|
}
|
||||||
|
return await query.lean();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count the number of user documents in the collection based on the provided filter.
|
||||||
|
*
|
||||||
|
* @param {Object} [filter={}] - The filter to apply when counting the documents.
|
||||||
|
* @returns {Promise<number>} The count of documents that match the filter.
|
||||||
|
*/
|
||||||
|
userSchema.statics.countUsers = async function (filter: Record<string, any> = {}) {
|
||||||
|
return await this.countDocuments(filter);
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Creates a new user, optionally with a TTL of 1 week.
|
||||||
|
* @param {MongoUser} data - The user data to be created, must contain user_id.
|
||||||
|
* @param {boolean} [disableTTL=true] - Whether to disable the TTL. Defaults to `true`.
|
||||||
|
* @param {boolean} [returnUser=false] - Whether to return the created user object.
|
||||||
|
* @returns {Promise<ObjectId|MongoUser>} A promise that resolves to the created user document ID or user object.
|
||||||
|
* @throws {Error} If a user with the same user_id already exists.
|
||||||
|
*/
|
||||||
|
userSchema.statics.createUser = async function (
|
||||||
|
data: Partial<IUser>,
|
||||||
|
balanceConfig: any,
|
||||||
|
disableTTL: boolean = true,
|
||||||
|
returnUser: boolean = false,
|
||||||
|
) {
|
||||||
|
const userData: Partial<IUser> = {
|
||||||
|
...data,
|
||||||
|
expiresAt: disableTTL ? null : new Date(Date.now() + 604800 * 1000), // 1 week in milliseconds
|
||||||
|
};
|
||||||
|
|
||||||
|
if (disableTTL) {
|
||||||
|
delete userData.expiresAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await this.create(userData);
|
||||||
|
|
||||||
|
// If balance is enabled, create or update a balance record for the user using global.interfaceConfig.balance
|
||||||
|
if (balanceConfig?.enabled && balanceConfig?.startBalance) {
|
||||||
|
const update = {
|
||||||
|
$inc: { tokenCredits: balanceConfig.startBalance },
|
||||||
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
balanceConfig.autoRefillEnabled &&
|
||||||
|
balanceConfig.refillIntervalValue != null &&
|
||||||
|
balanceConfig.refillIntervalUnit != null &&
|
||||||
|
balanceConfig.refillAmount != null
|
||||||
|
) {
|
||||||
|
update.$set = {
|
||||||
|
autoRefillEnabled: true,
|
||||||
|
refillIntervalValue: balanceConfig.refillIntervalValue,
|
||||||
|
refillIntervalUnit: balanceConfig.refillIntervalUnit,
|
||||||
|
refillAmount: balanceConfig.refillAmount,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const balanceModel = mongoose.model('Balance', balanceSchema);
|
||||||
|
await balanceModel
|
||||||
|
.findOneAndUpdate({ user: user._id }, update, { upsert: true, new: true })
|
||||||
|
.lean();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (returnUser) {
|
||||||
|
return user.toObject();
|
||||||
|
}
|
||||||
|
return user._id;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Update a user with new data without overwriting existing properties.
|
||||||
|
*
|
||||||
|
* @param {string} userId - The ID of the user to update.
|
||||||
|
* @param {Object} updateData - An object containing the properties to update.
|
||||||
|
* @returns {Promise<MongoUser>} The updated user document as a plain object, or `null` if no user is found.
|
||||||
|
*/
|
||||||
|
userSchema.statics.updateUser = async function (userId: string, updateData: Partial<IUser>) {
|
||||||
|
const updateOperation = {
|
||||||
|
$set: updateData,
|
||||||
|
$unset: { expiresAt: '' }, // Remove the expiresAt field to prevent TTL
|
||||||
|
};
|
||||||
|
return await this.findByIdAndUpdate(userId, updateOperation, {
|
||||||
|
new: true,
|
||||||
|
runValidators: true,
|
||||||
|
}).lean();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a user by ID and convert the found user document to a plain object.
|
||||||
|
*
|
||||||
|
* @param {string} userId - The ID of the user to find and return as a plain object.
|
||||||
|
* @param {string|string[]} [fieldsToSelect] - The fields to include or exclude in the returned document.
|
||||||
|
* @returns {Promise<MongoUser>} A plain object representing the user document, or `null` if no user is found.
|
||||||
|
*/
|
||||||
|
userSchema.statics.getUserById = async function (
|
||||||
|
userId: string,
|
||||||
|
fieldsToSelect: string | string[] | null = null,
|
||||||
|
) {
|
||||||
|
const query = this.findById(userId);
|
||||||
|
if (fieldsToSelect) {
|
||||||
|
query.select(fieldsToSelect);
|
||||||
|
}
|
||||||
|
return await query.lean();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a user by their unique ID.
|
||||||
|
*
|
||||||
|
* @param {string} userId - The ID of the user to delete.
|
||||||
|
* @returns {Promise<{ deletedCount: number }>} An object indicating the number of deleted documents.
|
||||||
|
*/
|
||||||
|
userSchema.statics.deleteUserById = async function (userId: string) {
|
||||||
|
try {
|
||||||
|
const result = await this.deleteOne({ _id: userId });
|
||||||
|
if (result.deletedCount === 0) {
|
||||||
|
return { deletedCount: 0, message: 'No user found with that ID.' };
|
||||||
|
}
|
||||||
|
return { deletedCount: result.deletedCount, message: 'User was deleted successfully.' };
|
||||||
|
} catch (error: any) {
|
||||||
|
throw new Error('Error deleting user: ' + error?.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a JWT token for a given user.
|
||||||
|
*
|
||||||
|
* @param {MongoUser} user - The user for whom the token is being generated.
|
||||||
|
* @returns {Promise<string>} A promise that resolves to a JWT token.
|
||||||
|
*/
|
||||||
|
userSchema.methods.generateToken = async function (user: IUser): Promise<string> {
|
||||||
|
if (!user) {
|
||||||
|
throw new Error('No user provided');
|
||||||
|
}
|
||||||
|
|
||||||
|
const expires = eval(process.env.SESSION_EXPIRY ?? '0') ?? 1000 * 60 * 15;
|
||||||
|
|
||||||
|
return await signPayload({
|
||||||
|
payload: {
|
||||||
|
id: user._id,
|
||||||
|
username: user.username,
|
||||||
|
provider: user.provider,
|
||||||
|
email: user.email,
|
||||||
|
},
|
||||||
|
secret: process.env.JWT_SECRET,
|
||||||
|
expirationTime: expires / 1000,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default userSchema;
|
||||||
|
|
|
||||||
|
|
@ -15,5 +15,6 @@
|
||||||
"sourceMap": true
|
"sourceMap": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"],
|
"include": ["src/**/*"],
|
||||||
"exclude": ["node_modules", "dist", "tests"]
|
"exclude": ["node_modules", "dist", "tests"],
|
||||||
|
"esModuleInterop": true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue