diff --git a/modules/datamodels/datamodelPagination.py b/modules/datamodels/datamodelPagination.py index 3222e0e7..b1bc3bc6 100644 --- a/modules/datamodels/datamodelPagination.py +++ b/modules/datamodels/datamodelPagination.py @@ -26,7 +26,16 @@ class PaginationParams(BaseModel): page: int = Field(ge=1, description="Current page number (1-based)") pageSize: int = Field(ge=1, le=1000, description="Number of items per page") sort: List[SortField] = Field(default_factory=list, description="List of sort fields in priority order") - filters: Optional[Dict[str, Any]] = Field(default=None, description="Filter criteria (structure TBD for future implementation)") + filters: Optional[Dict[str, Any]] = Field( + default=None, + description="""Filter criteria dictionary. Supports: + - General search: {"search": "text"} - searches across all text fields (case-insensitive) + - Field-specific filters: + - Simple equals: {"status": "running"} + - With operator: {"status": {"operator": "equals", "value": "running"}} + - Supported operators: equals/eq, contains, startsWith, endsWith, gt, gte, lt, lte, in, notIn + - Multiple filters are combined with AND logic""" + ) class PaginationRequest(BaseModel): @@ -57,7 +66,7 @@ class PaginationMetadata(BaseModel): totalItems: int = Field(..., description="Total number of items across all pages (after filters)") totalPages: int = Field(..., description="Total number of pages (calculated from totalItems / pageSize)") sort: List[SortField] = Field(..., description="Current sort configuration applied") - filters: Optional[Dict[str, Any]] = Field(default=None, description="Current filters applied (for future use)") + filters: Optional[Dict[str, Any]] = Field(default=None, description="Current filters applied (mirrors request filters)") class PaginatedResponse(BaseModel, Generic[T]): diff --git a/modules/interfaces/interfaceDbAppObjects.py b/modules/interfaces/interfaceDbAppObjects.py index b4f9893e..91d7bda4 100644 --- a/modules/interfaces/interfaceDbAppObjects.py +++ b/modules/interfaces/interfaceDbAppObjects.py @@ -236,9 +236,163 @@ class AppObjects: return self.access.canModify(model_class, recordId) def _applyFilters(self, records: List[Dict[str, Any]], filters: Dict[str, Any]) -> List[Dict[str, Any]]: - """Apply filter criteria to records (implementation for future filtering).""" - # TODO: Implement filtering logic when needed - return records + """ + Apply filter criteria to records. + + Supports: + - General search: {"search": "text"} - searches across all text fields + - Field-specific filters: + - Simple: {"status": "running"} - equals match + - With operator: {"status": {"operator": "equals", "value": "running"}} + - Operators: equals, contains, gt, gte, lt, lte, in, notIn, startsWith, endsWith + + Args: + records: List of record dictionaries to filter + filters: Filter criteria dictionary + + Returns: + Filtered list of records + """ + if not filters or not records: + return records + + filtered = [] + + for record in records: + matches = True + + # Handle general search across text fields + if "search" in filters: + search_term = str(filters["search"]).lower() + if search_term: + # Search in all string fields + found = False + for key, value in record.items(): + if isinstance(value, str) and search_term in value.lower(): + found = True + break + elif isinstance(value, (int, float)) and search_term in str(value): + found = True + break + if not found: + matches = False + + # Handle field-specific filters + for field_name, filter_value in filters.items(): + if field_name == "search": + continue # Already handled above + + if field_name not in record: + matches = False + break + + record_value = record.get(field_name) + + # Handle simple value (equals operator) + if not isinstance(filter_value, dict): + if record_value != filter_value: + matches = False + break + continue + + # Handle filter with operator + operator = filter_value.get("operator", "equals") + filter_val = filter_value.get("value") + + if operator in ["equals", "eq"]: + if record_value != filter_val: + matches = False + break + + elif operator == "contains": + record_str = str(record_value).lower() if record_value is not None else "" + filter_str = str(filter_val).lower() if filter_val is not None else "" + if filter_str not in record_str: + matches = False + break + + elif operator == "startsWith": + record_str = str(record_value).lower() if record_value is not None else "" + filter_str = str(filter_val).lower() if filter_val is not None else "" + if not record_str.startswith(filter_str): + matches = False + break + + elif operator == "endsWith": + record_str = str(record_value).lower() if record_value is not None else "" + filter_str = str(filter_val).lower() if filter_val is not None else "" + if not record_str.endswith(filter_str): + matches = False + break + + elif operator == "gt": + try: + record_num = float(record_value) if record_value is not None else float('-inf') + filter_num = float(filter_val) if filter_val is not None else float('-inf') + if record_num <= filter_num: + matches = False + break + except (ValueError, TypeError): + matches = False + break + + elif operator == "gte": + try: + record_num = float(record_value) if record_value is not None else float('-inf') + filter_num = float(filter_val) if filter_val is not None else float('-inf') + if record_num < filter_num: + matches = False + break + except (ValueError, TypeError): + matches = False + break + + elif operator == "lt": + try: + record_num = float(record_value) if record_value is not None else float('inf') + filter_num = float(filter_val) if filter_val is not None else float('inf') + if record_num >= filter_num: + matches = False + break + except (ValueError, TypeError): + matches = False + break + + elif operator == "lte": + try: + record_num = float(record_value) if record_value is not None else float('inf') + filter_num = float(filter_val) if filter_val is not None else float('inf') + if record_num > filter_num: + matches = False + break + except (ValueError, TypeError): + matches = False + break + + elif operator == "in": + if not isinstance(filter_val, list): + filter_val = [filter_val] + if record_value not in filter_val: + matches = False + break + + elif operator == "notIn": + if not isinstance(filter_val, list): + filter_val = [filter_val] + if record_value in filter_val: + matches = False + break + + else: + # Unknown operator - default to equals + if record_value != filter_val: + matches = False + break + + if matches: + filtered.append(record) + + return filtered def _applySorting(self, records: List[Dict[str, Any]], sortFields: List[Any]) -> List[Dict[str, Any]]: """Apply multi-level sorting to records using stable sort (sorts from least to most significant field).""" diff --git a/modules/interfaces/interfaceDbChatObjects.py b/modules/interfaces/interfaceDbChatObjects.py index db2c5d59..0b217bd1 100644 --- a/modules/interfaces/interfaceDbChatObjects.py +++ b/modules/interfaces/interfaceDbChatObjects.py @@ -211,9 +211,163 @@ class ChatObjects: return self.access.canModify(model_class, recordId) def _applyFilters(self, records: List[Dict[str, Any]], filters: Dict[str, Any]) -> List[Dict[str, Any]]: - """Apply filter criteria to records (implementation for future filtering).""" - # TODO: Implement filtering logic when needed - return records + """ + Apply filter criteria to records. + + Supports: + - General search: {"search": "text"} - searches across all text fields + - Field-specific filters: + - Simple: {"status": "running"} - equals match + - With operator: {"status": {"operator": "equals", "value": "running"}} + - Operators: equals, contains, gt, gte, lt, lte, in, notIn, startsWith, endsWith + + Args: + records: List of record dictionaries to filter + filters: Filter criteria dictionary + + Returns: + Filtered list of records + """ + if not filters or not records: + return records + + filtered = [] + + for record in records: + matches = True + + # Handle general search across text fields + if "search" in filters: + search_term = str(filters["search"]).lower() + if search_term: + # Search in all string fields + found = False + for key, value in record.items(): + if isinstance(value, str) and search_term in value.lower(): + found = True + break + elif isinstance(value, (int, float)) and search_term in str(value): + found = True + break + if not found: + matches = False + + # Handle field-specific filters + for field_name, filter_value in filters.items(): + if field_name == "search": + continue # Already handled above + + if field_name not in record: + matches = False + break + + record_value = record.get(field_name) + + # Handle simple value (equals operator) + if not isinstance(filter_value, dict): + if record_value != filter_value: + matches = False + break + continue + + # Handle filter with operator + operator = filter_value.get("operator", "equals") + filter_val = filter_value.get("value") + + if operator in ["equals", "eq"]: + if record_value != filter_val: + matches = False + break + + elif operator == "contains": + record_str = str(record_value).lower() if record_value is not None else "" + filter_str = str(filter_val).lower() if filter_val is not None else "" + if filter_str not in record_str: + matches = False + break + + elif operator == "startsWith": + record_str = str(record_value).lower() if record_value is not None else "" + filter_str = str(filter_val).lower() if filter_val is not None else "" + if not record_str.startswith(filter_str): + matches = False + break + + elif operator == "endsWith": + record_str = str(record_value).lower() if record_value is not None else "" + filter_str = str(filter_val).lower() if filter_val is not None else "" + if not record_str.endswith(filter_str): + matches = False + break + + elif operator == "gt": + try: + record_num = float(record_value) if record_value is not None else float('-inf') + filter_num = float(filter_val) if filter_val is not None else float('-inf') + if record_num <= filter_num: + matches = False + break + except (ValueError, TypeError): + matches = False + break + + elif operator == "gte": + try: + record_num = float(record_value) if record_value is not None else float('-inf') + filter_num = float(filter_val) if filter_val is not None else float('-inf') + if record_num < filter_num: + matches = False + break + except (ValueError, TypeError): + matches = False + break + + elif operator == "lt": + try: + record_num = float(record_value) if record_value is not None else float('inf') + filter_num = float(filter_val) if filter_val is not None else float('inf') + if record_num >= filter_num: + matches = False + break + except (ValueError, TypeError): + matches = False + break + + elif operator == "lte": + try: + record_num = float(record_value) if record_value is not None else float('inf') + filter_num = float(filter_val) if filter_val is not None else float('inf') + if record_num > filter_num: + matches = False + break + except (ValueError, TypeError): + matches = False + break + + elif operator == "in": + if not isinstance(filter_val, list): + filter_val = [filter_val] + if record_value not in filter_val: + matches = False + break + + elif operator == "notIn": + if not isinstance(filter_val, list): + filter_val = [filter_val] + if record_value in filter_val: + matches = False + break + + else: + # Unknown operator - default to equals + if record_value != filter_val: + matches = False + break + + if matches: + filtered.append(record) + + return filtered def _applySorting(self, records: List[Dict[str, Any]], sortFields: List[Any]) -> List[Dict[str, Any]]: """Apply multi-level sorting to records using stable sort (sorts from least to most significant field).""" diff --git a/modules/interfaces/interfaceDbComponentObjects.py b/modules/interfaces/interfaceDbComponentObjects.py index 10e2d85d..225f8ad5 100644 --- a/modules/interfaces/interfaceDbComponentObjects.py +++ b/modules/interfaces/interfaceDbComponentObjects.py @@ -247,9 +247,163 @@ class ComponentObjects: return self.access.canModify(model_class, recordId) def _applyFilters(self, records: List[Dict[str, Any]], filters: Dict[str, Any]) -> List[Dict[str, Any]]: - """Apply filter criteria to records (implementation for future filtering).""" - # TODO: Implement filtering logic when needed - return records + """ + Apply filter criteria to records. + + Supports: + - General search: {"search": "text"} - searches across all text fields + - Field-specific filters: + - Simple: {"status": "running"} - equals match + - With operator: {"status": {"operator": "equals", "value": "running"}} + - Operators: equals, contains, gt, gte, lt, lte, in, notIn, startsWith, endsWith + + Args: + records: List of record dictionaries to filter + filters: Filter criteria dictionary + + Returns: + Filtered list of records + """ + if not filters or not records: + return records + + filtered = [] + + for record in records: + matches = True + + # Handle general search across text fields + if "search" in filters: + search_term = str(filters["search"]).lower() + if search_term: + # Search in all string fields + found = False + for key, value in record.items(): + if isinstance(value, str) and search_term in value.lower(): + found = True + break + elif isinstance(value, (int, float)) and search_term in str(value): + found = True + break + if not found: + matches = False + + # Handle field-specific filters + for field_name, filter_value in filters.items(): + if field_name == "search": + continue # Already handled above + + if field_name not in record: + matches = False + break + + record_value = record.get(field_name) + + # Handle simple value (equals operator) + if not isinstance(filter_value, dict): + if record_value != filter_value: + matches = False + break + continue + + # Handle filter with operator + operator = filter_value.get("operator", "equals") + filter_val = filter_value.get("value") + + if operator in ["equals", "eq"]: + if record_value != filter_val: + matches = False + break + + elif operator == "contains": + record_str = str(record_value).lower() if record_value is not None else "" + filter_str = str(filter_val).lower() if filter_val is not None else "" + if filter_str not in record_str: + matches = False + break + + elif operator == "startsWith": + record_str = str(record_value).lower() if record_value is not None else "" + filter_str = str(filter_val).lower() if filter_val is not None else "" + if not record_str.startswith(filter_str): + matches = False + break + + elif operator == "endsWith": + record_str = str(record_value).lower() if record_value is not None else "" + filter_str = str(filter_val).lower() if filter_val is not None else "" + if not record_str.endswith(filter_str): + matches = False + break + + elif operator == "gt": + try: + record_num = float(record_value) if record_value is not None else float('-inf') + filter_num = float(filter_val) if filter_val is not None else float('-inf') + if record_num <= filter_num: + matches = False + break + except (ValueError, TypeError): + matches = False + break + + elif operator == "gte": + try: + record_num = float(record_value) if record_value is not None else float('-inf') + filter_num = float(filter_val) if filter_val is not None else float('-inf') + if record_num < filter_num: + matches = False + break + except (ValueError, TypeError): + matches = False + break + + elif operator == "lt": + try: + record_num = float(record_value) if record_value is not None else float('inf') + filter_num = float(filter_val) if filter_val is not None else float('inf') + if record_num >= filter_num: + matches = False + break + except (ValueError, TypeError): + matches = False + break + + elif operator == "lte": + try: + record_num = float(record_value) if record_value is not None else float('inf') + filter_num = float(filter_val) if filter_val is not None else float('inf') + if record_num > filter_num: + matches = False + break + except (ValueError, TypeError): + matches = False + break + + elif operator == "in": + if not isinstance(filter_val, list): + filter_val = [filter_val] + if record_value not in filter_val: + matches = False + break + + elif operator == "notIn": + if not isinstance(filter_val, list): + filter_val = [filter_val] + if record_value in filter_val: + matches = False + break + + else: + # Unknown operator - default to equals + if record_value != filter_val: + matches = False + break + + if matches: + filtered.append(record) + + return filtered def _applySorting(self, records: List[Dict[str, Any]], sortFields: List[Any]) -> List[Dict[str, Any]]: """Apply multi-level sorting to records using stable sort (sorts from least to most significant field)."""