From b51cd21b3c204b158e62faa6f43e3d29796b14b7 Mon Sep 17 00:00:00 2001 From: Ruben Talstra Date: Fri, 7 Mar 2025 17:55:44 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=A6=20refactor:=20Move=20DB=20Models?= =?UTF-8?q?=20to=20`@librechat/data-schemas`=20(#6210)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🚀 feat: Introduce data schemas and refactor models to use @librechat/data-schemas * 🚀 feat: Add installation step for Data Schemas Package in backend review workflow * chore: Add `data-schemas` package to update/rebuild packages scripts * chore: Update Dockerfile to include data-schemas package build process * fix: add missing @rollup/plugin-typescript package * chore: Add GitHub Actions workflow for publishing data-schemas package --------- Co-authored-by: Danny Avila --- .github/workflows/backend-review.yml | 3 + .github/workflows/data-schemas.yml | 34 +++ .../generate-release-changelog-pr.yml | 8 +- Dockerfile.multi | 11 +- api/lib/db/indexSync.js | 2 +- api/models/Action.js | 2 +- api/models/Agent.js | 2 +- api/models/Assistant.js | 2 +- api/models/Balance.js | 2 +- api/models/Banner.js | 6 +- api/models/Categories.js | 1 - api/models/ConversationTag.js | 6 +- api/models/File.js | 22 +- api/models/Key.js | 2 +- api/models/Project.js | 14 +- api/models/Prompt.js | 6 +- api/models/Role.js | 6 +- api/models/Role.spec.js | 2 +- api/models/Session.js | 2 +- api/models/Share.js | 4 +- api/models/Token.js | 2 +- api/models/ToolCall.js | 16 +- api/models/Transaction.js | 2 +- api/models/User.js | 2 +- api/models/schema/action.js | 60 ------ api/models/schema/balance.js | 17 -- api/models/schema/categories.js | 19 -- api/models/schema/conversationTagSchema.js | 32 --- api/models/schema/convoSchema.js | 47 +--- api/models/schema/fileSchema.js | 111 ---------- api/models/schema/messageSchema.js | 145 +------------ api/models/schema/pluginAuthSchema.js | 22 +- api/models/schema/presetSchema.js | 33 +-- api/models/schema/projectSchema.js | 35 --- api/models/schema/promptSchema.js | 118 ---------- api/models/schema/session.js | 20 -- api/models/schema/toolCallSchema.js | 54 ----- api/models/schema/userSchema.js | 151 ------------- api/package.json | 1 + api/typedefs.js | 30 --- config/packages.js | 1 + config/update.js | 1 + eslint.config.mjs | 15 +- package-lock.json | 204 ++++++++++++++++++ package.json | 7 +- packages/data-schemas/.gitignore | 2 + packages/data-schemas/babel.config.cjs | 4 + packages/data-schemas/jest.config.mjs | 19 ++ packages/data-schemas/package.json | 67 ++++++ packages/data-schemas/rollup.config.js | 25 +++ packages/data-schemas/src/index.ts | 50 +++++ packages/data-schemas/src/schema/action.ts | 78 +++++++ .../data-schemas/src/schema/agent.ts | 43 +++- .../data-schemas/src/schema/assistant.ts | 22 +- packages/data-schemas/src/schema/balance.ts | 22 ++ .../data-schemas/src/schema/banner.ts | 17 +- .../data-schemas/src/schema/categories.ts | 21 ++ .../src/schema/conversationTag.ts | 41 ++++ packages/data-schemas/src/schema/convo.ts | 101 +++++++++ .../data-schemas/src/schema/defaults.ts | 11 +- packages/data-schemas/src/schema/file.ts | 107 +++++++++ .../data-schemas/src/schema/key.ts | 13 +- packages/data-schemas/src/schema/message.ts | 185 ++++++++++++++++ .../data-schemas/src/schema/pluginAuth.ts | 33 +++ packages/data-schemas/src/schema/preset.ts | 85 ++++++++ packages/data-schemas/src/schema/project.ts | 34 +++ packages/data-schemas/src/schema/prompt.ts | 42 ++++ .../data-schemas/src/schema/promptGroup.ts | 85 ++++++++ .../data-schemas/src/schema/role.ts | 36 +++- packages/data-schemas/src/schema/session.ts | 26 +++ .../data-schemas/src/schema/share.ts | 17 +- .../data-schemas/src/schema/token.ts | 22 +- packages/data-schemas/src/schema/toolCall.ts | 55 +++++ .../data-schemas/src/schema/transaction.ts | 24 ++- packages/data-schemas/src/schema/user.ts | 158 ++++++++++++++ packages/data-schemas/tsconfig.json | 28 +++ packages/data-schemas/tsconfig.spec.json | 10 + 77 files changed, 1781 insertions(+), 984 deletions(-) create mode 100644 .github/workflows/data-schemas.yml delete mode 100644 api/models/schema/action.js delete mode 100644 api/models/schema/balance.js delete mode 100644 api/models/schema/categories.js delete mode 100644 api/models/schema/conversationTagSchema.js delete mode 100644 api/models/schema/fileSchema.js delete mode 100644 api/models/schema/projectSchema.js delete mode 100644 api/models/schema/promptSchema.js delete mode 100644 api/models/schema/session.js delete mode 100644 api/models/schema/toolCallSchema.js delete mode 100644 api/models/schema/userSchema.js create mode 100644 packages/data-schemas/.gitignore create mode 100644 packages/data-schemas/babel.config.cjs create mode 100644 packages/data-schemas/jest.config.mjs create mode 100644 packages/data-schemas/package.json create mode 100644 packages/data-schemas/rollup.config.js create mode 100644 packages/data-schemas/src/index.ts create mode 100644 packages/data-schemas/src/schema/action.ts rename api/models/schema/agent.js => packages/data-schemas/src/schema/agent.ts (59%) rename api/models/schema/assistant.js => packages/data-schemas/src/schema/assistant.ts (57%) create mode 100644 packages/data-schemas/src/schema/balance.ts rename api/models/schema/banner.js => packages/data-schemas/src/schema/banner.ts (61%) create mode 100644 packages/data-schemas/src/schema/categories.ts create mode 100644 packages/data-schemas/src/schema/conversationTag.ts create mode 100644 packages/data-schemas/src/schema/convo.ts rename api/models/schema/defaults.js => packages/data-schemas/src/schema/defaults.ts (91%) create mode 100644 packages/data-schemas/src/schema/file.ts rename api/models/schema/key.js => packages/data-schemas/src/schema/key.ts (54%) create mode 100644 packages/data-schemas/src/schema/message.ts create mode 100644 packages/data-schemas/src/schema/pluginAuth.ts create mode 100644 packages/data-schemas/src/schema/preset.ts create mode 100644 packages/data-schemas/src/schema/project.ts create mode 100644 packages/data-schemas/src/schema/prompt.ts create mode 100644 packages/data-schemas/src/schema/promptGroup.ts rename api/models/schema/roleSchema.js => packages/data-schemas/src/schema/role.ts (56%) create mode 100644 packages/data-schemas/src/schema/session.ts rename api/models/schema/shareSchema.js => packages/data-schemas/src/schema/share.ts (53%) rename api/models/schema/tokenSchema.js => packages/data-schemas/src/schema/token.ts (55%) create mode 100644 packages/data-schemas/src/schema/toolCall.ts rename api/models/schema/transaction.js => packages/data-schemas/src/schema/transaction.ts (54%) create mode 100644 packages/data-schemas/src/schema/user.ts create mode 100644 packages/data-schemas/tsconfig.json create mode 100644 packages/data-schemas/tsconfig.spec.json diff --git a/.github/workflows/backend-review.yml b/.github/workflows/backend-review.yml index 8469fc366d..b7bccecae8 100644 --- a/.github/workflows/backend-review.yml +++ b/.github/workflows/backend-review.yml @@ -39,6 +39,9 @@ jobs: - name: Install MCP Package run: npm run build:mcp + - name: Install Data Schemas Package + run: npm run build:data-schemas + - name: Create empty auth.json file run: | mkdir -p api/data diff --git a/.github/workflows/data-schemas.yml b/.github/workflows/data-schemas.yml new file mode 100644 index 0000000000..4cead540eb --- /dev/null +++ b/.github/workflows/data-schemas.yml @@ -0,0 +1,34 @@ +name: Node.js Package + +on: + push: + branches: + - main + paths: + - 'packages/data-schemas/package.json' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 16 + - run: cd packages/data-schemas && npm ci + - run: cd packages/data-schemas && npm run build + + publish-npm: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 16 + registry-url: 'https://registry.npmjs.org' + - run: cd packages/data-schemas && npm ci + - run: cd packages/data-schemas && npm run build + - run: cd packages/data-schemas && npm publish + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} diff --git a/.github/workflows/generate-release-changelog-pr.yml b/.github/workflows/generate-release-changelog-pr.yml index c3bceae9de..004431e577 100644 --- a/.github/workflows/generate-release-changelog-pr.yml +++ b/.github/workflows/generate-release-changelog-pr.yml @@ -84,11 +84,11 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} sign-commits: true - commit-message: "chore: update CHANGELOG for release ${GITHUB_REF##*/}" + commit-message: "chore: update CHANGELOG for release ${{ github.ref_name }}" base: main - branch: "changelog/${GITHUB_REF##*/}" + branch: "changelog/${{ github.ref_name }}" reviewers: danny-avila - title: "chore: update CHANGELOG for release ${GITHUB_REF##*/}" + title: "chore: update CHANGELOG for release ${{ github.ref_name }}" body: | **Description**: - - This PR updates the CHANGELOG.md by removing the "Unreleased" section and adding new release notes for release ${GITHUB_REF##*/} above previous releases. \ No newline at end of file + - This PR updates the CHANGELOG.md by removing the "Unreleased" section and adding new release notes for release ${{ github.ref_name }} above previous releases. \ No newline at end of file diff --git a/Dockerfile.multi b/Dockerfile.multi index 796849b75d..40721137bb 100644 --- a/Dockerfile.multi +++ b/Dockerfile.multi @@ -11,6 +11,7 @@ RUN npm config set fetch-retry-maxtimeout 600000 && \ COPY package*.json ./ COPY packages/data-provider/package*.json ./packages/data-provider/ COPY packages/mcp/package*.json ./packages/mcp/ +COPY packages/data-schemas/package*.json ./packages/data-schemas/ COPY client/package*.json ./client/ COPY api/package*.json ./api/ @@ -32,6 +33,13 @@ COPY packages/mcp ./ COPY --from=data-provider-build /app/packages/data-provider/dist /app/packages/data-provider/dist RUN npm run build +# Build data-schemas +FROM base AS data-schemas-build +WORKDIR /app/packages/data-schemas +COPY packages/data-schemas ./ +COPY --from=data-provider-build /app/packages/data-provider/dist /app/packages/data-provider/dist +RUN npm run build + # Client build FROM base AS client-build WORKDIR /app/client @@ -49,8 +57,9 @@ COPY api ./api COPY config ./config COPY --from=data-provider-build /app/packages/data-provider/dist ./packages/data-provider/dist COPY --from=mcp-build /app/packages/mcp/dist ./packages/mcp/dist +COPY --from=data-schemas-build /app/packages/data-schemas/dist ./packages/data-schemas/dist COPY --from=client-build /app/client/dist ./client/dist WORKDIR /app/api EXPOSE 3080 ENV HOST=0.0.0.0 -CMD ["node", "server/index.js"] +CMD ["node", "server/index.js"] \ No newline at end of file diff --git a/api/lib/db/indexSync.js b/api/lib/db/indexSync.js index 9c40e684d3..3c10cc20e4 100644 --- a/api/lib/db/indexSync.js +++ b/api/lib/db/indexSync.js @@ -1,5 +1,5 @@ const { MeiliSearch } = require('meilisearch'); -const Conversation = require('~/models/schema/convoSchema'); +const Conversation = require('~/models/Conversation'); const Message = require('~/models/schema/messageSchema'); const { isEnabled } = require('~/server/utils'); const { logger } = require('~/config'); diff --git a/api/models/Action.js b/api/models/Action.js index 299b3bf20a..677b4d78df 100644 --- a/api/models/Action.js +++ b/api/models/Action.js @@ -1,5 +1,5 @@ const mongoose = require('mongoose'); -const actionSchema = require('./schema/action'); +const { actionSchema } = require('@librechat/data-schemas'); const Action = mongoose.model('action', actionSchema); diff --git a/api/models/Agent.js b/api/models/Agent.js index 6ea203113c..1d3ea5af0c 100644 --- a/api/models/Agent.js +++ b/api/models/Agent.js @@ -9,7 +9,7 @@ const { removeAgentFromAllProjects, } = require('./Project'); const getLogStores = require('~/cache/getLogStores'); -const agentSchema = require('./schema/agent'); +const { agentSchema } = require('@librechat/data-schemas'); const Agent = mongoose.model('agent', agentSchema); diff --git a/api/models/Assistant.js b/api/models/Assistant.js index d0e73ad4e7..a8a5b98157 100644 --- a/api/models/Assistant.js +++ b/api/models/Assistant.js @@ -1,5 +1,5 @@ const mongoose = require('mongoose'); -const assistantSchema = require('./schema/assistant'); +const { assistantSchema } = require('@librechat/data-schemas'); const Assistant = mongoose.model('assistant', assistantSchema); diff --git a/api/models/Balance.js b/api/models/Balance.js index 24d9087b77..f7978d8049 100644 --- a/api/models/Balance.js +++ b/api/models/Balance.js @@ -1,5 +1,5 @@ const mongoose = require('mongoose'); -const balanceSchema = require('./schema/balance'); +const { balanceSchema } = require('@librechat/data-schemas'); const { getMultiplier } = require('./tx'); const { logger } = require('~/config'); diff --git a/api/models/Banner.js b/api/models/Banner.js index 8d439dae28..0f20faeba8 100644 --- a/api/models/Banner.js +++ b/api/models/Banner.js @@ -1,5 +1,9 @@ -const Banner = require('./schema/banner'); +const mongoose = require('mongoose'); const logger = require('~/config/winston'); +const { bannerSchema } = require('@librechat/data-schemas'); + +const Banner = mongoose.model('Banner', bannerSchema); + /** * Retrieves the current active banner. * @returns {Promise} The active banner object or null if no active banner is found. diff --git a/api/models/Categories.js b/api/models/Categories.js index 6fb88fb995..5da1f4b2da 100644 --- a/api/models/Categories.js +++ b/api/models/Categories.js @@ -1,5 +1,4 @@ const { logger } = require('~/config'); -// const { Categories } = require('./schema/categories'); const options = [ { diff --git a/api/models/ConversationTag.js b/api/models/ConversationTag.js index 53d144e1f5..f0cac8620e 100644 --- a/api/models/ConversationTag.js +++ b/api/models/ConversationTag.js @@ -1,7 +1,11 @@ -const ConversationTag = require('./schema/conversationTagSchema'); +const mongoose = require('mongoose'); const Conversation = require('./schema/convoSchema'); const logger = require('~/config/winston'); +const { conversationTagSchema } = require('@librechat/data-schemas'); + +const ConversationTag = mongoose.model('ConversationTag', conversationTagSchema); + /** * Retrieves all conversation tags for a user. * @param {string} user - The user ID. diff --git a/api/models/File.js b/api/models/File.js index 17f8506600..870a18a7c8 100644 --- a/api/models/File.js +++ b/api/models/File.js @@ -1,5 +1,5 @@ const mongoose = require('mongoose'); -const fileSchema = require('./schema/fileSchema'); +const { fileSchema } = require('@librechat/data-schemas'); const File = mongoose.model('File', fileSchema); @@ -7,7 +7,7 @@ const File = mongoose.model('File', fileSchema); * Finds a file by its file_id with additional query options. * @param {string} file_id - The unique identifier of the file. * @param {object} options - Query options for filtering, projection, etc. - * @returns {Promise} A promise that resolves to the file document or null. + * @returns {Promise} A promise that resolves to the file document or null. */ const findFileById = async (file_id, options = {}) => { return await File.findOne({ file_id, ...options }).lean(); @@ -17,7 +17,7 @@ const findFileById = async (file_id, options = {}) => { * Retrieves files matching a given filter, sorted by the most recently updated. * @param {Object} filter - The filter criteria to apply. * @param {Object} [_sortOptions] - Optional sort parameters. - * @returns {Promise>} A promise that resolves to an array of file documents. + * @returns {Promise>} A promise that resolves to an array of file documents. */ const getFiles = async (filter, _sortOptions) => { const sortOptions = { updatedAt: -1, ..._sortOptions }; @@ -26,9 +26,9 @@ const getFiles = async (filter, _sortOptions) => { /** * Creates a new file with a TTL of 1 hour. - * @param {MongoFile} data - The file data to be created, must contain file_id. + * @param {IMongoFile} data - The file data to be created, must contain file_id. * @param {boolean} disableTTL - Whether to disable the TTL. - * @returns {Promise} A promise that resolves to the created file document. + * @returns {Promise} A promise that resolves to the created file document. */ const createFile = async (data, disableTTL) => { const fileData = { @@ -48,8 +48,8 @@ const createFile = async (data, disableTTL) => { /** * Updates a file identified by file_id with new data and removes the TTL. - * @param {MongoFile} data - The data to update, must contain file_id. - * @returns {Promise} A promise that resolves to the updated file document. + * @param {IMongoFile} data - The data to update, must contain file_id. + * @returns {Promise} A promise that resolves to the updated file document. */ const updateFile = async (data) => { const { file_id, ...update } = data; @@ -62,8 +62,8 @@ const updateFile = async (data) => { /** * Increments the usage of a file identified by file_id. - * @param {MongoFile} data - The data to update, must contain file_id and the increment value for usage. - * @returns {Promise} A promise that resolves to the updated file document. + * @param {IMongoFile} data - The data to update, must contain file_id and the increment value for usage. + * @returns {Promise} A promise that resolves to the updated file document. */ const updateFileUsage = async (data) => { const { file_id, inc = 1 } = data; @@ -77,7 +77,7 @@ const updateFileUsage = async (data) => { /** * Deletes a file identified by file_id. * @param {string} file_id - The unique identifier of the file to delete. - * @returns {Promise} A promise that resolves to the deleted file document or null. + * @returns {Promise} A promise that resolves to the deleted file document or null. */ const deleteFile = async (file_id) => { return await File.findOneAndDelete({ file_id }).lean(); @@ -86,7 +86,7 @@ const deleteFile = async (file_id) => { /** * Deletes a file identified by a filter. * @param {object} filter - The filter criteria to apply. - * @returns {Promise} A promise that resolves to the deleted file document or null. + * @returns {Promise} A promise that resolves to the deleted file document or null. */ const deleteFileByFilter = async (filter) => { return await File.findOneAndDelete(filter).lean(); diff --git a/api/models/Key.js b/api/models/Key.js index 58fb0ac3a9..c69c350a42 100644 --- a/api/models/Key.js +++ b/api/models/Key.js @@ -1,4 +1,4 @@ const mongoose = require('mongoose'); -const keySchema = require('./schema/key'); +const { keySchema } = require('@librechat/data-schemas'); module.exports = mongoose.model('Key', keySchema); diff --git a/api/models/Project.js b/api/models/Project.js index 17ef3093a5..43d7263723 100644 --- a/api/models/Project.js +++ b/api/models/Project.js @@ -1,6 +1,6 @@ const { model } = require('mongoose'); const { GLOBAL_PROJECT_NAME } = require('librechat-data-provider').Constants; -const projectSchema = require('~/models/schema/projectSchema'); +const { projectSchema } = require('@librechat/data-schemas'); const Project = model('Project', projectSchema); @@ -9,7 +9,7 @@ const Project = model('Project', projectSchema); * * @param {string} projectId - The ID of the project to find and return as a plain object. * @param {string|string[]} [fieldsToSelect] - The fields to include or exclude in the returned document. - * @returns {Promise} A plain object representing the project document, or `null` if no project is found. + * @returns {Promise} A plain object representing the project document, or `null` if no project is found. */ const getProjectById = async function (projectId, fieldsToSelect = null) { const query = Project.findById(projectId); @@ -27,7 +27,7 @@ const getProjectById = async function (projectId, fieldsToSelect = null) { * * @param {string} projectName - The name of the project to find or create. * @param {string|string[]} [fieldsToSelect] - The fields to include or exclude in the returned document. - * @returns {Promise} A plain object representing the project document. + * @returns {Promise} A plain object representing the project document. */ const getProjectByName = async function (projectName, fieldsToSelect = null) { const query = { name: projectName }; @@ -47,7 +47,7 @@ const getProjectByName = async function (projectName, fieldsToSelect = null) { * * @param {string} projectId - The ID of the project to update. * @param {string[]} promptGroupIds - The array of prompt group IDs to add to the project. - * @returns {Promise} The updated project document. + * @returns {Promise} The updated project document. */ const addGroupIdsToProject = async function (projectId, promptGroupIds) { return await Project.findByIdAndUpdate( @@ -62,7 +62,7 @@ const addGroupIdsToProject = async function (projectId, promptGroupIds) { * * @param {string} projectId - The ID of the project to update. * @param {string[]} promptGroupIds - The array of prompt group IDs to remove from the project. - * @returns {Promise} The updated project document. + * @returns {Promise} The updated project document. */ const removeGroupIdsFromProject = async function (projectId, promptGroupIds) { return await Project.findByIdAndUpdate( @@ -87,7 +87,7 @@ const removeGroupFromAllProjects = async (promptGroupId) => { * * @param {string} projectId - The ID of the project to update. * @param {string[]} agentIds - The array of agent IDs to add to the project. - * @returns {Promise} The updated project document. + * @returns {Promise} The updated project document. */ const addAgentIdsToProject = async function (projectId, agentIds) { return await Project.findByIdAndUpdate( @@ -102,7 +102,7 @@ const addAgentIdsToProject = async function (projectId, agentIds) { * * @param {string} projectId - The ID of the project to update. * @param {string[]} agentIds - The array of agent IDs to remove from the project. - * @returns {Promise} The updated project document. + * @returns {Promise} The updated project document. */ const removeAgentIdsFromProject = async function (projectId, agentIds) { return await Project.findByIdAndUpdate( diff --git a/api/models/Prompt.js b/api/models/Prompt.js index 60456884a8..43dc3ec22b 100644 --- a/api/models/Prompt.js +++ b/api/models/Prompt.js @@ -1,3 +1,4 @@ +const mongoose = require('mongoose'); const { ObjectId } = require('mongodb'); const { SystemRoles, SystemCategories, Constants } = require('librechat-data-provider'); const { @@ -6,10 +7,13 @@ const { removeGroupIdsFromProject, removeGroupFromAllProjects, } = require('./Project'); -const { Prompt, PromptGroup } = require('./schema/promptSchema'); +const { promptGroupSchema, promptSchema } = require('@librechat/data-schemas'); const { escapeRegExp } = require('~/server/utils'); const { logger } = require('~/config'); +const PromptGroup = mongoose.model('PromptGroup', promptGroupSchema); +const Prompt = mongoose.model('Prompt', promptSchema); + /** * Create a pipeline for the aggregation to get prompt groups * @param {Object} query diff --git a/api/models/Role.js b/api/models/Role.js index 3f02570718..4be5faeadb 100644 --- a/api/models/Role.js +++ b/api/models/Role.js @@ -1,3 +1,4 @@ +const mongoose = require('mongoose'); const { CacheKeys, SystemRoles, @@ -12,9 +13,11 @@ const { temporaryChatPermissionsSchema, } = require('librechat-data-provider'); const getLogStores = require('~/cache/getLogStores'); -const Role = require('~/models/schema/roleSchema'); +const { roleSchema } = require('@librechat/data-schemas'); const { logger } = require('~/config'); +const Role = mongoose.model('Role', roleSchema); + /** * Retrieve a role by name and convert the found role document to a plain object. * If the role with the given name doesn't exist and the name is a system defined role, create it and return the lean version. @@ -168,6 +171,7 @@ const initializeRoles = async function () { } }; module.exports = { + Role, getRoleByName, initializeRoles, updateRoleByName, diff --git a/api/models/Role.spec.js b/api/models/Role.spec.js index 92386f0fa9..39611f7b95 100644 --- a/api/models/Role.spec.js +++ b/api/models/Role.spec.js @@ -8,7 +8,7 @@ const { } = require('librechat-data-provider'); const { updateAccessPermissions, initializeRoles } = require('~/models/Role'); const getLogStores = require('~/cache/getLogStores'); -const Role = require('~/models/schema/roleSchema'); +const { Role } = require('~/models/Role'); // Mock the cache jest.mock('~/cache/getLogStores', () => { diff --git a/api/models/Session.js b/api/models/Session.js index dbb66ed8ff..38821b77dd 100644 --- a/api/models/Session.js +++ b/api/models/Session.js @@ -1,7 +1,7 @@ const mongoose = require('mongoose'); const signPayload = require('~/server/services/signPayload'); const { hashToken } = require('~/server/utils/crypto'); -const sessionSchema = require('./schema/session'); +const { sessionSchema } = require('@librechat/data-schemas'); const { logger } = require('~/config'); const Session = mongoose.model('Session', sessionSchema); diff --git a/api/models/Share.js b/api/models/Share.js index 041927ec61..a8bfbce7fe 100644 --- a/api/models/Share.js +++ b/api/models/Share.js @@ -1,7 +1,9 @@ +const mongoose = require('mongoose'); const { nanoid } = require('nanoid'); const { Constants } = require('librechat-data-provider'); const { Conversation } = require('~/models/Conversation'); -const SharedLink = require('./schema/shareSchema'); +const { shareSchema } = require('@librechat/data-schemas'); +const SharedLink = mongoose.model('SharedLink', shareSchema); const { getMessages } = require('./Message'); const logger = require('~/config/winston'); diff --git a/api/models/Token.js b/api/models/Token.js index 0ed18320ae..c89abb8c84 100644 --- a/api/models/Token.js +++ b/api/models/Token.js @@ -1,6 +1,6 @@ const mongoose = require('mongoose'); const { encryptV2 } = require('~/server/utils/crypto'); -const tokenSchema = require('./schema/tokenSchema'); +const { tokenSchema } = require('@librechat/data-schemas'); const { logger } = require('~/config'); /** diff --git a/api/models/ToolCall.js b/api/models/ToolCall.js index e1d7b0cc84..7bc0f157dc 100644 --- a/api/models/ToolCall.js +++ b/api/models/ToolCall.js @@ -1,9 +1,11 @@ -const ToolCall = require('./schema/toolCallSchema'); +const mongoose = require('mongoose'); +const { toolCallSchema } = require('@librechat/data-schemas'); +const ToolCall = mongoose.model('ToolCall', toolCallSchema); /** * Create a new tool call - * @param {ToolCallData} toolCallData - The tool call data - * @returns {Promise} The created tool call document + * @param {IToolCallData} toolCallData - The tool call data + * @returns {Promise} The created tool call document */ async function createToolCall(toolCallData) { try { @@ -16,7 +18,7 @@ async function createToolCall(toolCallData) { /** * Get a tool call by ID * @param {string} id - The tool call document ID - * @returns {Promise} The tool call document or null if not found + * @returns {Promise} The tool call document or null if not found */ async function getToolCallById(id) { try { @@ -44,7 +46,7 @@ async function getToolCallsByMessage(messageId, userId) { * Get tool calls by conversation ID and user * @param {string} conversationId - The conversation ID * @param {string} userId - The user's ObjectId - * @returns {Promise} Array of tool call documents + * @returns {Promise} Array of tool call documents */ async function getToolCallsByConvo(conversationId, userId) { try { @@ -57,8 +59,8 @@ async function getToolCallsByConvo(conversationId, userId) { /** * Update a tool call * @param {string} id - The tool call document ID - * @param {Partial} updateData - The data to update - * @returns {Promise} The updated tool call document or null if not found + * @param {Partial} updateData - The data to update + * @returns {Promise} The updated tool call document or null if not found */ async function updateToolCall(id, updateData) { try { diff --git a/api/models/Transaction.js b/api/models/Transaction.js index 8435a812c4..b1c4c65710 100644 --- a/api/models/Transaction.js +++ b/api/models/Transaction.js @@ -1,6 +1,6 @@ const mongoose = require('mongoose'); const { isEnabled } = require('~/server/utils/handleText'); -const transactionSchema = require('./schema/transaction'); +const { transactionSchema } = require('@librechat/data-schemas'); const { getMultiplier, getCacheMultiplier } = require('./tx'); const { logger } = require('~/config'); const Balance = require('./Balance'); diff --git a/api/models/User.js b/api/models/User.js index 55750b4ae5..f4e8b0ec5b 100644 --- a/api/models/User.js +++ b/api/models/User.js @@ -1,5 +1,5 @@ const mongoose = require('mongoose'); -const userSchema = require('~/models/schema/userSchema'); +const { userSchema } = require('@librechat/data-schemas'); const User = mongoose.model('User', userSchema); diff --git a/api/models/schema/action.js b/api/models/schema/action.js deleted file mode 100644 index f86a9bfa2d..0000000000 --- a/api/models/schema/action.js +++ /dev/null @@ -1,60 +0,0 @@ -const mongoose = require('mongoose'); - -const { Schema } = mongoose; - -const AuthSchema = new Schema( - { - authorization_type: String, - custom_auth_header: String, - type: { - type: String, - enum: ['service_http', 'oauth', 'none'], - }, - authorization_content_type: String, - authorization_url: String, - client_url: String, - scope: String, - token_exchange_method: { - type: String, - enum: ['default_post', 'basic_auth_header', null], - }, - }, - { _id: false }, -); - -const actionSchema = new Schema({ - user: { - type: mongoose.Schema.Types.ObjectId, - ref: 'User', - index: true, - required: true, - }, - action_id: { - type: String, - index: true, - required: true, - }, - type: { - type: String, - default: 'action_prototype', - }, - settings: Schema.Types.Mixed, - agent_id: String, - assistant_id: String, - metadata: { - api_key: String, // private, encrypted - auth: AuthSchema, - domain: { - type: String, - required: true, - }, - // json_schema: Schema.Types.Mixed, - privacy_policy_url: String, - raw_spec: String, - oauth_client_id: String, // private, encrypted - oauth_client_secret: String, // private, encrypted - }, -}); -// }, { minimize: false }); // Prevent removal of empty objects - -module.exports = actionSchema; diff --git a/api/models/schema/balance.js b/api/models/schema/balance.js deleted file mode 100644 index 8ca8116e09..0000000000 --- a/api/models/schema/balance.js +++ /dev/null @@ -1,17 +0,0 @@ -const mongoose = require('mongoose'); - -const balanceSchema = mongoose.Schema({ - user: { - type: mongoose.Schema.Types.ObjectId, - ref: 'User', - index: true, - required: true, - }, - // 1000 tokenCredits = 1 mill ($0.001 USD) - tokenCredits: { - type: Number, - default: 0, - }, -}); - -module.exports = balanceSchema; diff --git a/api/models/schema/categories.js b/api/models/schema/categories.js deleted file mode 100644 index 3167685667..0000000000 --- a/api/models/schema/categories.js +++ /dev/null @@ -1,19 +0,0 @@ -const mongoose = require('mongoose'); -const Schema = mongoose.Schema; - -const categoriesSchema = new Schema({ - label: { - type: String, - required: true, - unique: true, - }, - value: { - type: String, - required: true, - unique: true, - }, -}); - -const categories = mongoose.model('categories', categoriesSchema); - -module.exports = { Categories: categories }; diff --git a/api/models/schema/conversationTagSchema.js b/api/models/schema/conversationTagSchema.js deleted file mode 100644 index 9b2a98c6d8..0000000000 --- a/api/models/schema/conversationTagSchema.js +++ /dev/null @@ -1,32 +0,0 @@ -const mongoose = require('mongoose'); - -const conversationTagSchema = mongoose.Schema( - { - tag: { - type: String, - index: true, - }, - user: { - type: String, - index: true, - }, - description: { - type: String, - index: true, - }, - count: { - type: Number, - default: 0, - }, - position: { - type: Number, - default: 0, - index: true, - }, - }, - { timestamps: true }, -); - -conversationTagSchema.index({ tag: 1, user: 1 }, { unique: true }); - -module.exports = mongoose.model('ConversationTag', conversationTagSchema); diff --git a/api/models/schema/convoSchema.js b/api/models/schema/convoSchema.js index ae50b7cd22..89cb9c80b5 100644 --- a/api/models/schema/convoSchema.js +++ b/api/models/schema/convoSchema.js @@ -1,46 +1,7 @@ const mongoose = require('mongoose'); const mongoMeili = require('../plugins/mongoMeili'); -const { conversationPreset } = require('./defaults'); -const convoSchema = mongoose.Schema( - { - conversationId: { - type: String, - unique: true, - required: true, - index: true, - meiliIndex: true, - }, - title: { - type: String, - default: 'New Chat', - meiliIndex: true, - }, - user: { - type: String, - index: true, - }, - messages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Message' }], - agentOptions: { - type: mongoose.Schema.Types.Mixed, - }, - ...conversationPreset, - agent_id: { - type: String, - }, - tags: { - type: [String], - default: [], - meiliIndex: true, - }, - files: { - type: [String], - }, - expiredAt: { - type: Date, - }, - }, - { timestamps: true }, -); + +const { convoSchema } = require('@librechat/data-schemas'); if (process.env.MEILI_HOST && process.env.MEILI_MASTER_KEY) { convoSchema.plugin(mongoMeili, { @@ -52,10 +13,6 @@ if (process.env.MEILI_HOST && process.env.MEILI_MASTER_KEY) { }); } -convoSchema.index({ expiredAt: 1 }, { expireAfterSeconds: 0 }); -convoSchema.index({ createdAt: 1, updatedAt: 1 }); -convoSchema.index({ conversationId: 1, user: 1 }, { unique: true }); - const Conversation = mongoose.models.Conversation || mongoose.model('Conversation', convoSchema); module.exports = Conversation; diff --git a/api/models/schema/fileSchema.js b/api/models/schema/fileSchema.js deleted file mode 100644 index 77c6ff94d4..0000000000 --- a/api/models/schema/fileSchema.js +++ /dev/null @@ -1,111 +0,0 @@ -const { FileSources } = require('librechat-data-provider'); -const mongoose = require('mongoose'); - -/** - * @typedef {Object} MongoFile - * @property {ObjectId} [_id] - MongoDB Document ID - * @property {number} [__v] - MongoDB Version Key - * @property {ObjectId} user - User ID - * @property {string} [conversationId] - Optional conversation ID - * @property {string} file_id - File identifier - * @property {string} [temp_file_id] - Temporary File identifier - * @property {number} bytes - Size of the file in bytes - * @property {string} filename - Name of the file - * @property {string} filepath - Location of the file - * @property {'file'} object - Type of object, always 'file' - * @property {string} type - Type of file - * @property {number} [usage=0] - Number of uses of the file - * @property {string} [context] - Context of the file origin - * @property {boolean} [embedded=false] - Whether or not the file is embedded in vector db - * @property {string} [model] - The model to identify the group region of the file (for Azure OpenAI hosting) - * @property {string} [source] - The source of the file (e.g., from FileSources) - * @property {number} [width] - Optional width of the file - * @property {number} [height] - Optional height of the file - * @property {Object} [metadata] - Metadata related to the file - * @property {string} [metadata.fileIdentifier] - Unique identifier for the file in metadata - * @property {Date} [expiresAt] - Optional expiration date of the file - * @property {Date} [createdAt] - Date when the file was created - * @property {Date} [updatedAt] - Date when the file was updated - */ - -/** @type {MongooseSchema} */ -const fileSchema = mongoose.Schema( - { - user: { - type: mongoose.Schema.Types.ObjectId, - ref: 'User', - index: true, - required: true, - }, - conversationId: { - type: String, - ref: 'Conversation', - index: true, - }, - file_id: { - type: String, - // required: true, - index: true, - }, - temp_file_id: { - type: String, - // required: true, - }, - bytes: { - type: Number, - required: true, - }, - filename: { - type: String, - required: true, - }, - filepath: { - type: String, - required: true, - }, - object: { - type: String, - required: true, - default: 'file', - }, - embedded: { - type: Boolean, - }, - type: { - type: String, - required: true, - }, - context: { - type: String, - // required: true, - }, - usage: { - type: Number, - required: true, - default: 0, - }, - source: { - type: String, - default: FileSources.local, - }, - model: { - type: String, - }, - width: Number, - height: Number, - metadata: { - fileIdentifier: String, - }, - expiresAt: { - type: Date, - expires: 3600, // 1 hour in seconds - }, - }, - { - timestamps: true, - }, -); - -fileSchema.index({ createdAt: 1, updatedAt: 1 }); - -module.exports = fileSchema; diff --git a/api/models/schema/messageSchema.js b/api/models/schema/messageSchema.js index be71155295..cf97b84eea 100644 --- a/api/models/schema/messageSchema.js +++ b/api/models/schema/messageSchema.js @@ -1,145 +1,6 @@ const mongoose = require('mongoose'); const mongoMeili = require('~/models/plugins/mongoMeili'); -const messageSchema = mongoose.Schema( - { - messageId: { - type: String, - unique: true, - required: true, - index: true, - meiliIndex: true, - }, - conversationId: { - type: String, - index: true, - required: true, - meiliIndex: true, - }, - user: { - type: String, - index: true, - required: true, - default: null, - }, - model: { - type: String, - default: null, - }, - endpoint: { - type: String, - }, - conversationSignature: { - type: String, - }, - clientId: { - type: String, - }, - invocationId: { - type: Number, - }, - parentMessageId: { - type: String, - }, - tokenCount: { - type: Number, - }, - summaryTokenCount: { - type: Number, - }, - sender: { - type: String, - meiliIndex: true, - }, - text: { - type: String, - meiliIndex: true, - }, - summary: { - type: String, - }, - isCreatedByUser: { - type: Boolean, - required: true, - default: false, - }, - unfinished: { - type: Boolean, - default: false, - }, - error: { - type: Boolean, - default: false, - }, - finish_reason: { - type: String, - }, - _meiliIndex: { - type: Boolean, - required: false, - select: false, - default: false, - }, - files: { type: [{ type: mongoose.Schema.Types.Mixed }], default: undefined }, - plugin: { - type: { - latest: { - type: String, - required: false, - }, - inputs: { - type: [mongoose.Schema.Types.Mixed], - required: false, - default: undefined, - }, - outputs: { - type: String, - required: false, - }, - }, - default: undefined, - }, - plugins: { type: [{ type: mongoose.Schema.Types.Mixed }], default: undefined }, - content: { - type: [{ type: mongoose.Schema.Types.Mixed }], - default: undefined, - meiliIndex: true, - }, - thread_id: { - type: String, - }, - /* frontend components */ - iconURL: { - type: String, - }, - attachments: { type: [{ type: mongoose.Schema.Types.Mixed }], default: undefined }, - /* - attachments: { - type: [ - { - file_id: String, - filename: String, - filepath: String, - expiresAt: Date, - width: Number, - height: Number, - type: String, - conversationId: String, - messageId: { - type: String, - required: true, - }, - toolCallId: String, - }, - ], - default: undefined, - }, - */ - expiredAt: { - type: Date, - }, - }, - { timestamps: true }, -); +const { messageSchema } = require('@librechat/data-schemas'); if (process.env.MEILI_HOST && process.env.MEILI_MASTER_KEY) { messageSchema.plugin(mongoMeili, { @@ -149,11 +10,7 @@ if (process.env.MEILI_HOST && process.env.MEILI_MASTER_KEY) { primaryKey: 'messageId', }); } -messageSchema.index({ expiredAt: 1 }, { expireAfterSeconds: 0 }); -messageSchema.index({ createdAt: 1 }); -messageSchema.index({ messageId: 1, user: 1 }, { unique: true }); -/** @type {mongoose.Model} */ const Message = mongoose.models.Message || mongoose.model('Message', messageSchema); module.exports = Message; diff --git a/api/models/schema/pluginAuthSchema.js b/api/models/schema/pluginAuthSchema.js index 4b4251dda3..2066eda4c4 100644 --- a/api/models/schema/pluginAuthSchema.js +++ b/api/models/schema/pluginAuthSchema.js @@ -1,25 +1,5 @@ const mongoose = require('mongoose'); - -const pluginAuthSchema = mongoose.Schema( - { - authField: { - type: String, - required: true, - }, - value: { - type: String, - required: true, - }, - userId: { - type: String, - required: true, - }, - pluginKey: { - type: String, - }, - }, - { timestamps: true }, -); +const { pluginAuthSchema } = require('@librechat/data-schemas'); const PluginAuth = mongoose.models.Plugin || mongoose.model('PluginAuth', pluginAuthSchema); diff --git a/api/models/schema/presetSchema.js b/api/models/schema/presetSchema.js index 918e5c4069..6d03803ace 100644 --- a/api/models/schema/presetSchema.js +++ b/api/models/schema/presetSchema.js @@ -1,36 +1,5 @@ const mongoose = require('mongoose'); -const { conversationPreset } = require('./defaults'); -const presetSchema = mongoose.Schema( - { - presetId: { - type: String, - unique: true, - required: true, - index: true, - }, - title: { - type: String, - default: 'New Chat', - meiliIndex: true, - }, - user: { - type: String, - default: null, - }, - defaultPreset: { - type: Boolean, - }, - order: { - type: Number, - }, - ...conversationPreset, - agentOptions: { - type: mongoose.Schema.Types.Mixed, - default: null, - }, - }, - { timestamps: true }, -); +const { presetSchema } = require('@librechat/data-schemas'); const Preset = mongoose.models.Preset || mongoose.model('Preset', presetSchema); diff --git a/api/models/schema/projectSchema.js b/api/models/schema/projectSchema.js deleted file mode 100644 index dfa68a06c2..0000000000 --- a/api/models/schema/projectSchema.js +++ /dev/null @@ -1,35 +0,0 @@ -const { Schema } = require('mongoose'); - -/** - * @typedef {Object} MongoProject - * @property {ObjectId} [_id] - MongoDB Document ID - * @property {string} name - The name of the project - * @property {ObjectId[]} promptGroupIds - Array of PromptGroup IDs associated with the project - * @property {Date} [createdAt] - Date when the project was created (added by timestamps) - * @property {Date} [updatedAt] - Date when the project was last updated (added by timestamps) - */ - -const projectSchema = new Schema( - { - name: { - type: String, - required: true, - index: true, - }, - promptGroupIds: { - type: [Schema.Types.ObjectId], - ref: 'PromptGroup', - default: [], - }, - agentIds: { - type: [String], - ref: 'Agent', - default: [], - }, - }, - { - timestamps: true, - }, -); - -module.exports = projectSchema; diff --git a/api/models/schema/promptSchema.js b/api/models/schema/promptSchema.js deleted file mode 100644 index 5464caf639..0000000000 --- a/api/models/schema/promptSchema.js +++ /dev/null @@ -1,118 +0,0 @@ -const mongoose = require('mongoose'); -const { Constants } = require('librechat-data-provider'); -const Schema = mongoose.Schema; - -/** - * @typedef {Object} MongoPromptGroup - * @property {ObjectId} [_id] - MongoDB Document ID - * @property {string} name - The name of the prompt group - * @property {ObjectId} author - The author of the prompt group - * @property {ObjectId} [projectId=null] - The project ID of the prompt group - * @property {ObjectId} [productionId=null] - The project ID of the prompt group - * @property {string} authorName - The name of the author of the prompt group - * @property {number} [numberOfGenerations=0] - Number of generations the prompt group has - * @property {string} [oneliner=''] - Oneliner description of the prompt group - * @property {string} [category=''] - Category of the prompt group - * @property {string} [command] - Command for the prompt group - * @property {Date} [createdAt] - Date when the prompt group was created (added by timestamps) - * @property {Date} [updatedAt] - Date when the prompt group was last updated (added by timestamps) - */ - -const promptGroupSchema = new Schema( - { - name: { - type: String, - required: true, - index: true, - }, - numberOfGenerations: { - type: Number, - default: 0, - }, - oneliner: { - type: String, - default: '', - }, - category: { - type: String, - default: '', - index: true, - }, - projectIds: { - type: [Schema.Types.ObjectId], - ref: 'Project', - index: true, - }, - productionId: { - type: Schema.Types.ObjectId, - ref: 'Prompt', - required: true, - index: true, - }, - author: { - type: Schema.Types.ObjectId, - ref: 'User', - required: true, - index: true, - }, - authorName: { - type: String, - required: true, - }, - command: { - type: String, - index: true, - validate: { - validator: function (v) { - return v === undefined || v === null || v === '' || /^[a-z0-9-]+$/.test(v); - }, - message: (props) => - `${props.value} is not a valid command. Only lowercase alphanumeric characters and highfins (') are allowed.`, - }, - maxlength: [ - Constants.COMMANDS_MAX_LENGTH, - `Command cannot be longer than ${Constants.COMMANDS_MAX_LENGTH} characters`, - ], - }, - }, - { - timestamps: true, - }, -); - -const PromptGroup = mongoose.model('PromptGroup', promptGroupSchema); - -const promptSchema = new Schema( - { - groupId: { - type: Schema.Types.ObjectId, - ref: 'PromptGroup', - required: true, - index: true, - }, - author: { - type: Schema.Types.ObjectId, - ref: 'User', - required: true, - }, - prompt: { - type: String, - required: true, - }, - type: { - type: String, - enum: ['text', 'chat'], - required: true, - }, - }, - { - timestamps: true, - }, -); - -const Prompt = mongoose.model('Prompt', promptSchema); - -promptSchema.index({ createdAt: 1, updatedAt: 1 }); -promptGroupSchema.index({ createdAt: 1, updatedAt: 1 }); - -module.exports = { Prompt, PromptGroup }; diff --git a/api/models/schema/session.js b/api/models/schema/session.js deleted file mode 100644 index ccda43573d..0000000000 --- a/api/models/schema/session.js +++ /dev/null @@ -1,20 +0,0 @@ -const mongoose = require('mongoose'); - -const sessionSchema = mongoose.Schema({ - refreshTokenHash: { - type: String, - required: true, - }, - expiration: { - type: Date, - required: true, - expires: 0, - }, - user: { - type: mongoose.Schema.Types.ObjectId, - ref: 'User', - required: true, - }, -}); - -module.exports = sessionSchema; diff --git a/api/models/schema/toolCallSchema.js b/api/models/schema/toolCallSchema.js deleted file mode 100644 index 2af4c67c1b..0000000000 --- a/api/models/schema/toolCallSchema.js +++ /dev/null @@ -1,54 +0,0 @@ -const mongoose = require('mongoose'); - -/** - * @typedef {Object} ToolCallData - * @property {string} conversationId - The ID of the conversation - * @property {string} messageId - The ID of the message - * @property {string} toolId - The ID of the tool - * @property {string | ObjectId} user - The user's ObjectId - * @property {unknown} [result] - Optional result data - * @property {TAttachment[]} [attachments] - Optional attachments data - * @property {number} [blockIndex] - Optional code block index - * @property {number} [partIndex] - Optional part index - */ - -/** @type {MongooseSchema} */ -const toolCallSchema = mongoose.Schema( - { - conversationId: { - type: String, - required: true, - }, - messageId: { - type: String, - required: true, - }, - toolId: { - type: String, - required: true, - }, - user: { - type: mongoose.Schema.Types.ObjectId, - ref: 'User', - required: true, - }, - result: { - type: mongoose.Schema.Types.Mixed, - }, - attachments: { - type: mongoose.Schema.Types.Mixed, - }, - blockIndex: { - type: Number, - }, - partIndex: { - type: Number, - }, - }, - { timestamps: true }, -); - -toolCallSchema.index({ messageId: 1, user: 1 }); -toolCallSchema.index({ conversationId: 1, user: 1 }); - -module.exports = mongoose.model('ToolCall', toolCallSchema); diff --git a/api/models/schema/userSchema.js b/api/models/schema/userSchema.js deleted file mode 100644 index bebc7fea1e..0000000000 --- a/api/models/schema/userSchema.js +++ /dev/null @@ -1,151 +0,0 @@ -const mongoose = require('mongoose'); -const { SystemRoles } = require('librechat-data-provider'); - -/** - * @typedef {Object} MongoSession - * @property {string} [refreshToken] - The refresh token - */ - -/** - * @typedef {Object} MongoUser - * @property {ObjectId} [_id] - MongoDB Document ID - * @property {string} [name] - The user's name - * @property {string} [username] - The user's username, in lowercase - * @property {string} email - The user's email address - * @property {boolean} emailVerified - Whether the user's email is verified - * @property {string} [password] - The user's password, trimmed with 8-128 characters - * @property {string} [avatar] - The URL of the user's avatar - * @property {string} provider - The provider of the user's account (e.g., 'local', 'google') - * @property {string} [role='USER'] - The role of the user - * @property {string} [googleId] - Optional Google ID for the user - * @property {string} [facebookId] - Optional Facebook ID for the user - * @property {string} [openidId] - Optional OpenID ID for the user - * @property {string} [ldapId] - Optional LDAP ID for the user - * @property {string} [githubId] - Optional GitHub ID for the user - * @property {string} [discordId] - Optional Discord ID for the user - * @property {string} [appleId] - Optional Apple ID for the user - * @property {Array} [plugins=[]] - List of plugins used by the user - * @property {Array.} [refreshToken] - List of sessions with refresh tokens - * @property {Date} [expiresAt] - Optional expiration date of the file - * @property {Date} [createdAt] - Date when the user was created (added by timestamps) - * @property {Date} [updatedAt] - Date when the user was last updated (added by timestamps) - */ - -/** @type {MongooseSchema} */ -const Session = mongoose.Schema({ - refreshToken: { - type: String, - default: '', - }, -}); - -const backupCodeSchema = mongoose.Schema({ - codeHash: { type: String, required: true }, - used: { type: Boolean, default: false }, - usedAt: { type: Date, default: null }, -}); - -/** @type {MongooseSchema} */ -const userSchema = mongoose.Schema( - { - name: { - type: String, - }, - username: { - type: String, - lowercase: true, - default: '', - }, - email: { - type: String, - required: [true, 'can\'t be blank'], - lowercase: true, - unique: true, - match: [/\S+@\S+\.\S+/, 'is invalid'], - index: true, - }, - emailVerified: { - type: Boolean, - required: true, - default: false, - }, - password: { - type: String, - trim: true, - minlength: 8, - maxlength: 128, - }, - avatar: { - type: String, - required: false, - }, - provider: { - type: String, - required: true, - default: 'local', - }, - role: { - type: String, - default: SystemRoles.USER, - }, - googleId: { - type: String, - unique: true, - sparse: true, - }, - facebookId: { - type: String, - unique: true, - sparse: true, - }, - openidId: { - type: String, - unique: true, - sparse: true, - }, - ldapId: { - type: String, - unique: true, - sparse: true, - }, - githubId: { - type: String, - unique: true, - sparse: true, - }, - discordId: { - type: String, - unique: true, - sparse: true, - }, - appleId: { - type: String, - unique: true, - sparse: true, - }, - plugins: { - type: Array, - }, - totpSecret: { - type: String, - }, - backupCodes: { - type: [backupCodeSchema], - }, - refreshToken: { - type: [Session], - }, - expiresAt: { - type: Date, - expires: 604800, // 7 days in seconds - }, - termsAccepted: { - type: Boolean, - default: false, - }, - }, - - { timestamps: true }, -); - -module.exports = userSchema; diff --git a/api/package.json b/api/package.json index 68d3d97590..00f4d086db 100644 --- a/api/package.json +++ b/api/package.json @@ -76,6 +76,7 @@ "klona": "^2.0.6", "langchain": "^0.2.19", "librechat-data-provider": "*", + "@librechat/data-schemas": "*", "librechat-mcp": "*", "lodash": "^4.17.21", "meilisearch": "^0.38.0", diff --git a/api/typedefs.js b/api/typedefs.js index cc7ae41895..3045d9543b 100644 --- a/api/typedefs.js +++ b/api/typedefs.js @@ -766,36 +766,6 @@ * @memberof typedefs */ -/** - * @exports MongoFile - * @typedef {import('~/models/schema/fileSchema.js').MongoFile} MongoFile - * @memberof typedefs - */ - -/** - * @exports ToolCallData - * @typedef {import('~/models/schema/toolCallSchema.js').ToolCallData} ToolCallData - * @memberof typedefs - */ - -/** - * @exports MongoUser - * @typedef {import('~/models/schema/userSchema.js').MongoUser} MongoUser - * @memberof typedefs - */ - -/** - * @exports MongoProject - * @typedef {import('~/models/schema/projectSchema.js').MongoProject} MongoProject - * @memberof typedefs - */ - -/** - * @exports MongoPromptGroup - * @typedef {import('~/models/schema/promptSchema.js').MongoPromptGroup} MongoPromptGroup - * @memberof typedefs - */ - /** * @exports uploadImageBuffer * @typedef {import('~/server/services/Files/process').uploadImageBuffer} uploadImageBuffer diff --git a/config/packages.js b/config/packages.js index b09bc6e666..2ca187d5b9 100644 --- a/config/packages.js +++ b/config/packages.js @@ -9,6 +9,7 @@ const rootDir = path.resolve(__dirname, '..'); const directories = [ rootDir, path.resolve(rootDir, 'packages', 'data-provider'), + path.resolve(rootDir, 'packages', 'data-schemas'), path.resolve(rootDir, 'packages', 'mcp'), path.resolve(rootDir, 'client'), path.resolve(rootDir, 'api'), diff --git a/config/update.js b/config/update.js index 0130d81905..55867fedd6 100644 --- a/config/update.js +++ b/config/update.js @@ -16,6 +16,7 @@ const rootDir = path.resolve(__dirname, '..'); const directories = [ rootDir, path.resolve(rootDir, 'packages', 'data-provider'), + path.resolve(rootDir, 'packages', 'data-schemas'), path.resolve(rootDir, 'packages', 'mcp'), path.resolve(rootDir, 'client'), path.resolve(rootDir, 'api'), diff --git a/eslint.config.mjs b/eslint.config.mjs index dc2567e9b9..67e3f24f4d 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -220,9 +220,6 @@ export default [ 'jsx-a11y/interactive-supports-focus': 'off', 'jsx-a11y/no-noninteractive-tabindex': 'off', 'jsx-a11y/img-redundant-alt': 'off', - 'jsx-a11y/media-has-caption': 'off', - 'jsx-a11y/no-autofocus': 'off', - 'jsx-a11y/alt-text': 'off', }, }, { @@ -369,4 +366,16 @@ export default [ }, }, }, + { + // **New Data-schemas configuration block** + files: ['./packages/data-schemas/**/*.ts'], + languageOptions: { + parser: tsParser, + ecmaVersion: 'latest', + sourceType: 'module', + parserOptions: { + project: './packages/data-schemas/tsconfig.json', + }, + }, + }, ]; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 078fc4dba7..14b8f4973f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,6 +62,7 @@ "@langchain/google-vertexai": "^0.2.0", "@langchain/textsplitters": "^0.1.0", "@librechat/agents": "^2.2.0", + "@librechat/data-schemas": "*", "@waylaidwanderer/fetch-event-source": "^3.0.1", "axios": "1.7.8", "bcryptjs": "^2.4.3", @@ -17927,6 +17928,10 @@ "resolved": "api", "link": true }, + "node_modules/@librechat/data-schemas": { + "resolved": "packages/data-schemas", + "link": true + }, "node_modules/@librechat/frontend": { "resolved": "client", "link": true @@ -43691,6 +43696,205 @@ "url": "https://github.com/sponsors/isaacs" } }, + "packages/data-schemas": { + "name": "@librechat/data-schemas", + "version": "0.0.1", + "license": "ISC", + "dependencies": { + "mongoose": "^8.9.5" + }, + "devDependencies": { + "@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": "^4.5.4" + } + }, + "packages/data-schemas/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "packages/data-schemas/node_modules/bson": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.5.1.tgz", + "integrity": "sha512-ix0EwukN2EpC0SRWIj/7B5+A6uQMQy6KMREI9qQqvgpkV2frH63T0UDVd1SYedL6dNCmDBYB3QtXi4ISk9YT+g==", + "license": "Apache-2.0", + "engines": { + "node": ">=14.20.1" + } + }, + "packages/data-schemas/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "packages/data-schemas/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "packages/data-schemas/node_modules/kareem": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz", + "integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==", + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "packages/data-schemas/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "packages/data-schemas/node_modules/mongodb": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.2.tgz", + "integrity": "sha512-H60HecKO4Bc+7dhOv4sJlgvenK4fQNqqUIlXxZYQNbfEWSALGAwGoyJd/0Qwk4TttFXUOHJ2ZJQe/52ScaUwtQ==", + "license": "Apache-2.0", + "dependencies": { + "bson": "^5.5.0", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" + }, + "engines": { + "node": ">=14.20.1" + }, + "optionalDependencies": { + "@mongodb-js/saslprep": "^1.1.0" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.0.0", + "kerberos": "^1.0.0 || ^2.0.0", + "mongodb-client-encryption": ">=2.3.0 <3", + "snappy": "^7.2.2" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + } + } + }, + "packages/data-schemas/node_modules/mongoose": { + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.8.6.tgz", + "integrity": "sha512-1oVPRHvcmPVwk/zeSTEzayzQEVeYQM1D5zrkLsttfNNB7pPRUmkKeFu6gpbvyEswOuZLrWJjqB8kSTY+k2AZOA==", + "license": "MIT", + "dependencies": { + "bson": "^5.5.0", + "kareem": "2.5.1", + "mongodb": "5.9.2", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "16.0.1" + }, + "engines": { + "node": ">=14.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "packages/data-schemas/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "packages/data-schemas/node_modules/sift": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz", + "integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==", + "license": "MIT" + }, "packages/mcp": { "name": "librechat-mcp", "version": "1.1.0", diff --git a/package.json b/package.json index 47f3adbc15..ef46c3d30c 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,8 @@ "backend:stop": "node config/stop-backend.js", "build:data-provider": "cd packages/data-provider && npm run build", "build:mcp": "cd packages/mcp && npm run build", - "frontend": "npm run build:data-provider && npm run build:mcp && cd client && npm run build", + "build:data-schemas": "cd packages/data-schemas && npm run build", + "frontend": "npm run build:data-provider && npm run build:mcp && npm run build:data-schemas && cd client && npm run build", "frontend:ci": "npm run build:data-provider && cd client && npm run build:ci", "frontend:dev": "cd client && npm run dev", "e2e": "playwright test --config=e2e/playwright.config.local.ts", @@ -61,7 +62,9 @@ "b:api-inspect": "NODE_ENV=production bun --inspect run api/server/index.js", "b:api:dev": "NODE_ENV=production bun run --watch api/server/index.js", "b:data": "cd packages/data-provider && bun run b:build", - "b:client": "bun --bun run b:data && cd client && bun --bun run b:build", + "b:mcp": "cd packages/mcp && bun run b:build", + "b:data-schemas": "cd packages/data-schemas && bun run b:build", + "b:client": "bun --bun run b:data && bun --bun run b:mcp && bun --bun run b:data-schemas && cd client && bun --bun run b:build", "b:client:dev": "cd client && bun run b:dev", "b:test:client": "cd client && bun run b:test", "b:test:api": "cd api && bun run b:test", diff --git a/packages/data-schemas/.gitignore b/packages/data-schemas/.gitignore new file mode 100644 index 0000000000..7b961825b3 --- /dev/null +++ b/packages/data-schemas/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +test_bundle/ diff --git a/packages/data-schemas/babel.config.cjs b/packages/data-schemas/babel.config.cjs new file mode 100644 index 0000000000..7d5344d252 --- /dev/null +++ b/packages/data-schemas/babel.config.cjs @@ -0,0 +1,4 @@ +module.exports = { + presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'], + plugins: ['babel-plugin-replace-ts-export-assignment'], +}; diff --git a/packages/data-schemas/jest.config.mjs b/packages/data-schemas/jest.config.mjs new file mode 100644 index 0000000000..f5fb1f20d7 --- /dev/null +++ b/packages/data-schemas/jest.config.mjs @@ -0,0 +1,19 @@ +export default { + collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}', '!/node_modules/'], + coveragePathIgnorePatterns: ['/node_modules/', '/dist/'], + coverageReporters: ['text', 'cobertura'], + testResultsProcessor: 'jest-junit', + moduleNameMapper: { + '^@src/(.*)$': '/src/$1', + }, + // coverageThreshold: { + // global: { + // statements: 58, + // branches: 49, + // functions: 50, + // lines: 57, + // }, + // }, + restoreMocks: true, + testTimeout: 15000, +}; \ No newline at end of file diff --git a/packages/data-schemas/package.json b/packages/data-schemas/package.json new file mode 100644 index 0000000000..b41a16de5b --- /dev/null +++ b/packages/data-schemas/package.json @@ -0,0 +1,67 @@ +{ + "name": "@librechat/data-schemas", + "version": "0.0.1", + "type": "module", + "description": "Mongoose schemas and models for LibreChat", + "main": "dist/index.cjs", + "module": "dist/index.es.js", + "types": "./dist/types/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.es.js", + "require": "./dist/index.cjs", + "types": "./dist/types/index.d.ts" + } + }, + "scripts": { + "clean": "rimraf dist", + "build": "npm run clean && rollup -c --silent --bundleConfigAsCjs", + "build:watch": "rollup -c -w", + "test": "jest --coverage --watch", + "test:ci": "jest --coverage --ci", + "verify": "npm run test:ci", + "b:clean": "bun run rimraf dist", + "b:build": "bun run b:clean && bun run rollup -c --silent --bundleConfigAsCjs" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/danny-avila/LibreChat.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/danny-avila/LibreChat/issues" + }, + "homepage": "https://librechat.ai", + "devDependencies": { + "@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" + }, + "publishConfig": { + "registry": "https://registry.npmjs.org/" + }, + "dependencies": { + "mongoose": "^8.9.5" + }, + "peerDependencies": { + "keyv": "^4.5.4" + } +} diff --git a/packages/data-schemas/rollup.config.js b/packages/data-schemas/rollup.config.js new file mode 100644 index 0000000000..8b27ce81cb --- /dev/null +++ b/packages/data-schemas/rollup.config.js @@ -0,0 +1,25 @@ +import json from '@rollup/plugin-json'; +import typescript from '@rollup/plugin-typescript'; +import commonjs from '@rollup/plugin-commonjs'; + +export default { + input: 'src/index.ts', + output: [ + { + file: 'dist/index.cjs', // Changed from index.js to index.cjs + format: 'cjs', + sourcemap: true, + exports: 'named', + }, + { + file: 'dist/index.es.js', + format: 'esm', + sourcemap: true, + exports: 'named', + }, + ], + plugins: [json(), commonjs(), typescript({ tsconfig: './tsconfig.json' })], + external: [ + // list your external dependencies + ], +}; diff --git a/packages/data-schemas/src/index.ts b/packages/data-schemas/src/index.ts new file mode 100644 index 0000000000..33fa916f32 --- /dev/null +++ b/packages/data-schemas/src/index.ts @@ -0,0 +1,50 @@ +// index.ts +import actionSchema from './schema/action'; +import agentSchema from './schema/agent'; +import assistantSchema from './schema/assistant'; +import balanceSchema from './schema/balance'; +import bannerSchema from './schema/banner'; +import categoriesSchema from './schema/categories'; +import conversationTagSchema from './schema/conversationTag'; +import convoSchema from './schema/convo'; +import fileSchema from './schema/file'; +import keySchema from './schema/key'; +import messageSchema from './schema/message'; +import pluginAuthSchema from './schema/pluginAuth'; +import presetSchema from './schema/preset'; +import projectSchema from './schema/project'; +import promptSchema from './schema/prompt'; +import promptGroupSchema from './schema/promptGroup'; +import roleSchema from './schema/role'; +import sessionSchema from './schema/session'; +import shareSchema from './schema/share'; +import tokenSchema from './schema/token'; +import toolCallSchema from './schema/toolCall'; +import transactionSchema from './schema/transaction'; +import userSchema from './schema/user'; + +export { + actionSchema, + agentSchema, + assistantSchema, + balanceSchema, + bannerSchema, + categoriesSchema, + conversationTagSchema, + convoSchema, + fileSchema, + keySchema, + messageSchema, + pluginAuthSchema, + presetSchema, + projectSchema, + promptSchema, + promptGroupSchema, + roleSchema, + sessionSchema, + shareSchema, + tokenSchema, + toolCallSchema, + transactionSchema, + userSchema, +}; diff --git a/packages/data-schemas/src/schema/action.ts b/packages/data-schemas/src/schema/action.ts new file mode 100644 index 0000000000..b47ea5f0fa --- /dev/null +++ b/packages/data-schemas/src/schema/action.ts @@ -0,0 +1,78 @@ +import mongoose, { Schema, Document } from 'mongoose'; + +export interface IAction extends Document { + user: mongoose.Types.ObjectId; + action_id: string; + type: string; + settings?: unknown; + agent_id?: string; + assistant_id?: string; + metadata: { + api_key?: string; + auth: { + authorization_type?: string; + custom_auth_header?: string; + type: 'service_http' | 'oauth' | 'none'; + authorization_content_type?: string; + authorization_url?: string; + client_url?: string; + scope?: string; + token_exchange_method: 'default_post' | 'basic_auth_header' | null; + }; + domain: string; + privacy_policy_url?: string; + raw_spec?: string; + oauth_client_id?: string; + oauth_client_secret?: string; + }; +} + +// Define the Auth sub-schema with type-safety. +const AuthSchema = new Schema( + { + authorization_type: { type: String }, + custom_auth_header: { type: String }, + type: { type: String, enum: ['service_http', 'oauth', 'none'] }, + authorization_content_type: { type: String }, + authorization_url: { type: String }, + client_url: { type: String }, + scope: { type: String }, + token_exchange_method: { type: String, enum: ['default_post', 'basic_auth_header', null] }, + }, + { _id: false }, +); + +const Action = new Schema({ + user: { + type: mongoose.Schema.Types.ObjectId, + ref: 'User', + index: true, + required: true, + }, + action_id: { + type: String, + index: true, + required: true, + }, + type: { + type: String, + default: 'action_prototype', + }, + settings: Schema.Types.Mixed, + agent_id: String, + assistant_id: String, + metadata: { + api_key: String, + auth: AuthSchema, + domain: { + type: String, + required: true, + }, + privacy_policy_url: String, + raw_spec: String, + oauth_client_id: String, + oauth_client_secret: String, + }, +}); + +export default Action; diff --git a/api/models/schema/agent.js b/packages/data-schemas/src/schema/agent.ts similarity index 59% rename from api/models/schema/agent.js rename to packages/data-schemas/src/schema/agent.ts index 53e49e1cfd..809dc7b0f0 100644 --- a/api/models/schema/agent.js +++ b/packages/data-schemas/src/schema/agent.ts @@ -1,6 +1,35 @@ -const mongoose = require('mongoose'); +import { Schema, Document, Types } from 'mongoose'; -const agentSchema = mongoose.Schema( +// @ts-ignore +export interface IAgent extends Omit { + id: string; + name?: string; + description?: string; + instructions?: string; + avatar?: { + filepath: string; + source: string; + }; + provider: string; + model: string; + model_parameters?: Record; + artifacts?: string; + access_level?: number; + tools?: string[]; + tool_kwargs?: Array; + actions?: string[]; + author: Types.ObjectId; + authorName?: string; + hide_sequential_outputs?: boolean; + end_after_tools?: boolean; + agent_ids?: string[]; + isCollaborative?: boolean; + conversation_starters?: string[]; + tool_resources?: unknown; + projectIds?: Types.ObjectId[]; +} + +const agentSchema = new Schema( { id: { type: String, @@ -46,14 +75,14 @@ const agentSchema = mongoose.Schema( default: undefined, }, tool_kwargs: { - type: [{ type: mongoose.Schema.Types.Mixed }], + type: [{ type: Schema.Types.Mixed }], }, actions: { type: [String], default: undefined, }, author: { - type: mongoose.Schema.Types.ObjectId, + type: Schema.Types.ObjectId, ref: 'User', required: true, }, @@ -79,11 +108,11 @@ const agentSchema = mongoose.Schema( default: [], }, tool_resources: { - type: mongoose.Schema.Types.Mixed, + type: Schema.Types.Mixed, default: {}, }, projectIds: { - type: [mongoose.Schema.Types.ObjectId], + type: [Schema.Types.ObjectId], ref: 'Project', index: true, }, @@ -93,4 +122,4 @@ const agentSchema = mongoose.Schema( }, ); -module.exports = agentSchema; +export default agentSchema; diff --git a/api/models/schema/assistant.js b/packages/data-schemas/src/schema/assistant.ts similarity index 57% rename from api/models/schema/assistant.js rename to packages/data-schemas/src/schema/assistant.ts index 46150fd2a8..b67fcf3a76 100644 --- a/api/models/schema/assistant.js +++ b/packages/data-schemas/src/schema/assistant.ts @@ -1,9 +1,23 @@ -const mongoose = require('mongoose'); +import { Schema, Document, Types } from 'mongoose'; -const assistantSchema = mongoose.Schema( +export interface IAssistant extends Document { + user: Types.ObjectId; + assistant_id: string; + avatar?: { + filepath: string; + source: string; + }; + conversation_starters?: string[]; + access_level?: number; + file_ids?: string[]; + actions?: string[]; + append_current_datetime?: boolean; +} + +const assistantSchema = new Schema( { user: { - type: mongoose.Schema.Types.ObjectId, + type: Schema.Types.ObjectId, ref: 'User', required: true, }, @@ -38,4 +52,4 @@ const assistantSchema = mongoose.Schema( }, ); -module.exports = assistantSchema; +export default assistantSchema; diff --git a/packages/data-schemas/src/schema/balance.ts b/packages/data-schemas/src/schema/balance.ts new file mode 100644 index 0000000000..0878a57601 --- /dev/null +++ b/packages/data-schemas/src/schema/balance.ts @@ -0,0 +1,22 @@ +import { Schema, Document, Types } from 'mongoose'; + +export interface IBalance extends Document { + user: Types.ObjectId; + tokenCredits: number; +} + +const balanceSchema = new Schema({ + user: { + type: Schema.Types.ObjectId, + ref: 'User', + index: true, + required: true, + }, + // 1000 tokenCredits = 1 mill ($0.001 USD) + tokenCredits: { + type: Number, + default: 0, + }, +}); + +export default balanceSchema; diff --git a/api/models/schema/banner.js b/packages/data-schemas/src/schema/banner.ts similarity index 61% rename from api/models/schema/banner.js rename to packages/data-schemas/src/schema/banner.ts index 7fd86c1b67..0b4d218344 100644 --- a/api/models/schema/banner.js +++ b/packages/data-schemas/src/schema/banner.ts @@ -1,6 +1,15 @@ -const mongoose = require('mongoose'); +import { Schema, Document } from 'mongoose'; -const bannerSchema = mongoose.Schema( +export interface IBanner extends Document { + bannerId: string; + message: string; + displayFrom: Date; + displayTo?: Date; + type: 'banner' | 'popup'; + isPublic: boolean; +} + +const bannerSchema = new Schema( { bannerId: { type: String, @@ -28,9 +37,7 @@ const bannerSchema = mongoose.Schema( default: false, }, }, - { timestamps: true }, ); -const Banner = mongoose.model('Banner', bannerSchema); -module.exports = Banner; +export default bannerSchema; diff --git a/packages/data-schemas/src/schema/categories.ts b/packages/data-schemas/src/schema/categories.ts new file mode 100644 index 0000000000..5ebd2afb83 --- /dev/null +++ b/packages/data-schemas/src/schema/categories.ts @@ -0,0 +1,21 @@ +import { Schema, Document } from 'mongoose'; + +export interface ICategory extends Document { + label: string; + value: string; +} + +const categoriesSchema = new Schema({ + label: { + type: String, + required: true, + unique: true, + }, + value: { + type: String, + required: true, + unique: true, + }, +}); + +export default categoriesSchema; diff --git a/packages/data-schemas/src/schema/conversationTag.ts b/packages/data-schemas/src/schema/conversationTag.ts new file mode 100644 index 0000000000..e22231fdc2 --- /dev/null +++ b/packages/data-schemas/src/schema/conversationTag.ts @@ -0,0 +1,41 @@ +import { Schema, Document } from 'mongoose'; + +export interface IConversationTag extends Document { + tag?: string; + user?: string; + description?: string; + count?: number; + position?: number; +} + +const conversationTag = new Schema( + { + tag: { + type: String, + index: true, + }, + user: { + type: String, + index: true, + }, + description: { + type: String, + index: true, + }, + count: { + type: Number, + default: 0, + }, + position: { + type: Number, + default: 0, + index: true, + }, + }, + { timestamps: true }, +); + +// Create a compound index on tag and user with unique constraint. +conversationTag.index({ tag: 1, user: 1 }, { unique: true }); + +export default conversationTag; diff --git a/packages/data-schemas/src/schema/convo.ts b/packages/data-schemas/src/schema/convo.ts new file mode 100644 index 0000000000..44a7a6affd --- /dev/null +++ b/packages/data-schemas/src/schema/convo.ts @@ -0,0 +1,101 @@ +import mongoose, { Schema, Document, Types } from 'mongoose'; +import { conversationPreset } from './defaults'; + +// @ts-ignore +export interface IConversation extends Document { + conversationId: string; + title?: string; + user?: string; + messages?: Types.ObjectId[]; + agentOptions?: unknown; + // Fields provided by conversationPreset (adjust types as needed) + endpoint?: string; + endpointType?: string; + model?: string; + region?: string; + chatGptLabel?: string; + examples?: unknown[]; + modelLabel?: string; + promptPrefix?: string; + temperature?: number; + top_p?: number; + topP?: number; + topK?: number; + maxOutputTokens?: number; + maxTokens?: number; + presence_penalty?: number; + frequency_penalty?: number; + file_ids?: string[]; + resendImages?: boolean; + promptCache?: boolean; + thinking?: boolean; + thinkingBudget?: number; + system?: string; + resendFiles?: boolean; + imageDetail?: string; + agent_id?: string; + assistant_id?: string; + instructions?: string; + stop?: string[]; + isArchived?: boolean; + iconURL?: string; + greeting?: string; + spec?: string; + tags?: string[]; + tools?: string[]; + maxContextTokens?: number; + max_tokens?: number; + reasoning_effort?: string; + // Additional fields + files?: string[]; + expiredAt?: Date; + createdAt?: Date; + updatedAt?: Date; +} + +const convoSchema: Schema = new Schema( + { + conversationId: { + type: String, + unique: true, + required: true, + index: true, + meiliIndex: true, + }, + title: { + type: String, + default: 'New Chat', + meiliIndex: true, + }, + user: { + type: String, + index: true, + }, + messages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Message' }], + agentOptions: { + type: mongoose.Schema.Types.Mixed, + }, + ...conversationPreset, + agent_id: { + type: String, + }, + tags: { + type: [String], + default: [], + meiliIndex: true, + }, + files: { + type: [String], + }, + expiredAt: { + type: Date, + }, + }, + { timestamps: true }, +); + +convoSchema.index({ expiredAt: 1 }, { expireAfterSeconds: 0 }); +convoSchema.index({ createdAt: 1, updatedAt: 1 }); +convoSchema.index({ conversationId: 1, user: 1 }, { unique: true }); + +export default convoSchema; diff --git a/api/models/schema/defaults.js b/packages/data-schemas/src/schema/defaults.ts similarity index 91% rename from api/models/schema/defaults.js rename to packages/data-schemas/src/schema/defaults.ts index be2af7fb49..d6e8ed851c 100644 --- a/api/models/schema/defaults.js +++ b/packages/data-schemas/src/schema/defaults.ts @@ -1,6 +1,7 @@ -const mongoose = require('mongoose'); +import { Schema } from 'mongoose'; -const conversationPreset = { +// @ts-ignore +export const conversationPreset = { // endpoint: [azureOpenAI, openAI, anthropic, chatGPTBrowser] endpoint: { type: String, @@ -26,7 +27,7 @@ const conversationPreset = { required: false, }, // for google only - examples: { type: [{ type: mongoose.Schema.Types.Mixed }], default: undefined }, + examples: { type: [{ type: Schema.Types.Mixed }], default: undefined }, modelLabel: { type: String, required: false, @@ -135,7 +136,3 @@ const conversationPreset = { type: String, }, }; - -module.exports = { - conversationPreset, -}; diff --git a/packages/data-schemas/src/schema/file.ts b/packages/data-schemas/src/schema/file.ts new file mode 100644 index 0000000000..b9fddd82d5 --- /dev/null +++ b/packages/data-schemas/src/schema/file.ts @@ -0,0 +1,107 @@ +import mongoose, { Schema, Document, Types } from 'mongoose'; +import { FileSources } from 'librechat-data-provider'; + +// @ts-ignore +export interface IMongoFile extends Document { + user: Types.ObjectId; + conversationId?: string; + file_id: string; + temp_file_id?: string; + bytes: number; + filename: string; + filepath: string; + object: 'file'; + embedded?: boolean; + type: string; + context?: string; + usage: number; + source: string; + model?: string; + width?: number; + height?: number; + metadata?: { + fileIdentifier?: string; + }; + expiresAt?: Date; + createdAt?: Date; + updatedAt?: Date; +} + +const file: Schema = new Schema( + { + user: { + type: mongoose.Schema.Types.ObjectId, + ref: 'User', + index: true, + required: true, + }, + conversationId: { + type: String, + ref: 'Conversation', + index: true, + }, + file_id: { + type: String, + index: true, + required: true, + }, + temp_file_id: { + type: String, + }, + bytes: { + type: Number, + required: true, + }, + filename: { + type: String, + required: true, + }, + filepath: { + type: String, + required: true, + }, + object: { + type: String, + required: true, + default: 'file', + }, + embedded: { + type: Boolean, + }, + type: { + type: String, + required: true, + }, + context: { + type: String, + }, + usage: { + type: Number, + required: true, + default: 0, + }, + source: { + type: String, + default: FileSources.local, + }, + model: { + type: String, + }, + width: Number, + height: Number, + metadata: { + fileIdentifier: String, + }, + expiresAt: { + type: Date, + expires: 3600, // 1 hour in seconds + }, + }, + { + timestamps: true, + }, +); + +file.index({ createdAt: 1, updatedAt: 1 }); + +export default file; diff --git a/api/models/schema/key.js b/packages/data-schemas/src/schema/key.ts similarity index 54% rename from api/models/schema/key.js rename to packages/data-schemas/src/schema/key.ts index 513d66ce1c..54857db753 100644 --- a/api/models/schema/key.js +++ b/packages/data-schemas/src/schema/key.ts @@ -1,6 +1,13 @@ -const mongoose = require('mongoose'); +import mongoose, { Schema, Document, Types } from 'mongoose'; -const keySchema = mongoose.Schema({ +export interface IKey extends Document { + userId: Types.ObjectId; + name: string; + value: string; + expiresAt?: Date; +} + +const keySchema: Schema = new Schema({ userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', @@ -21,4 +28,4 @@ const keySchema = mongoose.Schema({ keySchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 }); -module.exports = keySchema; +export default keySchema; diff --git a/packages/data-schemas/src/schema/message.ts b/packages/data-schemas/src/schema/message.ts new file mode 100644 index 0000000000..82970de6de --- /dev/null +++ b/packages/data-schemas/src/schema/message.ts @@ -0,0 +1,185 @@ +import mongoose, { Schema, Document } from 'mongoose'; + +// @ts-ignore +export interface IMessage extends Document { + messageId: string; + conversationId: string; + user: string; + model?: string; + endpoint?: string; + conversationSignature?: string; + clientId?: string; + invocationId?: number; + parentMessageId?: string; + tokenCount?: number; + summaryTokenCount?: number; + sender?: string; + text?: string; + summary?: string; + isCreatedByUser: boolean; + unfinished?: boolean; + error?: boolean; + finish_reason?: string; + _meiliIndex?: boolean; + files?: unknown[]; + plugin?: { + latest?: string; + inputs?: unknown[]; + outputs?: string; + }; + plugins?: unknown[]; + content?: unknown[]; + thread_id?: string; + iconURL?: string; + attachments?: unknown[]; + expiredAt?: Date; + createdAt?: Date; + updatedAt?: Date; +} + +const messageSchema: Schema = new Schema( + { + messageId: { + type: String, + unique: true, + required: true, + index: true, + meiliIndex: true, + }, + conversationId: { + type: String, + index: true, + required: true, + meiliIndex: true, + }, + user: { + type: String, + index: true, + required: true, + default: null, + }, + model: { + type: String, + default: null, + }, + endpoint: { + type: String, + }, + conversationSignature: { + type: String, + }, + clientId: { + type: String, + }, + invocationId: { + type: Number, + }, + parentMessageId: { + type: String, + }, + tokenCount: { + type: Number, + }, + summaryTokenCount: { + type: Number, + }, + sender: { + type: String, + meiliIndex: true, + }, + text: { + type: String, + meiliIndex: true, + }, + summary: { + type: String, + }, + isCreatedByUser: { + type: Boolean, + required: true, + default: false, + }, + unfinished: { + type: Boolean, + default: false, + }, + error: { + type: Boolean, + default: false, + }, + finish_reason: { + type: String, + }, + _meiliIndex: { + type: Boolean, + required: false, + select: false, + default: false, + }, + files: { type: [{ type: mongoose.Schema.Types.Mixed }], default: undefined }, + plugin: { + type: { + latest: { + type: String, + required: false, + }, + inputs: { + type: [mongoose.Schema.Types.Mixed], + required: false, + default: undefined, + }, + outputs: { + type: String, + required: false, + }, + }, + default: undefined, + }, + plugins: { type: [{ type: mongoose.Schema.Types.Mixed }], default: undefined }, + content: { + type: [{ type: mongoose.Schema.Types.Mixed }], + default: undefined, + meiliIndex: true, + }, + thread_id: { + type: String, + }, + /* frontend components */ + iconURL: { + type: String, + }, + attachments: { type: [{ type: mongoose.Schema.Types.Mixed }], default: undefined }, + /* + attachments: { + type: [ + { + file_id: String, + filename: String, + filepath: String, + expiresAt: Date, + width: Number, + height: Number, + type: String, + conversationId: String, + messageId: { + type: String, + required: true, + }, + toolCallId: String, + }, + ], + default: undefined, + }, + */ + expiredAt: { + type: Date, + }, + }, + { timestamps: true }, +); + +messageSchema.index({ expiredAt: 1 }, { expireAfterSeconds: 0 }); +messageSchema.index({ createdAt: 1 }); +messageSchema.index({ messageId: 1, user: 1 }, { unique: true }); + +export default messageSchema; diff --git a/packages/data-schemas/src/schema/pluginAuth.ts b/packages/data-schemas/src/schema/pluginAuth.ts new file mode 100644 index 0000000000..5c29024453 --- /dev/null +++ b/packages/data-schemas/src/schema/pluginAuth.ts @@ -0,0 +1,33 @@ +import { Schema, Document } from 'mongoose'; + +export interface IPluginAuth extends Document { + authField: string; + value: string; + userId: string; + pluginKey?: string; + createdAt?: Date; + updatedAt?: Date; +} + +const pluginAuthSchema: Schema = new Schema( + { + authField: { + type: String, + required: true, + }, + value: { + type: String, + required: true, + }, + userId: { + type: String, + required: true, + }, + pluginKey: { + type: String, + }, + }, + { timestamps: true }, +); + +export default pluginAuthSchema; diff --git a/packages/data-schemas/src/schema/preset.ts b/packages/data-schemas/src/schema/preset.ts new file mode 100644 index 0000000000..95f1e276d9 --- /dev/null +++ b/packages/data-schemas/src/schema/preset.ts @@ -0,0 +1,85 @@ +import mongoose, { Schema, Document } from 'mongoose'; +import { conversationPreset } from './defaults'; + +// @ts-ignore +export interface IPreset extends Document { + presetId: string; + title: string; + user: string | null; + defaultPreset?: boolean; + order?: number; + // Additional fields from conversationPreset and others will be available via an index signature. + endpoint?: string; + endpointType?: string; + model?: string; + region?: string; + chatGptLabel?: string; + examples?: unknown[]; + modelLabel?: string; + promptPrefix?: string; + temperature?: number; + top_p?: number; + topP?: number; + topK?: number; + maxOutputTokens?: number; + maxTokens?: number; + presence_penalty?: number; + frequency_penalty?: number; + file_ids?: string[]; + resendImages?: boolean; + promptCache?: boolean; + thinking?: boolean; + thinkingBudget?: number; + system?: string; + resendFiles?: boolean; + imageDetail?: string; + agent_id?: string; + assistant_id?: string; + instructions?: string; + stop?: string[]; + isArchived?: boolean; + iconURL?: string; + greeting?: string; + spec?: string; + tags?: string[]; + tools?: string[]; + maxContextTokens?: number; + max_tokens?: number; + reasoning_effort?: string; + // end of additional fields + agentOptions?: unknown; +} + +const presetSchema: Schema = new Schema( + { + presetId: { + type: String, + unique: true, + required: true, + index: true, + }, + title: { + type: String, + default: 'New Chat', + meiliIndex: true, + }, + user: { + type: String, + default: null, + }, + defaultPreset: { + type: Boolean, + }, + order: { + type: Number, + }, + ...conversationPreset, + agentOptions: { + type: mongoose.Schema.Types.Mixed, + default: null, + }, + }, + { timestamps: true }, +); + +export default presetSchema; diff --git a/packages/data-schemas/src/schema/project.ts b/packages/data-schemas/src/schema/project.ts new file mode 100644 index 0000000000..05c2ddc6f2 --- /dev/null +++ b/packages/data-schemas/src/schema/project.ts @@ -0,0 +1,34 @@ +import { Schema, Document, Types } from 'mongoose'; + +export interface IMongoProject extends Document { + name: string; + promptGroupIds: Types.ObjectId[]; + agentIds: string[]; + createdAt?: Date; + updatedAt?: Date; +} + +const projectSchema = new Schema( + { + name: { + type: String, + required: true, + index: true, + }, + promptGroupIds: { + type: [Schema.Types.ObjectId], + ref: 'PromptGroup', + default: [], + }, + agentIds: { + type: [String], + ref: 'Agent', + default: [], + }, + }, + { + timestamps: true, + }, +); + +export default projectSchema; diff --git a/packages/data-schemas/src/schema/prompt.ts b/packages/data-schemas/src/schema/prompt.ts new file mode 100644 index 0000000000..eef966c9c7 --- /dev/null +++ b/packages/data-schemas/src/schema/prompt.ts @@ -0,0 +1,42 @@ +import { Schema, Document, Types } from 'mongoose'; + +export interface IPrompt extends Document { + groupId: Types.ObjectId; + author: Types.ObjectId; + prompt: string; + type: 'text' | 'chat'; + createdAt?: Date; + updatedAt?: Date; +} + +const promptSchema: Schema = new Schema( + { + groupId: { + type: Schema.Types.ObjectId, + ref: 'PromptGroup', + required: true, + index: true, + }, + author: { + type: Schema.Types.ObjectId, + ref: 'User', + required: true, + }, + prompt: { + type: String, + required: true, + }, + type: { + type: String, + enum: ['text', 'chat'], + required: true, + }, + }, + { + timestamps: true, + }, +); + +promptSchema.index({ createdAt: 1, updatedAt: 1 }); + +export default promptSchema; diff --git a/packages/data-schemas/src/schema/promptGroup.ts b/packages/data-schemas/src/schema/promptGroup.ts new file mode 100644 index 0000000000..b13a83724f --- /dev/null +++ b/packages/data-schemas/src/schema/promptGroup.ts @@ -0,0 +1,85 @@ +import { Schema, Document, Types } from 'mongoose'; +import { Constants } from 'librechat-data-provider'; + +export interface IPromptGroup { + name: string; + numberOfGenerations: number; + oneliner: string; + category: string; + projectIds: Types.ObjectId[]; + productionId: Types.ObjectId; + author: Types.ObjectId; + authorName: string; + command?: string; + createdAt?: Date; + updatedAt?: Date; +} + +export interface IPromptGroupDocument extends IPromptGroup, Document {} + +const promptGroupSchema = new Schema( + { + name: { + type: String, + required: true, + index: true, + }, + numberOfGenerations: { + type: Number, + default: 0, + }, + oneliner: { + type: String, + default: '', + }, + category: { + type: String, + default: '', + index: true, + }, + projectIds: { + type: [Schema.Types.ObjectId], + ref: 'Project', + index: true, + default: [], + }, + productionId: { + type: Schema.Types.ObjectId, + ref: 'Prompt', + required: true, + index: true, + }, + author: { + type: Schema.Types.ObjectId, + ref: 'User', + required: true, + index: true, + }, + authorName: { + type: String, + required: true, + }, + command: { + type: String, + index: true, + validate: { + validator: function (v: unknown): boolean { + return v === undefined || v === null || v === '' || /^[a-z0-9-]+$/.test(v); + }, + message: (props: unknown) => + `${props.value} is not a valid command. Only lowercase alphanumeric characters and hyphens are allowed.`, + }, + maxlength: [ + Constants.COMMANDS_MAX_LENGTH as number, + `Command cannot be longer than ${Constants.COMMANDS_MAX_LENGTH} characters`, + ], + }, // Casting here bypasses the type error for the command field. + }, + { + timestamps: true, + }, +); + +promptGroupSchema.index({ createdAt: 1, updatedAt: 1 }); + +export default promptGroupSchema; diff --git a/api/models/schema/roleSchema.js b/packages/data-schemas/src/schema/role.ts similarity index 56% rename from api/models/schema/roleSchema.js rename to packages/data-schemas/src/schema/role.ts index 5e6729bd88..9d3dd4c681 100644 --- a/api/models/schema/roleSchema.js +++ b/packages/data-schemas/src/schema/role.ts @@ -1,7 +1,33 @@ -const { PermissionTypes, Permissions } = require('librechat-data-provider'); -const mongoose = require('mongoose'); +import { Schema, Document } from 'mongoose'; +import { PermissionTypes, Permissions } from 'librechat-data-provider'; -const roleSchema = new mongoose.Schema({ +export interface IRole extends Document { + name: string; + [PermissionTypes.BOOKMARKS]?: { + [Permissions.USE]?: boolean; + }; + [PermissionTypes.PROMPTS]?: { + [Permissions.SHARED_GLOBAL]?: boolean; + [Permissions.USE]?: boolean; + [Permissions.CREATE]?: boolean; + }; + [PermissionTypes.AGENTS]?: { + [Permissions.SHARED_GLOBAL]?: boolean; + [Permissions.USE]?: boolean; + [Permissions.CREATE]?: boolean; + }; + [PermissionTypes.MULTI_CONVO]?: { + [Permissions.USE]?: boolean; + }; + [PermissionTypes.TEMPORARY_CHAT]?: { + [Permissions.USE]?: boolean; + }; + [PermissionTypes.RUN_CODE]?: { + [Permissions.USE]?: boolean; + }; +} + +const roleSchema: Schema = new Schema({ name: { type: String, required: true, @@ -62,6 +88,4 @@ const roleSchema = new mongoose.Schema({ }, }); -const Role = mongoose.model('Role', roleSchema); - -module.exports = Role; +export default roleSchema; diff --git a/packages/data-schemas/src/schema/session.ts b/packages/data-schemas/src/schema/session.ts new file mode 100644 index 0000000000..558bfd2252 --- /dev/null +++ b/packages/data-schemas/src/schema/session.ts @@ -0,0 +1,26 @@ +import mongoose, { Schema, Document, Types } from 'mongoose'; + +export interface ISession extends Document { + refreshTokenHash: string; + expiration: Date; + user: Types.ObjectId; +} + +const sessionSchema: Schema = new Schema({ + refreshTokenHash: { + type: String, + required: true, + }, + expiration: { + type: Date, + required: true, + expires: 0, + }, + user: { + type: mongoose.Schema.Types.ObjectId, + ref: 'User', + required: true, + }, +}); + +export default sessionSchema; diff --git a/api/models/schema/shareSchema.js b/packages/data-schemas/src/schema/share.ts similarity index 53% rename from api/models/schema/shareSchema.js rename to packages/data-schemas/src/schema/share.ts index 12699a39ec..62347beb56 100644 --- a/api/models/schema/shareSchema.js +++ b/packages/data-schemas/src/schema/share.ts @@ -1,6 +1,17 @@ -const mongoose = require('mongoose'); +import mongoose, { Schema, Document, Types } from 'mongoose'; -const shareSchema = mongoose.Schema( +export interface ISharedLink extends Document { + conversationId: string; + title?: string; + user?: string; + messages?: Types.ObjectId[]; + shareId?: string; + isPublic: boolean; + createdAt?: Date; + updatedAt?: Date; +} + +const shareSchema: Schema = new Schema( { conversationId: { type: String, @@ -27,4 +38,4 @@ const shareSchema = mongoose.Schema( { timestamps: true }, ); -module.exports = mongoose.model('SharedLink', shareSchema); +export default shareSchema; diff --git a/api/models/schema/tokenSchema.js b/packages/data-schemas/src/schema/token.ts similarity index 55% rename from api/models/schema/tokenSchema.js rename to packages/data-schemas/src/schema/token.ts index 1b45b2ff33..f238408209 100644 --- a/api/models/schema/tokenSchema.js +++ b/packages/data-schemas/src/schema/token.ts @@ -1,7 +1,17 @@ -const mongoose = require('mongoose'); -const Schema = mongoose.Schema; +import { Schema, Document, Types } from 'mongoose'; -const tokenSchema = new Schema({ +export interface IToken extends Document { + userId: Types.ObjectId; + email?: string; + type?: string; + identifier?: string; + token: string; + createdAt: Date; + expiresAt: Date; + metadata?: Map; +} + +const tokenSchema: Schema = new Schema({ userId: { type: Schema.Types.ObjectId, required: true, @@ -10,7 +20,9 @@ const tokenSchema = new Schema({ email: { type: String, }, - type: String, + type: { + type: String, + }, identifier: { type: String, }, @@ -35,4 +47,4 @@ const tokenSchema = new Schema({ tokenSchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 }); -module.exports = tokenSchema; +export default tokenSchema; diff --git a/packages/data-schemas/src/schema/toolCall.ts b/packages/data-schemas/src/schema/toolCall.ts new file mode 100644 index 0000000000..4bc35e5799 --- /dev/null +++ b/packages/data-schemas/src/schema/toolCall.ts @@ -0,0 +1,55 @@ +import mongoose, { Schema, Document, Types } from 'mongoose'; +import type { TAttachment } from 'librechat-data-provider'; + +export interface IToolCallData extends Document { + conversationId: string; + messageId: string; + toolId: string; + user: Types.ObjectId; + result?: unknown; + attachments?: TAttachment[]; + blockIndex?: number; + partIndex?: number; + createdAt?: Date; + updatedAt?: Date; +} + +const toolCallSchema: Schema = new Schema( + { + conversationId: { + type: String, + required: true, + }, + messageId: { + type: String, + required: true, + }, + toolId: { + type: String, + required: true, + }, + user: { + type: mongoose.Schema.Types.ObjectId, + ref: 'User', + required: true, + }, + result: { + type: mongoose.Schema.Types.Mixed, + }, + attachments: { + type: mongoose.Schema.Types.Mixed, + }, + blockIndex: { + type: Number, + }, + partIndex: { + type: Number, + }, + }, + { timestamps: true }, +); + +toolCallSchema.index({ messageId: 1, user: 1 }); +toolCallSchema.index({ conversationId: 1, user: 1 }); + +export default toolCallSchema; diff --git a/api/models/schema/transaction.js b/packages/data-schemas/src/schema/transaction.ts similarity index 54% rename from api/models/schema/transaction.js rename to packages/data-schemas/src/schema/transaction.ts index 8cb9ba59cc..5522989652 100644 --- a/api/models/schema/transaction.js +++ b/packages/data-schemas/src/schema/transaction.ts @@ -1,6 +1,24 @@ -const mongoose = require('mongoose'); +import mongoose, { Schema, Document, Types } from 'mongoose'; -const transactionSchema = mongoose.Schema( +// @ts-ignore +export interface ITransaction extends Document { + user: Types.ObjectId; + conversationId?: string; + tokenType: 'prompt' | 'completion' | 'credits'; + model?: string; + context?: string; + valueKey?: string; + rate?: number; + rawAmount?: number; + tokenValue?: number; + inputTokens?: number; + writeTokens?: number; + readTokens?: number; + createdAt?: Date; + updatedAt?: Date; +} + +const transactionSchema: Schema = new Schema( { user: { type: mongoose.Schema.Types.ObjectId, @@ -39,4 +57,4 @@ const transactionSchema = mongoose.Schema( }, ); -module.exports = transactionSchema; +export default transactionSchema; diff --git a/packages/data-schemas/src/schema/user.ts b/packages/data-schemas/src/schema/user.ts new file mode 100644 index 0000000000..f381d19086 --- /dev/null +++ b/packages/data-schemas/src/schema/user.ts @@ -0,0 +1,158 @@ +import { Schema, Document } from 'mongoose'; +import { SystemRoles } from 'librechat-data-provider'; + +export interface IUser extends Document { + name?: string; + username?: string; + email: string; + emailVerified: boolean; + password?: string; + avatar?: string; + provider: string; + role?: string; + googleId?: string; + facebookId?: string; + openidId?: string; + ldapId?: string; + githubId?: string; + discordId?: string; + appleId?: string; + plugins?: unknown[]; + totpSecret?: string; + backupCodes?: Array<{ + codeHash: string; + used: boolean; + usedAt?: Date | null; + }>; + refreshToken?: Array<{ + refreshToken: string; + }>; + expiresAt?: Date; + termsAccepted?: boolean; + createdAt?: Date; + updatedAt?: Date; +} + +// Session sub-schema +const SessionSchema = new Schema( + { + refreshToken: { + type: String, + default: '', + }, + }, + { _id: false }, +); + +// Backup code sub-schema +const BackupCodeSchema = new Schema( + { + codeHash: { type: String, required: true }, + used: { type: Boolean, default: false }, + usedAt: { type: Date, default: null }, + }, + { _id: false }, +); + +const User = new Schema( + { + name: { + type: String, + }, + username: { + type: String, + lowercase: true, + default: '', + }, + email: { + type: String, + required: [true, 'can\'t be blank'], + lowercase: true, + unique: true, + match: [/\S+@\S+\.\S+/, 'is invalid'], + index: true, + }, + emailVerified: { + type: Boolean, + required: true, + default: false, + }, + password: { + type: String, + trim: true, + minlength: 8, + maxlength: 128, + }, + avatar: { + type: String, + required: false, + }, + provider: { + type: String, + required: true, + default: 'local', + }, + role: { + type: String, + default: SystemRoles.USER, + }, + googleId: { + type: String, + unique: true, + sparse: true, + }, + facebookId: { + type: String, + unique: true, + sparse: true, + }, + openidId: { + type: String, + unique: true, + sparse: true, + }, + ldapId: { + type: String, + unique: true, + sparse: true, + }, + githubId: { + type: String, + unique: true, + sparse: true, + }, + discordId: { + type: String, + unique: true, + sparse: true, + }, + appleId: { + type: String, + unique: true, + sparse: true, + }, + plugins: { + type: Array, + }, + totpSecret: { + type: String, + }, + backupCodes: { + type: [BackupCodeSchema], + }, + refreshToken: { + type: [SessionSchema], + }, + expiresAt: { + type: Date, + expires: 604800, // 7 days in seconds + }, + termsAccepted: { + type: Boolean, + default: false, + }, + }, + { timestamps: true }, +); + +export default User; diff --git a/packages/data-schemas/tsconfig.json b/packages/data-schemas/tsconfig.json new file mode 100644 index 0000000000..287f21089a --- /dev/null +++ b/packages/data-schemas/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "declaration": true, + "declarationDir": "./dist/types", + "module": "esnext", + "noImplicitAny": true, + "outDir": "./dist", + "target": "es2015", + "moduleResolution": "node", + "lib": ["es2017", "dom", "ES2021.String"], + "skipLibCheck": true, + "esModuleInterop": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": false, + "sourceMap": true, + "baseUrl": "." + }, + "ts-node": { + "experimentalSpecifierResolution": "node", + "transpileOnly": true, + "esm": true + }, + "exclude": ["node_modules", "dist", "types"], + "include": ["src/**/*", "types/index.d.ts", "types/react-query/index.d.ts"] +} diff --git a/packages/data-schemas/tsconfig.spec.json b/packages/data-schemas/tsconfig.spec.json new file mode 100644 index 0000000000..f766b118e4 --- /dev/null +++ b/packages/data-schemas/tsconfig.spec.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true, + "outDir": "./dist/tests", + "baseUrl": "." + }, + "include": ["specs/**/*", "src/**/*"], + "exclude": ["node_modules", "dist"] +}