mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-23 03:40:14 +01:00
refactor(data-schemas): enhance method organization and add librechat-data-provider dependency
This commit is contained in:
parent
c201d54cac
commit
2d492b932f
6 changed files with 490 additions and 414 deletions
1
package-lock.json
generated
1
package-lock.json
generated
|
|
@ -45642,6 +45642,7 @@
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"keyv": "^5.3.2",
|
"keyv": "^5.3.2",
|
||||||
|
"librechat-data-provider": "*",
|
||||||
"mongoose": "^8.12.1"
|
"mongoose": "^8.12.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,8 @@
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"keyv": "^5.3.2",
|
"keyv": "^5.3.2",
|
||||||
"mongoose": "^8.12.1"
|
"mongoose": "^8.12.1",
|
||||||
|
"librechat-data-provider": "*"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"registry": "https://registry.npmjs.org/",
|
"registry": "https://registry.npmjs.org/",
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,24 @@
|
||||||
// User methods
|
import { createUserMethods, type UserMethods } from './user';
|
||||||
export * from './user';
|
import { createSessionMethods, type SessionMethods } from './session';
|
||||||
|
import { createTokenMethods, type TokenMethods } from './token';
|
||||||
|
import { createRoleMethods, type RoleMethods } from './role';
|
||||||
|
|
||||||
// Session methods
|
/**
|
||||||
export * from './session';
|
* Creates all database methods for all collections
|
||||||
|
*/
|
||||||
|
export function createAllMethods(mongoose: typeof import('mongoose')) {
|
||||||
|
return {
|
||||||
|
...createUserMethods(mongoose),
|
||||||
|
...createSessionMethods(mongoose),
|
||||||
|
...createTokenMethods(mongoose),
|
||||||
|
...createRoleMethods(mongoose),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Token methods
|
export type AllMethods = UserMethods & SessionMethods & TokenMethods & RoleMethods;
|
||||||
export * from './token';
|
|
||||||
|
// Also export individual factory functions for granular usage if needed
|
||||||
|
export { createUserMethods, type UserMethods } from './user';
|
||||||
|
export { createSessionMethods, type SessionMethods } from './session';
|
||||||
|
export { createTokenMethods, type TokenMethods } from './token';
|
||||||
|
export { createRoleMethods, type RoleMethods } from './role';
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import mongoose from 'mongoose';
|
|
||||||
import { Session } from '~/models';
|
|
||||||
import {
|
import {
|
||||||
ISession,
|
ISession,
|
||||||
CreateSessionOptions,
|
CreateSessionOptions,
|
||||||
|
|
@ -16,10 +14,12 @@ import logger from '~/config/winston';
|
||||||
const { REFRESH_TOKEN_EXPIRY } = process.env ?? {};
|
const { REFRESH_TOKEN_EXPIRY } = process.env ?? {};
|
||||||
const expires = eval(REFRESH_TOKEN_EXPIRY ?? '0') ?? 1000 * 60 * 60 * 24 * 7; // 7 days default
|
const expires = eval(REFRESH_TOKEN_EXPIRY ?? '0') ?? 1000 * 60 * 60 * 24 * 7; // 7 days default
|
||||||
|
|
||||||
|
// Factory function that takes mongoose instance and returns the methods
|
||||||
|
export function createSessionMethods(mongoose: typeof import('mongoose')) {
|
||||||
/**
|
/**
|
||||||
* Creates a new session for a user
|
* Creates a new session for a user
|
||||||
*/
|
*/
|
||||||
export async function createSession(
|
async function createSession(
|
||||||
userId: string,
|
userId: string,
|
||||||
options: CreateSessionOptions = {},
|
options: CreateSessionOptions = {},
|
||||||
): Promise<SessionResult> {
|
): Promise<SessionResult> {
|
||||||
|
|
@ -45,15 +45,19 @@ export async function createSession(
|
||||||
/**
|
/**
|
||||||
* Finds a session by various parameters
|
* Finds a session by various parameters
|
||||||
*/
|
*/
|
||||||
export async function findSession(
|
async function findSession(
|
||||||
params: SessionSearchParams,
|
params: SessionSearchParams,
|
||||||
options: SessionQueryOptions = { lean: true },
|
options: SessionQueryOptions = { lean: true },
|
||||||
): Promise<ISession | null> {
|
): Promise<ISession | null> {
|
||||||
try {
|
try {
|
||||||
|
const Session = mongoose.models.Session;
|
||||||
const query: Record<string, unknown> = {};
|
const query: Record<string, unknown> = {};
|
||||||
|
|
||||||
if (!params.refreshToken && !params.userId && !params.sessionId) {
|
if (!params.refreshToken && !params.userId && !params.sessionId) {
|
||||||
throw new SessionError('At least one search parameter is required', 'INVALID_SEARCH_PARAMS');
|
throw new SessionError(
|
||||||
|
'At least one search parameter is required',
|
||||||
|
'INVALID_SEARCH_PARAMS',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.refreshToken) {
|
if (params.refreshToken) {
|
||||||
|
|
@ -82,7 +86,7 @@ export async function findSession(
|
||||||
const sessionQuery = Session.findOne(query);
|
const sessionQuery = Session.findOne(query);
|
||||||
|
|
||||||
if (options.lean) {
|
if (options.lean) {
|
||||||
return await sessionQuery.lean();
|
return (await sessionQuery.lean()) as ISession | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await sessionQuery.exec();
|
return await sessionQuery.exec();
|
||||||
|
|
@ -95,10 +99,10 @@ export async function findSession(
|
||||||
/**
|
/**
|
||||||
* Deletes a session by refresh token or session ID
|
* Deletes a session by refresh token or session ID
|
||||||
*/
|
*/
|
||||||
export async function deleteSession(
|
async function deleteSession(params: DeleteSessionParams): Promise<{ deletedCount?: number }> {
|
||||||
params: DeleteSessionParams,
|
|
||||||
): Promise<{ deletedCount?: number }> {
|
|
||||||
try {
|
try {
|
||||||
|
const Session = mongoose.models.Session;
|
||||||
|
|
||||||
if (!params.refreshToken && !params.sessionId) {
|
if (!params.refreshToken && !params.sessionId) {
|
||||||
throw new SessionError(
|
throw new SessionError(
|
||||||
'Either refreshToken or sessionId is required',
|
'Either refreshToken or sessionId is required',
|
||||||
|
|
@ -132,12 +136,13 @@ export async function deleteSession(
|
||||||
/**
|
/**
|
||||||
* Generates a refresh token for a session
|
* Generates a refresh token for a session
|
||||||
*/
|
*/
|
||||||
export async function generateRefreshToken(session: Partial<ISession>): Promise<string> {
|
async function generateRefreshToken(session: Partial<ISession>): Promise<string> {
|
||||||
if (!session || !session.user) {
|
if (!session || !session.user) {
|
||||||
throw new SessionError('Invalid session object', 'INVALID_SESSION');
|
throw new SessionError('Invalid session object', 'INVALID_SESSION');
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const Session = mongoose.models.Session;
|
||||||
const expiresIn = session.expiration ? session.expiration.getTime() : Date.now() + expires;
|
const expiresIn = session.expiration ? session.expiration.getTime() : Date.now() + expires;
|
||||||
if (!session.expiration) {
|
if (!session.expiration) {
|
||||||
session.expiration = new Date(expiresIn);
|
session.expiration = new Date(expiresIn);
|
||||||
|
|
@ -164,11 +169,13 @@ export async function generateRefreshToken(session: Partial<ISession>): Promise<
|
||||||
/**
|
/**
|
||||||
* Deletes all sessions for a user
|
* Deletes all sessions for a user
|
||||||
*/
|
*/
|
||||||
export async function deleteAllUserSessions(
|
async function deleteAllUserSessions(
|
||||||
userId: string | { userId: string },
|
userId: string | { userId: string },
|
||||||
options: DeleteAllSessionsOptions = {},
|
options: DeleteAllSessionsOptions = {},
|
||||||
): Promise<{ deletedCount?: number }> {
|
): Promise<{ deletedCount?: number }> {
|
||||||
try {
|
try {
|
||||||
|
const Session = mongoose.models.Session;
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
throw new SessionError('User ID is required', 'INVALID_USER_ID');
|
throw new SessionError('User ID is required', 'INVALID_USER_ID');
|
||||||
}
|
}
|
||||||
|
|
@ -200,3 +207,15 @@ export async function deleteAllUserSessions(
|
||||||
throw new SessionError('Failed to delete user sessions', 'DELETE_ALL_SESSIONS_FAILED');
|
throw new SessionError('Failed to delete user sessions', 'DELETE_ALL_SESSIONS_FAILED');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return all methods
|
||||||
|
return {
|
||||||
|
createSession,
|
||||||
|
findSession,
|
||||||
|
deleteSession,
|
||||||
|
generateRefreshToken,
|
||||||
|
deleteAllUserSessions,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SessionMethods = ReturnType<typeof createSessionMethods>;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
import { Token } from '~/models';
|
|
||||||
import { IToken, TokenCreateData, TokenQuery, TokenUpdateData, TokenDeleteResult } from '~/types';
|
import { IToken, TokenCreateData, TokenQuery, TokenUpdateData, TokenDeleteResult } from '~/types';
|
||||||
import logger from '~/config/winston';
|
import logger from '~/config/winston';
|
||||||
|
|
||||||
|
// Factory function that takes mongoose instance and returns the methods
|
||||||
|
export function createTokenMethods(mongoose: typeof import('mongoose')) {
|
||||||
/**
|
/**
|
||||||
* Creates a new Token instance.
|
* Creates a new Token instance.
|
||||||
*/
|
*/
|
||||||
export async function createToken(tokenData: TokenCreateData): Promise<IToken> {
|
async function createToken(tokenData: TokenCreateData): Promise<IToken> {
|
||||||
try {
|
try {
|
||||||
|
const Token = mongoose.models.Token;
|
||||||
const currentTime = new Date();
|
const currentTime = new Date();
|
||||||
const expiresAt = new Date(currentTime.getTime() + tokenData.expiresIn * 1000);
|
const expiresAt = new Date(currentTime.getTime() + tokenData.expiresIn * 1000);
|
||||||
|
|
||||||
|
|
@ -26,11 +28,12 @@ export async function createToken(tokenData: TokenCreateData): Promise<IToken> {
|
||||||
/**
|
/**
|
||||||
* Updates a Token document that matches the provided query.
|
* Updates a Token document that matches the provided query.
|
||||||
*/
|
*/
|
||||||
export async function updateToken(
|
async function updateToken(
|
||||||
query: TokenQuery,
|
query: TokenQuery,
|
||||||
updateData: TokenUpdateData,
|
updateData: TokenUpdateData,
|
||||||
): Promise<IToken | null> {
|
): Promise<IToken | null> {
|
||||||
try {
|
try {
|
||||||
|
const Token = mongoose.models.Token;
|
||||||
return await Token.findOneAndUpdate(query, updateData, { new: true });
|
return await Token.findOneAndUpdate(query, updateData, { new: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.debug('An error occurred while updating token:', error);
|
logger.debug('An error occurred while updating token:', error);
|
||||||
|
|
@ -41,8 +44,9 @@ export async function updateToken(
|
||||||
/**
|
/**
|
||||||
* Deletes all Token documents that match the provided token, user ID, or email.
|
* Deletes all Token documents that match the provided token, user ID, or email.
|
||||||
*/
|
*/
|
||||||
export async function deleteTokens(query: TokenQuery): Promise<TokenDeleteResult> {
|
async function deleteTokens(query: TokenQuery): Promise<TokenDeleteResult> {
|
||||||
try {
|
try {
|
||||||
|
const Token = mongoose.models.Token;
|
||||||
const conditions = [];
|
const conditions = [];
|
||||||
|
|
||||||
if (query.userId) {
|
if (query.userId) {
|
||||||
|
|
@ -74,8 +78,9 @@ export async function deleteTokens(query: TokenQuery): Promise<TokenDeleteResult
|
||||||
/**
|
/**
|
||||||
* Finds a Token document that matches the provided query.
|
* Finds a Token document that matches the provided query.
|
||||||
*/
|
*/
|
||||||
export async function findToken(query: TokenQuery): Promise<IToken | null> {
|
async function findToken(query: TokenQuery): Promise<IToken | null> {
|
||||||
try {
|
try {
|
||||||
|
const Token = mongoose.models.Token;
|
||||||
const conditions = [];
|
const conditions = [];
|
||||||
|
|
||||||
if (query.userId) {
|
if (query.userId) {
|
||||||
|
|
@ -103,3 +108,14 @@ export async function findToken(query: TokenQuery): Promise<IToken | null> {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return all methods
|
||||||
|
return {
|
||||||
|
createToken,
|
||||||
|
updateToken,
|
||||||
|
deleteTokens,
|
||||||
|
findToken,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TokenMethods = ReturnType<typeof createTokenMethods>;
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,44 @@
|
||||||
import mongoose, { FilterQuery } from 'mongoose';
|
import mongoose, { FilterQuery } from 'mongoose';
|
||||||
import { User, Balance } from '~/models';
|
|
||||||
import { IUser, BalanceConfig, UserCreateData, UserUpdateResult } from '~/types';
|
import { IUser, BalanceConfig, UserCreateData, UserUpdateResult } from '~/types';
|
||||||
import { signPayload } from '~/schema/session';
|
import { signPayload } from '~/schema/session';
|
||||||
|
|
||||||
|
/** Factory function that takes mongoose instance and returns the methods */
|
||||||
|
export function createUserMethods(mongoose: typeof import('mongoose')) {
|
||||||
/**
|
/**
|
||||||
* Search for a single user based on partial data and return matching user document as plain object.
|
* Search for a single user based on partial data and return matching user document as plain object.
|
||||||
*/
|
*/
|
||||||
export async function findUser(
|
async function findUser(
|
||||||
searchCriteria: FilterQuery<IUser>,
|
searchCriteria: FilterQuery<IUser>,
|
||||||
fieldsToSelect?: string | string[] | null,
|
fieldsToSelect?: string | string[] | null,
|
||||||
): Promise<IUser | null> {
|
): Promise<IUser | null> {
|
||||||
|
const User = mongoose.models.User;
|
||||||
const query = User.findOne(searchCriteria);
|
const query = User.findOne(searchCriteria);
|
||||||
if (fieldsToSelect) {
|
if (fieldsToSelect) {
|
||||||
query.select(fieldsToSelect);
|
query.select(fieldsToSelect);
|
||||||
}
|
}
|
||||||
return await query.lean();
|
return (await query.lean()) as IUser | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Count the number of user documents in the collection based on the provided filter.
|
* Count the number of user documents in the collection based on the provided filter.
|
||||||
*/
|
*/
|
||||||
export async function countUsers(filter: FilterQuery<IUser> = {}): Promise<number> {
|
async function countUsers(filter: FilterQuery<IUser> = {}): Promise<number> {
|
||||||
|
const User = mongoose.models.User;
|
||||||
return await User.countDocuments(filter);
|
return await User.countDocuments(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new user, optionally with a TTL of 1 week.
|
* Creates a new user, optionally with a TTL of 1 week.
|
||||||
*/
|
*/
|
||||||
export async function createUser(
|
async function createUser(
|
||||||
data: UserCreateData,
|
data: UserCreateData,
|
||||||
balanceConfig?: BalanceConfig,
|
balanceConfig?: BalanceConfig,
|
||||||
disableTTL: boolean = true,
|
disableTTL: boolean = true,
|
||||||
returnUser: boolean = false,
|
returnUser: boolean = false,
|
||||||
): Promise<mongoose.Types.ObjectId | Partial<IUser>> {
|
): Promise<mongoose.Types.ObjectId | Partial<IUser>> {
|
||||||
|
const User = mongoose.models.User;
|
||||||
|
const Balance = mongoose.models.Balance;
|
||||||
|
|
||||||
const userData: Partial<IUser> = {
|
const userData: Partial<IUser> = {
|
||||||
...data,
|
...data,
|
||||||
expiresAt: disableTTL ? undefined : new Date(Date.now() + 604800 * 1000), // 1 week in milliseconds
|
expiresAt: disableTTL ? undefined : new Date(Date.now() + 604800 * 1000), // 1 week in milliseconds
|
||||||
|
|
@ -72,7 +78,10 @@ export async function createUser(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
await Balance.findOneAndUpdate({ user: user._id }, update, { upsert: true, new: true }).lean();
|
await Balance.findOneAndUpdate({ user: user._id }, update, {
|
||||||
|
upsert: true,
|
||||||
|
new: true,
|
||||||
|
}).lean();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (returnUser) {
|
if (returnUser) {
|
||||||
|
|
@ -84,39 +93,39 @@ export async function createUser(
|
||||||
/**
|
/**
|
||||||
* Update a user with new data without overwriting existing properties.
|
* Update a user with new data without overwriting existing properties.
|
||||||
*/
|
*/
|
||||||
export async function updateUser(
|
async function updateUser(userId: string, updateData: Partial<IUser>): Promise<IUser | null> {
|
||||||
userId: string,
|
const User = mongoose.models.User;
|
||||||
updateData: Partial<IUser>,
|
|
||||||
): Promise<IUser | null> {
|
|
||||||
const updateOperation = {
|
const updateOperation = {
|
||||||
$set: updateData,
|
$set: updateData,
|
||||||
$unset: { expiresAt: '' }, // Remove the expiresAt field to prevent TTL
|
$unset: { expiresAt: '' }, // Remove the expiresAt field to prevent TTL
|
||||||
};
|
};
|
||||||
return await User.findByIdAndUpdate(userId, updateOperation, {
|
return (await User.findByIdAndUpdate(userId, updateOperation, {
|
||||||
new: true,
|
new: true,
|
||||||
runValidators: true,
|
runValidators: true,
|
||||||
}).lean();
|
}).lean()) as IUser | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve a user by ID and convert the found user document to a plain object.
|
* Retrieve a user by ID and convert the found user document to a plain object.
|
||||||
*/
|
*/
|
||||||
export async function getUserById(
|
async function getUserById(
|
||||||
userId: string,
|
userId: string,
|
||||||
fieldsToSelect?: string | string[] | null,
|
fieldsToSelect?: string | string[] | null,
|
||||||
): Promise<IUser | null> {
|
): Promise<IUser | null> {
|
||||||
|
const User = mongoose.models.User;
|
||||||
const query = User.findById(userId);
|
const query = User.findById(userId);
|
||||||
if (fieldsToSelect) {
|
if (fieldsToSelect) {
|
||||||
query.select(fieldsToSelect);
|
query.select(fieldsToSelect);
|
||||||
}
|
}
|
||||||
return await query.lean();
|
return (await query.lean()) as IUser | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a user by their unique ID.
|
* Delete a user by their unique ID.
|
||||||
*/
|
*/
|
||||||
export async function deleteUserById(userId: string): Promise<UserUpdateResult> {
|
async function deleteUserById(userId: string): Promise<UserUpdateResult> {
|
||||||
try {
|
try {
|
||||||
|
const User = mongoose.models.User;
|
||||||
const result = await User.deleteOne({ _id: userId });
|
const result = await User.deleteOne({ _id: userId });
|
||||||
if (result.deletedCount === 0) {
|
if (result.deletedCount === 0) {
|
||||||
return { deletedCount: 0, message: 'No user found with that ID.' };
|
return { deletedCount: 0, message: 'No user found with that ID.' };
|
||||||
|
|
@ -131,7 +140,7 @@ export async function deleteUserById(userId: string): Promise<UserUpdateResult>
|
||||||
/**
|
/**
|
||||||
* Generates a JWT token for a given user.
|
* Generates a JWT token for a given user.
|
||||||
*/
|
*/
|
||||||
export async function generateToken(user: IUser): Promise<string> {
|
async function generateToken(user: IUser): Promise<string> {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new Error('No user provided');
|
throw new Error('No user provided');
|
||||||
}
|
}
|
||||||
|
|
@ -149,3 +158,17 @@ export async function generateToken(user: IUser): Promise<string> {
|
||||||
expirationTime: expires / 1000,
|
expirationTime: expires / 1000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return all methods
|
||||||
|
return {
|
||||||
|
findUser,
|
||||||
|
countUsers,
|
||||||
|
createUser,
|
||||||
|
updateUser,
|
||||||
|
getUserById,
|
||||||
|
deleteUserById,
|
||||||
|
generateToken,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UserMethods = ReturnType<typeof createUserMethods>;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue