mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 06:00:56 +02:00
🪐 refactor: Migrate Share Functionality to Type-Safe Methods (#7903)
* chore: Update import for isEnabled utility in convoAccess middleware * refactor: Migrate Share functionality to new methods structure in `@librechat/data-schemas` - Deleted the old Share.js model and moved its functionality to a new share.ts file within the data-schemas package. - Updated imports across the codebase to reflect the new structure. - Enhanced error handling and logging in shared link operations. - Introduced TypeScript types for shared links and related operations to improve type safety and maintainability. * chore: Update promptGroupSchema validation with typing * fix: error handling and logging in createSharedLink * fix: don't allow empty shared link or shared link without messages * ci: add tests for shared link methods * chore: Bump version of @librechat/data-schemas to 0.0.9 in package.json and package-lock.json * chore: Add nanoid as peer dependency - Introduced `nanoid` as a dependency in `package.json` and `package-lock.json`. - Replaced UUID generation with `nanoid` for creating unique conversation and message IDs in share methods tests.
This commit is contained in:
parent
0103b4b08a
commit
3af2666890
13 changed files with 1720 additions and 411 deletions
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@librechat/data-schemas",
|
||||
"version": "0.0.8",
|
||||
"version": "0.0.9",
|
||||
"description": "Mongoose schemas and models for LibreChat",
|
||||
"type": "module",
|
||||
"main": "dist/index.cjs",
|
||||
|
@ -51,6 +51,7 @@
|
|||
"@types/traverse": "^0.6.37",
|
||||
"jest": "^29.5.0",
|
||||
"jest-junit": "^16.0.0",
|
||||
"mongodb-memory-server": "^10.1.4",
|
||||
"rimraf": "^5.0.1",
|
||||
"rollup": "^4.22.4",
|
||||
"rollup-plugin-generate-package-json": "^3.2.0",
|
||||
|
@ -60,13 +61,14 @@
|
|||
"typescript": "^5.0.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"keyv": "^5.3.2",
|
||||
"mongoose": "^8.12.1",
|
||||
"librechat-data-provider": "*",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"keyv": "^5.3.2",
|
||||
"klona": "^2.0.6",
|
||||
"librechat-data-provider": "*",
|
||||
"lodash": "^4.17.21",
|
||||
"meilisearch": "^0.38.0",
|
||||
"mongoose": "^8.12.1",
|
||||
"nanoid": "^3.3.7",
|
||||
"traverse": "^0.6.11",
|
||||
"winston": "^3.17.0",
|
||||
"winston-daily-rotate-file": "^5.0.0"
|
||||
|
|
|
@ -4,6 +4,7 @@ import { createTokenMethods, type TokenMethods } from './token';
|
|||
import { createRoleMethods, type RoleMethods } from './role';
|
||||
/* Memories */
|
||||
import { createMemoryMethods, type MemoryMethods } from './memory';
|
||||
import { createShareMethods, type ShareMethods } from './share';
|
||||
|
||||
/**
|
||||
* Creates all database methods for all collections
|
||||
|
@ -15,8 +16,14 @@ export function createMethods(mongoose: typeof import('mongoose')) {
|
|||
...createTokenMethods(mongoose),
|
||||
...createRoleMethods(mongoose),
|
||||
...createMemoryMethods(mongoose),
|
||||
...createShareMethods(mongoose),
|
||||
};
|
||||
}
|
||||
|
||||
export type { MemoryMethods };
|
||||
export type AllMethods = UserMethods & SessionMethods & TokenMethods & RoleMethods & MemoryMethods;
|
||||
export type { MemoryMethods, ShareMethods };
|
||||
export type AllMethods = UserMethods &
|
||||
SessionMethods &
|
||||
TokenMethods &
|
||||
RoleMethods &
|
||||
MemoryMethods &
|
||||
ShareMethods;
|
||||
|
|
1043
packages/data-schemas/src/methods/share.test.ts
Normal file
1043
packages/data-schemas/src/methods/share.test.ts
Normal file
File diff suppressed because it is too large
Load diff
442
packages/data-schemas/src/methods/share.ts
Normal file
442
packages/data-schemas/src/methods/share.ts
Normal file
|
@ -0,0 +1,442 @@
|
|||
import { nanoid } from 'nanoid';
|
||||
import { Constants } from 'librechat-data-provider';
|
||||
import type { FilterQuery, Model } from 'mongoose';
|
||||
import type { SchemaWithMeiliMethods } from '~/models/plugins/mongoMeili';
|
||||
import type * as t from '~/types';
|
||||
import logger from '~/config/winston';
|
||||
|
||||
class ShareServiceError extends Error {
|
||||
code: string;
|
||||
constructor(message: string, code: string) {
|
||||
super(message);
|
||||
this.name = 'ShareServiceError';
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
function memoizedAnonymizeId(prefix: string) {
|
||||
const memo = new Map<string, string>();
|
||||
return (id: string) => {
|
||||
if (!memo.has(id)) {
|
||||
memo.set(id, `${prefix}_${nanoid()}`);
|
||||
}
|
||||
return memo.get(id) as string;
|
||||
};
|
||||
}
|
||||
|
||||
const anonymizeConvoId = memoizedAnonymizeId('convo');
|
||||
const anonymizeAssistantId = memoizedAnonymizeId('a');
|
||||
const anonymizeMessageId = (id: string) =>
|
||||
id === Constants.NO_PARENT ? id : memoizedAnonymizeId('msg')(id);
|
||||
|
||||
function anonymizeConvo(conversation: Partial<t.IConversation> & Partial<t.ISharedLink>) {
|
||||
if (!conversation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const newConvo = { ...conversation };
|
||||
if (newConvo.assistant_id) {
|
||||
newConvo.assistant_id = anonymizeAssistantId(newConvo.assistant_id);
|
||||
}
|
||||
return newConvo;
|
||||
}
|
||||
|
||||
function anonymizeMessages(messages: t.IMessage[], newConvoId: string): t.IMessage[] {
|
||||
if (!Array.isArray(messages)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const idMap = new Map<string, string>();
|
||||
return messages.map((message) => {
|
||||
const newMessageId = anonymizeMessageId(message.messageId);
|
||||
idMap.set(message.messageId, newMessageId);
|
||||
|
||||
type MessageAttachment = {
|
||||
messageId?: string;
|
||||
conversationId?: string;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
const anonymizedAttachments = (message.attachments as MessageAttachment[])?.map(
|
||||
(attachment) => {
|
||||
return {
|
||||
...attachment,
|
||||
messageId: newMessageId,
|
||||
conversationId: newConvoId,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
...message,
|
||||
messageId: newMessageId,
|
||||
parentMessageId:
|
||||
idMap.get(message.parentMessageId || '') ||
|
||||
anonymizeMessageId(message.parentMessageId || ''),
|
||||
conversationId: newConvoId,
|
||||
model: message.model?.startsWith('asst_')
|
||||
? anonymizeAssistantId(message.model)
|
||||
: message.model,
|
||||
attachments: anonymizedAttachments,
|
||||
} as t.IMessage;
|
||||
});
|
||||
}
|
||||
|
||||
/** Factory function that takes mongoose instance and returns the methods */
|
||||
export function createShareMethods(mongoose: typeof import('mongoose')) {
|
||||
/**
|
||||
* Get shared messages for a public share link
|
||||
*/
|
||||
async function getSharedMessages(shareId: string): Promise<t.SharedMessagesResult | null> {
|
||||
try {
|
||||
const SharedLink = mongoose.models.SharedLink as Model<t.ISharedLink>;
|
||||
const share = (await SharedLink.findOne({ shareId, isPublic: true })
|
||||
.populate({
|
||||
path: 'messages',
|
||||
select: '-_id -__v -user',
|
||||
})
|
||||
.select('-_id -__v -user')
|
||||
.lean()) as (t.ISharedLink & { messages: t.IMessage[] }) | null;
|
||||
|
||||
if (!share?.conversationId || !share.isPublic) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const newConvoId = anonymizeConvoId(share.conversationId);
|
||||
const result: t.SharedMessagesResult = {
|
||||
shareId: share.shareId || shareId,
|
||||
title: share.title,
|
||||
isPublic: share.isPublic,
|
||||
createdAt: share.createdAt,
|
||||
updatedAt: share.updatedAt,
|
||||
conversationId: newConvoId,
|
||||
messages: anonymizeMessages(share.messages, newConvoId),
|
||||
};
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
logger.error('[getSharedMessages] Error getting share link', {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
shareId,
|
||||
});
|
||||
throw new ShareServiceError('Error getting share link', 'SHARE_FETCH_ERROR');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shared links for a specific user with pagination and search
|
||||
*/
|
||||
async function getSharedLinks(
|
||||
user: string,
|
||||
pageParam?: Date,
|
||||
pageSize: number = 10,
|
||||
isPublic: boolean = true,
|
||||
sortBy: string = 'createdAt',
|
||||
sortDirection: string = 'desc',
|
||||
search?: string,
|
||||
): Promise<t.SharedLinksResult> {
|
||||
try {
|
||||
const SharedLink = mongoose.models.SharedLink as Model<t.ISharedLink>;
|
||||
const Conversation = mongoose.models.Conversation as SchemaWithMeiliMethods;
|
||||
const query: FilterQuery<t.ISharedLink> = { user, isPublic };
|
||||
|
||||
if (pageParam) {
|
||||
if (sortDirection === 'desc') {
|
||||
query[sortBy] = { $lt: pageParam };
|
||||
} else {
|
||||
query[sortBy] = { $gt: pageParam };
|
||||
}
|
||||
}
|
||||
|
||||
if (search && search.trim()) {
|
||||
try {
|
||||
const searchResults = await Conversation.meiliSearch(search);
|
||||
|
||||
if (!searchResults?.hits?.length) {
|
||||
return {
|
||||
links: [],
|
||||
nextCursor: undefined,
|
||||
hasNextPage: false,
|
||||
};
|
||||
}
|
||||
|
||||
const conversationIds = searchResults.hits.map((hit) => hit.conversationId);
|
||||
query['conversationId'] = { $in: conversationIds };
|
||||
} catch (searchError) {
|
||||
logger.error('[getSharedLinks] Meilisearch error', {
|
||||
error: searchError instanceof Error ? searchError.message : 'Unknown error',
|
||||
user,
|
||||
});
|
||||
return {
|
||||
links: [],
|
||||
nextCursor: undefined,
|
||||
hasNextPage: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const sort: Record<string, 1 | -1> = {};
|
||||
sort[sortBy] = sortDirection === 'desc' ? -1 : 1;
|
||||
|
||||
const sharedLinks = await SharedLink.find(query)
|
||||
.sort(sort)
|
||||
.limit(pageSize + 1)
|
||||
.select('-__v -user')
|
||||
.lean();
|
||||
|
||||
const hasNextPage = sharedLinks.length > pageSize;
|
||||
const links = sharedLinks.slice(0, pageSize);
|
||||
|
||||
const nextCursor = hasNextPage
|
||||
? (links[links.length - 1][sortBy as keyof t.ISharedLink] as Date)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
links: links.map((link) => ({
|
||||
shareId: link.shareId || '',
|
||||
title: link?.title || 'Untitled',
|
||||
isPublic: link.isPublic,
|
||||
createdAt: link.createdAt || new Date(),
|
||||
conversationId: link.conversationId,
|
||||
})),
|
||||
nextCursor,
|
||||
hasNextPage,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('[getSharedLinks] Error getting shares', {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
user,
|
||||
});
|
||||
throw new ShareServiceError('Error getting shares', 'SHARES_FETCH_ERROR');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all shared links for a user
|
||||
*/
|
||||
async function deleteAllSharedLinks(user: string): Promise<t.DeleteAllSharesResult> {
|
||||
try {
|
||||
const SharedLink = mongoose.models.SharedLink as Model<t.ISharedLink>;
|
||||
const result = await SharedLink.deleteMany({ user });
|
||||
return {
|
||||
message: 'All shared links deleted successfully',
|
||||
deletedCount: result.deletedCount,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('[deleteAllSharedLinks] Error deleting shared links', {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
user,
|
||||
});
|
||||
throw new ShareServiceError('Error deleting shared links', 'BULK_DELETE_ERROR');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new shared link for a conversation
|
||||
*/
|
||||
async function createSharedLink(
|
||||
user: string,
|
||||
conversationId: string,
|
||||
): Promise<t.CreateShareResult> {
|
||||
if (!user || !conversationId) {
|
||||
throw new ShareServiceError('Missing required parameters', 'INVALID_PARAMS');
|
||||
}
|
||||
try {
|
||||
const Message = mongoose.models.Message as SchemaWithMeiliMethods;
|
||||
const SharedLink = mongoose.models.SharedLink as Model<t.ISharedLink>;
|
||||
const Conversation = mongoose.models.Conversation as SchemaWithMeiliMethods;
|
||||
|
||||
const [existingShare, conversationMessages] = await Promise.all([
|
||||
SharedLink.findOne({ conversationId, user, isPublic: true })
|
||||
.select('-_id -__v -user')
|
||||
.lean() as Promise<t.ISharedLink | null>,
|
||||
Message.find({ conversationId, user }).sort({ createdAt: 1 }).lean(),
|
||||
]);
|
||||
|
||||
if (existingShare && existingShare.isPublic) {
|
||||
logger.error('[createSharedLink] Share already exists', {
|
||||
user,
|
||||
conversationId,
|
||||
});
|
||||
throw new ShareServiceError('Share already exists', 'SHARE_EXISTS');
|
||||
} else if (existingShare) {
|
||||
await SharedLink.deleteOne({ conversationId, user });
|
||||
}
|
||||
|
||||
const conversation = (await Conversation.findOne({ conversationId, user }).lean()) as {
|
||||
title?: string;
|
||||
} | null;
|
||||
|
||||
// Check if user owns the conversation
|
||||
if (!conversation) {
|
||||
throw new ShareServiceError(
|
||||
'Conversation not found or access denied',
|
||||
'CONVERSATION_NOT_FOUND',
|
||||
);
|
||||
}
|
||||
|
||||
// Check if there are any messages to share
|
||||
if (!conversationMessages || conversationMessages.length === 0) {
|
||||
throw new ShareServiceError('No messages to share', 'NO_MESSAGES');
|
||||
}
|
||||
|
||||
const title = conversation.title || 'Untitled';
|
||||
|
||||
const shareId = nanoid();
|
||||
await SharedLink.create({
|
||||
shareId,
|
||||
conversationId,
|
||||
messages: conversationMessages,
|
||||
title,
|
||||
user,
|
||||
});
|
||||
|
||||
return { shareId, conversationId };
|
||||
} catch (error) {
|
||||
if (error instanceof ShareServiceError) {
|
||||
throw error;
|
||||
}
|
||||
logger.error('[createSharedLink] Error creating shared link', {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
user,
|
||||
conversationId,
|
||||
});
|
||||
throw new ShareServiceError('Error creating shared link', 'SHARE_CREATE_ERROR');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a shared link for a conversation
|
||||
*/
|
||||
async function getSharedLink(
|
||||
user: string,
|
||||
conversationId: string,
|
||||
): Promise<t.GetShareLinkResult> {
|
||||
if (!user || !conversationId) {
|
||||
throw new ShareServiceError('Missing required parameters', 'INVALID_PARAMS');
|
||||
}
|
||||
|
||||
try {
|
||||
const SharedLink = mongoose.models.SharedLink as Model<t.ISharedLink>;
|
||||
const share = (await SharedLink.findOne({ conversationId, user, isPublic: true })
|
||||
.select('shareId -_id')
|
||||
.lean()) as { shareId?: string } | null;
|
||||
|
||||
if (!share) {
|
||||
return { shareId: null, success: false };
|
||||
}
|
||||
|
||||
return { shareId: share.shareId || null, success: true };
|
||||
} catch (error) {
|
||||
logger.error('[getSharedLink] Error getting shared link', {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
user,
|
||||
conversationId,
|
||||
});
|
||||
throw new ShareServiceError('Error getting shared link', 'SHARE_FETCH_ERROR');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a shared link with new messages
|
||||
*/
|
||||
async function updateSharedLink(user: string, shareId: string): Promise<t.UpdateShareResult> {
|
||||
if (!user || !shareId) {
|
||||
throw new ShareServiceError('Missing required parameters', 'INVALID_PARAMS');
|
||||
}
|
||||
|
||||
try {
|
||||
const SharedLink = mongoose.models.SharedLink as Model<t.ISharedLink>;
|
||||
const Message = mongoose.models.Message as SchemaWithMeiliMethods;
|
||||
const share = (await SharedLink.findOne({ shareId, user })
|
||||
.select('-_id -__v -user')
|
||||
.lean()) as t.ISharedLink | null;
|
||||
|
||||
if (!share) {
|
||||
throw new ShareServiceError('Share not found', 'SHARE_NOT_FOUND');
|
||||
}
|
||||
|
||||
const updatedMessages = await Message.find({ conversationId: share.conversationId, user })
|
||||
.sort({ createdAt: 1 })
|
||||
.lean();
|
||||
|
||||
const newShareId = nanoid();
|
||||
const update = {
|
||||
messages: updatedMessages,
|
||||
user,
|
||||
shareId: newShareId,
|
||||
};
|
||||
|
||||
const updatedShare = (await SharedLink.findOneAndUpdate({ shareId, user }, update, {
|
||||
new: true,
|
||||
upsert: false,
|
||||
runValidators: true,
|
||||
}).lean()) as t.ISharedLink | null;
|
||||
|
||||
if (!updatedShare) {
|
||||
throw new ShareServiceError('Share update failed', 'SHARE_UPDATE_ERROR');
|
||||
}
|
||||
|
||||
anonymizeConvo(updatedShare);
|
||||
|
||||
return { shareId: newShareId, conversationId: updatedShare.conversationId };
|
||||
} catch (error) {
|
||||
logger.error('[updateSharedLink] Error updating shared link', {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
user,
|
||||
shareId,
|
||||
});
|
||||
throw new ShareServiceError(
|
||||
error instanceof ShareServiceError ? error.message : 'Error updating shared link',
|
||||
error instanceof ShareServiceError ? error.code : 'SHARE_UPDATE_ERROR',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a shared link
|
||||
*/
|
||||
async function deleteSharedLink(
|
||||
user: string,
|
||||
shareId: string,
|
||||
): Promise<t.DeleteShareResult | null> {
|
||||
if (!user || !shareId) {
|
||||
throw new ShareServiceError('Missing required parameters', 'INVALID_PARAMS');
|
||||
}
|
||||
|
||||
try {
|
||||
const SharedLink = mongoose.models.SharedLink as Model<t.ISharedLink>;
|
||||
const result = await SharedLink.findOneAndDelete({ shareId, user }).lean();
|
||||
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
shareId,
|
||||
message: 'Share deleted successfully',
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('[deleteSharedLink] Error deleting shared link', {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
user,
|
||||
shareId,
|
||||
});
|
||||
throw new ShareServiceError('Error deleting shared link', 'SHARE_DELETE_ERROR');
|
||||
}
|
||||
}
|
||||
|
||||
// Return all methods
|
||||
return {
|
||||
getSharedLink,
|
||||
getSharedLinks,
|
||||
createSharedLink,
|
||||
updateSharedLink,
|
||||
deleteSharedLink,
|
||||
getSharedMessages,
|
||||
deleteAllSharedLinks,
|
||||
};
|
||||
}
|
||||
|
||||
export type ShareMethods = ReturnType<typeof createShareMethods>;
|
|
@ -1,5 +1,6 @@
|
|||
import _ from 'lodash';
|
||||
import { MeiliSearch, Index } from 'meilisearch';
|
||||
import { MeiliSearch } from 'meilisearch';
|
||||
import type { SearchResponse, Index } from 'meilisearch';
|
||||
import type {
|
||||
CallbackWithoutResultAndOptionalError,
|
||||
FilterQuery,
|
||||
|
@ -9,6 +10,7 @@ import type {
|
|||
Types,
|
||||
Model,
|
||||
} from 'mongoose';
|
||||
import type { IConversation, IMessage } from '~/types';
|
||||
import logger from '~/config/meiliLogger';
|
||||
|
||||
interface MongoMeiliOptions {
|
||||
|
@ -29,7 +31,7 @@ interface ContentItem {
|
|||
text?: string;
|
||||
}
|
||||
|
||||
interface DocumentWithMeiliIndex extends Document {
|
||||
interface _DocumentWithMeiliIndex extends Document {
|
||||
_meiliIndex?: boolean;
|
||||
preprocessObjectForIndex?: () => Record<string, unknown>;
|
||||
addObjectToMeili?: (next: CallbackWithoutResultAndOptionalError) => Promise<void>;
|
||||
|
@ -38,19 +40,18 @@ interface DocumentWithMeiliIndex extends Document {
|
|||
postSaveHook?: (next: CallbackWithoutResultAndOptionalError) => void;
|
||||
postUpdateHook?: (next: CallbackWithoutResultAndOptionalError) => void;
|
||||
postRemoveHook?: (next: CallbackWithoutResultAndOptionalError) => void;
|
||||
conversationId?: string;
|
||||
content?: ContentItem[];
|
||||
messageId?: string;
|
||||
unfinished?: boolean;
|
||||
messages?: unknown[];
|
||||
title?: string;
|
||||
toJSON(): Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface SchemaWithMeiliMethods extends Model<DocumentWithMeiliIndex> {
|
||||
export type DocumentWithMeiliIndex = _DocumentWithMeiliIndex & IConversation & Partial<IMessage>;
|
||||
|
||||
export interface SchemaWithMeiliMethods extends Model<DocumentWithMeiliIndex> {
|
||||
syncWithMeili(): Promise<void>;
|
||||
setMeiliIndexSettings(settings: Record<string, unknown>): Promise<unknown>;
|
||||
meiliSearch(q: string, params: Record<string, unknown>, populate: boolean): Promise<unknown>;
|
||||
meiliSearch(
|
||||
q: string,
|
||||
params?: Record<string, unknown>,
|
||||
populate?: boolean,
|
||||
): Promise<SearchResponse<MeiliIndexable, Record<string, unknown>>>;
|
||||
}
|
||||
|
||||
// Environment flags
|
||||
|
@ -247,7 +248,7 @@ const createMeiliMongooseModel = ({
|
|||
q: string,
|
||||
params: Record<string, unknown>,
|
||||
populate: boolean,
|
||||
): Promise<unknown> {
|
||||
): Promise<SearchResponse<MeiliIndexable, Record<string, unknown>>> {
|
||||
const data = await index.search(q, params);
|
||||
|
||||
if (populate) {
|
||||
|
|
|
@ -63,11 +63,11 @@ const promptGroupSchema = new Schema<IPromptGroupDocument>(
|
|||
type: String,
|
||||
index: true,
|
||||
validate: {
|
||||
validator: function (v: unknown): boolean {
|
||||
validator: function (v: string | undefined | null): 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.`,
|
||||
message: (props: { value?: string } | undefined) =>
|
||||
`${props?.value ?? 'Value'} is not a valid command. Only lowercase alphanumeric characters and hyphens are allowed.`,
|
||||
},
|
||||
maxlength: [
|
||||
Constants.COMMANDS_MAX_LENGTH as number,
|
||||
|
|
|
@ -13,5 +13,6 @@ export * from './role';
|
|||
export * from './action';
|
||||
export * from './assistant';
|
||||
export * from './file';
|
||||
export * from './share';
|
||||
/* Memories */
|
||||
export * from './memory';
|
||||
|
|
66
packages/data-schemas/src/types/share.ts
Normal file
66
packages/data-schemas/src/types/share.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
import type { Types } from 'mongoose';
|
||||
import type { IMessage } from './message';
|
||||
|
||||
export interface ISharedLink {
|
||||
_id?: Types.ObjectId;
|
||||
conversationId: string;
|
||||
title?: string;
|
||||
user?: string;
|
||||
messages?: Types.ObjectId[];
|
||||
shareId?: string;
|
||||
isPublic: boolean;
|
||||
createdAt?: Date;
|
||||
updatedAt?: Date;
|
||||
}
|
||||
|
||||
export interface ShareServiceError extends Error {
|
||||
code: string;
|
||||
}
|
||||
|
||||
export interface SharedLinksResult {
|
||||
links: Array<{
|
||||
shareId: string;
|
||||
title: string;
|
||||
isPublic: boolean;
|
||||
createdAt: Date;
|
||||
conversationId: string;
|
||||
}>;
|
||||
nextCursor?: Date;
|
||||
hasNextPage: boolean;
|
||||
}
|
||||
|
||||
export interface SharedMessagesResult {
|
||||
conversationId: string;
|
||||
messages: Array<IMessage>;
|
||||
shareId: string;
|
||||
title?: string;
|
||||
isPublic: boolean;
|
||||
createdAt?: Date;
|
||||
updatedAt?: Date;
|
||||
}
|
||||
|
||||
export interface CreateShareResult {
|
||||
shareId: string;
|
||||
conversationId: string;
|
||||
}
|
||||
|
||||
export interface UpdateShareResult {
|
||||
shareId: string;
|
||||
conversationId: string;
|
||||
}
|
||||
|
||||
export interface DeleteShareResult {
|
||||
success: boolean;
|
||||
shareId: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface GetShareLinkResult {
|
||||
shareId: string | null;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export interface DeleteAllSharesResult {
|
||||
message: string;
|
||||
deletedCount: number;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue