From 02f639f00eba55bb270b8d9e0dd22df762d74695 Mon Sep 17 00:00:00 2001 From: Atef Bellaaj Date: Fri, 13 Jun 2025 14:07:36 +0200 Subject: [PATCH] refactored and moved agent category methods and schema to data-schema package --- .../data-schemas/src/methods/agentCategory.ts | 161 ++++++++++++++++++ packages/data-schemas/src/methods/index.ts | 4 + .../data-schemas/src/schema/agentCategory.ts | 49 ++++++ packages/data-schemas/src/schema/index.ts | 1 + .../data-schemas/src/types/agentCategory.ts | 19 +++ 5 files changed, 234 insertions(+) create mode 100644 packages/data-schemas/src/methods/agentCategory.ts create mode 100644 packages/data-schemas/src/schema/agentCategory.ts create mode 100644 packages/data-schemas/src/types/agentCategory.ts diff --git a/packages/data-schemas/src/methods/agentCategory.ts b/packages/data-schemas/src/methods/agentCategory.ts new file mode 100644 index 0000000000..a35e6128f3 --- /dev/null +++ b/packages/data-schemas/src/methods/agentCategory.ts @@ -0,0 +1,161 @@ +import type { Model, Types, DeleteResult } from 'mongoose'; +import type { IAgentCategory, AgentCategory } from '../types/agentCategory'; + +export function createAgentCategoryMethods(mongoose: typeof import('mongoose')) { + /** + * Get all active categories sorted by order + * @returns Array of active categories + */ + async function getActiveCategories(): Promise { + const AgentCategory = mongoose.models.AgentCategory as Model; + return await AgentCategory.find({ isActive: true }).sort({ order: 1, label: 1 }).lean(); + } + + /** + * Get categories with agent counts + * @returns Categories with agent counts + */ + async function getCategoriesWithCounts(): Promise<(IAgentCategory & { agentCount: number })[]> { + const Agent = mongoose.models.Agent; + + const categoryCounts = await Agent.aggregate([ + { $match: { category: { $exists: true, $ne: null } } }, + { $group: { _id: '$category', count: { $sum: 1 } } }, + ]); + + const countMap = new Map(categoryCounts.map((c) => [c._id, c.count])); + const categories = await getActiveCategories(); + + return categories.map((category) => ({ + ...category, + agentCount: countMap.get(category.value) || (0 as number), + })) as (IAgentCategory & { agentCount: number })[]; + } + + /** + * Get valid category values for Agent model validation + * @returns Array of valid category values + */ + async function getValidCategoryValues(): Promise { + const AgentCategory = mongoose.models.AgentCategory as Model; + return await AgentCategory.find({ isActive: true }).distinct('value').lean(); + } + + /** + * Seed initial categories from existing constants + * @param categories - Array of category data to seed + * @returns Bulk write result + */ + async function seedCategories( + categories: Array<{ + value: string; + label?: string; + description?: string; + order?: number; + }>, + ): Promise { + const AgentCategory = mongoose.models.AgentCategory as Model; + + const operations = categories.map((category, index) => ({ + updateOne: { + filter: { value: category.value }, + update: { + $setOnInsert: { + value: category.value, + label: category.label || category.value, + description: category.description || '', + order: category.order || index, + isActive: true, + }, + }, + upsert: true, + }, + })); + + return await AgentCategory.bulkWrite(operations); + } + + /** + * Find a category by value + * @param value - The category value to search for + * @returns The category document or null + */ + async function findCategoryByValue(value: string): Promise { + const AgentCategory = mongoose.models.AgentCategory as Model; + return await AgentCategory.findOne({ value }).lean(); + } + + /** + * Create a new category + * @param categoryData - The category data to create + * @returns The created category + */ + async function createCategory(categoryData: Partial): Promise { + const AgentCategory = mongoose.models.AgentCategory as Model; + const category = await AgentCategory.create(categoryData); + return category.toObject() as IAgentCategory; + } + + /** + * Update a category by value + * @param value - The category value to update + * @param updateData - The data to update + * @returns The updated category or null + */ + async function updateCategory( + value: string, + updateData: Partial, + ): Promise { + const AgentCategory = mongoose.models.AgentCategory as Model; + return await AgentCategory.findOneAndUpdate( + { value }, + { $set: updateData }, + { new: true, runValidators: true }, + ).lean(); + } + + /** + * Delete a category by value + * @param value - The category value to delete + * @returns Whether the deletion was successful + */ + async function deleteCategory(value: string): Promise { + const AgentCategory = mongoose.models.AgentCategory as Model; + const result = await AgentCategory.deleteOne({ value }); + return result.deletedCount > 0; + } + + /** + * Find a category by ID + * @param id - The category ID to search for + * @returns The category document or null + */ + async function findCategoryById(id: string | Types.ObjectId): Promise { + const AgentCategory = mongoose.models.AgentCategory as Model; + return await AgentCategory.findById(id).lean(); + } + + /** + * Get all categories (active and inactive) + * @returns Array of all categories + */ + async function getAllCategories(): Promise { + const AgentCategory = mongoose.models.AgentCategory as Model; + return await AgentCategory.find({}).sort({ order: 1, label: 1 }).lean(); + } + + return { + getActiveCategories, + getCategoriesWithCounts, + getValidCategoryValues, + seedCategories, + findCategoryByValue, + createCategory, + updateCategory, + deleteCategory, + findCategoryById, + getAllCategories, + }; +} + +export type AgentCategoryMethods = ReturnType; diff --git a/packages/data-schemas/src/methods/index.ts b/packages/data-schemas/src/methods/index.ts index bc27e58abc..c42590f61e 100644 --- a/packages/data-schemas/src/methods/index.ts +++ b/packages/data-schemas/src/methods/index.ts @@ -4,6 +4,8 @@ import { createTokenMethods, type TokenMethods } from './token'; import { createRoleMethods, type RoleMethods } from './role'; /* Memories */ import { createMemoryMethods, type MemoryMethods } from './memory'; +/* Agent Categories */ +import { createAgentCategoryMethods, type AgentCategoryMethods } from './agentCategory'; /* Permissions */ import { createAccessRoleMethods, type AccessRoleMethods } from './accessRole'; import { createUserGroupMethods, type UserGroupMethods } from './userGroup'; @@ -22,6 +24,7 @@ export function createMethods(mongoose: typeof import('mongoose')) { ...createTokenMethods(mongoose), ...createRoleMethods(mongoose), ...createMemoryMethods(mongoose), + ...createAgentCategoryMethods(mongoose), ...createAccessRoleMethods(mongoose), ...createUserGroupMethods(mongoose), ...createAclEntryMethods(mongoose), @@ -37,6 +40,7 @@ export type AllMethods = UserMethods & TokenMethods & RoleMethods & MemoryMethods & + AgentCategoryMethods & AccessRoleMethods & UserGroupMethods & AclEntryMethods & diff --git a/packages/data-schemas/src/schema/agentCategory.ts b/packages/data-schemas/src/schema/agentCategory.ts new file mode 100644 index 0000000000..4bc0d778dd --- /dev/null +++ b/packages/data-schemas/src/schema/agentCategory.ts @@ -0,0 +1,49 @@ +import { Schema, Document } from 'mongoose'; + +export interface IAgentCategory extends Document { + value: string; + label: string; + description?: string; + order: number; + isActive: boolean; +} + +const agentCategorySchema = new Schema( + { + value: { + type: String, + required: true, + unique: true, + trim: true, + lowercase: true, + index: true, + }, + label: { + type: String, + required: true, + trim: true, + }, + description: { + type: String, + trim: true, + default: '', + }, + order: { + type: Number, + default: 0, + index: true, + }, + isActive: { + type: Boolean, + default: true, + index: true, + }, + }, + { + timestamps: true, + }, +); + +agentCategorySchema.index({ isActive: 1, order: 1 }); + +export default agentCategorySchema; \ No newline at end of file diff --git a/packages/data-schemas/src/schema/index.ts b/packages/data-schemas/src/schema/index.ts index e95f560b23..a7de162e0b 100644 --- a/packages/data-schemas/src/schema/index.ts +++ b/packages/data-schemas/src/schema/index.ts @@ -1,5 +1,6 @@ export { default as actionSchema } from './action'; export { default as agentSchema } from './agent'; +export { default as agentCategorySchema } from './agentCategory'; export { default as assistantSchema } from './assistant'; export { default as balanceSchema } from './balance'; export { default as bannerSchema } from './banner'; diff --git a/packages/data-schemas/src/types/agentCategory.ts b/packages/data-schemas/src/types/agentCategory.ts new file mode 100644 index 0000000000..ccf266a613 --- /dev/null +++ b/packages/data-schemas/src/types/agentCategory.ts @@ -0,0 +1,19 @@ +import type { Document, Types } from 'mongoose'; + +export type AgentCategory = { + /** Unique identifier for the category (e.g., 'general', 'hr', 'finance') */ + value: string; + /** Display label for the category */ + label: string; + /** Description of the category */ + description?: string; + /** Display order for sorting categories */ + order: number; + /** Whether the category is active and should be displayed */ + isActive: boolean; +}; + +export type IAgentCategory = AgentCategory & + Document & { + _id: Types.ObjectId; + };