diff --git a/api/server/controllers/AuthController.js b/api/server/controllers/AuthController.js index 8104a1c1d1..3b19494330 100644 --- a/api/server/controllers/AuthController.js +++ b/api/server/controllers/AuthController.js @@ -1,6 +1,5 @@ const cookies = require('cookie'); const jwt = require('jsonwebtoken'); -const mongoose = require('mongoose'); const openIdClient = require('openid-client'); const { logger } = require('@librechat/data-schemas'); const { @@ -10,11 +9,11 @@ const { requestPasswordReset, setOpenIDAuthTokens, } = require('~/server/services/AuthService'); +const { findUser, getUserById } = require('~/models'); const { getOpenIdConfig } = require('~/strategies'); const { isEnabled } = require('~/server/utils'); const Session = require('~/db/models').Session; -const User = require('~/db/models').User; const registrationController = async (req, res) => { try { @@ -73,7 +72,7 @@ const refreshController = async (req, res) => { const openIdConfig = getOpenIdConfig(); const tokenset = await openIdClient.refreshTokenGrant(openIdConfig, refreshToken); const claims = tokenset.claims(); - const user = await User.findUser({ email: claims.email }); + const user = await findUser({ email: claims.email }); if (!user) { return res.status(401).redirect('/login'); } @@ -86,7 +85,7 @@ const refreshController = async (req, res) => { } try { const payload = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET); - const user = await User.getUserById(payload.id, '-password -__v -totpSecret'); + const user = await getUserById(payload.id, '-password -__v -totpSecret'); if (!user) { return res.status(401).redirect('/login'); } diff --git a/api/server/middleware/checkBan.js b/api/server/middleware/checkBan.js index 7e4301e227..91c31ab66a 100644 --- a/api/server/middleware/checkBan.js +++ b/api/server/middleware/checkBan.js @@ -1,14 +1,12 @@ const { Keyv } = require('keyv'); const uap = require('ua-parser-js'); -const mongoose = require('mongoose'); const { logger } = require('@librechat/data-schemas'); const { ViolationTypes } = require('librechat-data-provider'); const { isEnabled, removePorts } = require('~/server/utils'); const keyvMongo = require('~/cache/keyvMongo'); const denyRequest = require('./denyRequest'); const { getLogStores } = require('~/cache'); - -const User = require('~/db/models').User; +const { findUser } = require('~/models'); 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.'; @@ -59,7 +57,7 @@ const checkBan = async (req, res, next = () => {}) => { let userId = req.user?.id ?? req.user?._id ?? null; if (!userId && req?.body?.email) { - const user = await User.findUser({ email: req.body.email }, '_id'); + const user = await findUser({ email: req.body.email }, '_id'); userId = user?._id ? user._id.toString() : userId; } diff --git a/api/strategies/localStrategy.js b/api/strategies/localStrategy.js index 03453ace9a..a8859028e5 100644 --- a/api/strategies/localStrategy.js +++ b/api/strategies/localStrategy.js @@ -1,9 +1,8 @@ -const mongoose = require('mongoose'); const { logger } = require('@librechat/data-schemas'); const { errorsToString } = require('librechat-data-provider'); const { Strategy: PassportLocalStrategy } = require('passport-local'); const { isEnabled, checkEmailConfig } = require('~/server/utils'); -const { comparePassword } = require('~/models'); +const { findUser, comparePassword } = require('~/models'); const { loginSchema } = require('./validators'); const User = require('~/db/models').User; @@ -25,7 +24,7 @@ async function passportLogin(req, email, password, done) { return done(null, false, { message: validationError }); } - const user = await User.findUser({ email: email.trim() }); + const user = await findUser({ email: email.trim() }); if (!user) { logError('Passport Local Strategy - User Not Found', { email }); logger.error(`[Login] [Login failed] [Username: ${email}] [Request-IP: ${req.ip}]`); diff --git a/api/strategies/openidStrategy.js b/api/strategies/openidStrategy.js index 82ead13a3c..60c9f2df07 100644 --- a/api/strategies/openidStrategy.js +++ b/api/strategies/openidStrategy.js @@ -1,5 +1,4 @@ const fetch = require('node-fetch'); -const mongoose = require('mongoose'); const passport = require('passport'); const client = require('openid-client'); const jwtDecode = require('jsonwebtoken/decode'); @@ -8,11 +7,10 @@ const { CacheKeys } = require('librechat-data-provider'); const { HttpsProxyAgent } = require('https-proxy-agent'); const { Strategy: OpenIDStrategy } = require('openid-client/passport'); const { getStrategyFunctions } = require('~/server/services/Files/strategies'); +const { findUser, createUser, updateUser } = require('~/models'); const getLogStores = require('~/cache/getLogStores'); const { isEnabled } = require('~/server/utils'); -const User = require('~/db/models').User; - /** * @typedef {import('openid-client').ClientMetadata} ClientMetadata * @typedef {import('openid-client').Configuration} Configuration @@ -248,13 +246,13 @@ async function setupOpenId() { async (tokenset, done) => { try { const claims = tokenset.claims(); - let user = await User.findUser({ openidId: claims.sub }); + let user = await findUser({ openidId: claims.sub }); logger.info( `[openidStrategy] user ${user ? 'found' : 'not found'} with openidId: ${claims.sub}`, ); if (!user) { - user = await User.findUser({ email: claims.email }); + user = await findUser({ email: claims.email }); logger.info( `[openidStrategy] user ${user ? 'found' : 'not found'} with email: ${ claims.email @@ -318,7 +316,7 @@ async function setupOpenId() { const balanceConfig = await getBalanceConfig(); - user = await User.createUser(user, balanceConfig, true, true); + user = await createUser(user, balanceConfig, true, true); } else { user.provider = 'openid'; user.openidId = userinfo.sub; @@ -354,7 +352,7 @@ async function setupOpenId() { } } - user = await User.updateUser(user._id, user); + user = await updateUser(user._id, user); logger.info( `[openidStrategy] login success openidId: ${user.openidId} | email: ${user.email} | username: ${user.username} `, diff --git a/packages/data-schemas/src/types/README.md b/packages/data-schemas/src/types/README.md deleted file mode 100644 index 803b899304..0000000000 --- a/packages/data-schemas/src/types/README.md +++ /dev/null @@ -1,223 +0,0 @@ -# Data Schemas - Refactored Architecture - -This package has been refactored to follow a clean, modular architecture with clear separation of concerns. - -## ๐Ÿ“ Directory Structure - -``` -packages/data-schemas/src/ -โ”œโ”€โ”€ index.ts # Main exports -โ”œโ”€โ”€ schema/ # ๐Ÿ—„๏ธ Mongoose schema definitions -โ”‚ โ”œโ”€โ”€ user.ts -โ”‚ โ”œโ”€โ”€ session.ts -โ”‚ โ””โ”€โ”€ token.ts -โ”œโ”€โ”€ models/ # ๐Ÿ—๏ธ Mongoose model instances -โ”‚ โ””โ”€โ”€ index.ts -โ”œโ”€โ”€ methods/ # โš™๏ธ Business logic functions -โ”‚ โ”œโ”€โ”€ index.ts -โ”‚ โ”œโ”€โ”€ user.ts -โ”‚ โ”œโ”€โ”€ session.ts -โ”‚ โ””โ”€โ”€ token.ts -โ””โ”€โ”€ types/ # ๐Ÿ“‹ TypeScript interfaces & types - โ”œโ”€โ”€ index.ts - โ”œโ”€โ”€ user.ts - โ”œโ”€โ”€ session.ts - โ””โ”€โ”€ token.ts -``` - -## ๐ŸŽฏ Key Benefits - -### 1. **Separation of Concerns** -- **Schema**: Pure Mongoose schema definitions -- **Models**: Model instances created once -- **Methods**: Business logic as pure functions -- **Types**: Shared TypeScript interfaces - -### 2. **No Dynamic Imports** -- Models are created once in the models directory -- Methods import model instances directly -- No more dynamic `import()` calls - -### 3. **Better Type Safety** -- Shared types across all layers -- Proper TypeScript typing throughout -- Clear interfaces for all operations - -### 4. **Pure Functions** -- Methods are now side-effect free -- Easy to test and reason about -- No magic or hidden dependencies - -## ๐Ÿš€ Migration Guide - -### Before (Static Methods) -```typescript -import { User } from 'some-model-registry'; - -// Old way with static methods -const user = await User.findUser({ email: 'test@example.com' }); -const result = await User.deleteUserById(userId); -``` - -### After (Pure Functions) -```typescript -import { findUser, deleteUserById } from '~/methods'; - -// New way with pure functions -const user = await findUser({ email: 'test@example.com' }); -const result = await deleteUserById(userId); -``` - -## ๐Ÿ“š Usage Examples - -### User Operations -```typescript -import { - findUser, - createUser, - updateUser, - deleteUserById, - generateToken -} from '~/methods'; - -// Find a user -const user = await findUser( - { email: 'user@example.com' }, - 'name email role' -); - -// Create a user with balance config -const newUser = await createUser( - { email: 'new@example.com', name: 'John' }, - { enabled: true, startBalance: 100 }, - true, // disable TTL - true // return user object -); - -// Update user -const updated = await updateUser(userId, { name: 'Jane' }); - -// Delete user -const result = await deleteUserById(userId); - -// Generate JWT token -const token = await generateToken(user); -``` - -### Session Operations -```typescript -import { - createSession, - findSession, - deleteSession, - deleteAllUserSessions -} from '~/methods'; - -// Create session -const { session, refreshToken } = await createSession(userId); - -// Find session by refresh token -const foundSession = await findSession({ refreshToken }); - -// Delete specific session -await deleteSession({ sessionId }); - -// Delete all user sessions -await deleteAllUserSessions(userId, { - excludeCurrentSession: true, - currentSessionId -}); -``` - -### Token Operations -```typescript -import { - createToken, - findToken, - updateToken, - deleteTokens -} from '~/methods'; - -// Create token -const token = await createToken({ - userId, - token: 'abc123', - type: 'verification', - expiresIn: 3600 // 1 hour -}); - -// Find token -const foundToken = await findToken({ - token: 'abc123', - type: 'verification' -}); - -// Update token -const updated = await updateToken( - { token: 'abc123' }, - { type: 'password-reset' } -); - -// Delete tokens -await deleteTokens({ userId }); -``` - -## ๐Ÿ”ง Path Aliases - -The project uses `~/` as an alias for `./src/`: - -```typescript -import { IUser } from '~/types'; -import { User } from '~/models'; -import { findUser } from '~/methods'; -import userSchema from '~/schema/user'; -``` - -## โš ๏ธ Breaking Changes - -1. **Static Methods Removed**: All static methods have been removed from schema files -2. **Function Signatures**: Methods no longer take model as first parameter -3. **Import Paths**: Import from `~/methods` instead of calling static methods -4. **Type Definitions**: Types moved to dedicated `~/types` directory - -## ๐Ÿงช Testing - -The new pure function approach makes testing much easier: - -```typescript -import { findUser } from '~/methods'; - -// Easy to mock and test -jest.mock('~/models', () => ({ - User: { - findOne: jest.fn().mockReturnValue({ - select: jest.fn().mockReturnValue({ - lean: jest.fn().mockResolvedValue(mockUser) - }) - }) - } -})); - -test('findUser should return user', async () => { - const result = await findUser({ email: 'test@example.com' }); - expect(result).toEqual(mockUser); -}); -``` - -## ๐Ÿ”„ Error Handling - -All methods include proper error handling with typed errors: - -```typescript -import { SessionError } from '~/types'; - -try { - const session = await createSession(userId); -} catch (error) { - if (error instanceof SessionError) { - console.log('Session error:', error.code, error.message); - } -} -``` - -This refactoring provides a much cleaner, more maintainable, and type-safe architecture for data operations. \ No newline at end of file