diff --git a/backend/open_webui/models/access_grants.py b/backend/open_webui/models/access_grants.py index 03a7ef7202..10c8fbfa3e 100644 --- a/backend/open_webui/models/access_grants.py +++ b/backend/open_webui/models/access_grants.py @@ -80,73 +80,6 @@ class AccessGrantResponse(BaseModel): #################### -def access_control_to_grants( - resource_type: str, - resource_id: str, - access_control: Optional[dict], -) -> list[dict]: - """ - Convert an old-style access_control JSON dict to a flat list of grant dicts. - - Semantics: - - None → public read (user:* read) — except files which are private - - {} → private/owner-only (no grants) - - {read: {group_ids, user_ids}, write: {group_ids, user_ids}} → specific grants - - Returns a list of dicts with keys: resource_type, resource_id, principal_type, principal_id, permission - """ - grants = [] - - if access_control is None: - # NULL → public read (user:* for read) - # Exception: files with NULL are private (owner-only), no grants needed - if resource_type != 'file': - grants.append( - { - 'resource_type': resource_type, - 'resource_id': resource_id, - 'principal_type': 'user', - 'principal_id': '*', - 'permission': 'read', - } - ) - return grants - - # {} → private/owner-only, no grants - if not access_control: - return grants - - # Parse structured permissions - for permission in ['read', 'write']: - perm_data = access_control.get(permission, {}) - if not perm_data: - continue - - for group_id in perm_data.get('group_ids', []): - grants.append( - { - 'resource_type': resource_type, - 'resource_id': resource_id, - 'principal_type': 'group', - 'principal_id': group_id, - 'permission': permission, - } - ) - - for user_id in perm_data.get('user_ids', []): - grants.append( - { - 'resource_type': resource_type, - 'resource_id': resource_id, - 'principal_type': 'user', - 'principal_id': user_id, - 'permission': permission, - } - ) - - return grants - - def normalize_access_grants(access_grants: Optional[list]) -> list[dict]: """ Normalize direct access_grants payloads from API forms. @@ -273,118 +206,12 @@ def strip_anyone_access_grants(access_grants: Optional[list]) -> list: ] -def grants_to_access_control(grants: list) -> Optional[dict]: - """ - Convert a list of grant objects (AccessGrantModel or AccessGrantResponse) - back to the old-style access_control JSON dict for backward compatibility. - - Semantics: - - [] (empty) → {} (private/owner-only) - - Contains user:*:read → None (public), but write grants are preserved - - Otherwise → {read: {group_ids, user_ids}, write: {group_ids, user_ids}} - - Note: "public" (user:*:read) still allows additional write permissions - to coexist. When the wildcard read is present the function returns None - for the legacy dict, so callers that need write info should inspect the - grants list directly. - """ - if not grants: - return {} # No grants = private/owner-only - - result = { - 'read': {'group_ids': [], 'user_ids': []}, - 'write': {'group_ids': [], 'user_ids': []}, - } - - is_public = False - for grant in grants: - if grant.principal_type == 'user' and grant.principal_id == '*' and grant.permission == 'read': - is_public = True - continue # Don't add wildcard to user_ids list - - if grant.permission not in ('read', 'write'): - continue - - if grant.principal_type == 'group': - if grant.principal_id not in result[grant.permission]['group_ids']: - result[grant.permission]['group_ids'].append(grant.principal_id) - elif grant.principal_type == 'user': - if grant.principal_id not in result[grant.permission]['user_ids']: - result[grant.permission]['user_ids'].append(grant.principal_id) - - if is_public: - return None # Public read access - - return result - - #################### # Table Operations #################### class AccessGrantsTable: - async def grant_access( - self, - resource_type: str, - resource_id: str, - principal_type: str, - principal_id: str, - permission: str, - db: Optional[AsyncSession] = None, - ) -> Optional[AccessGrantModel]: - """Add a single access grant. Idempotent (ignores duplicates).""" - async with get_async_db_context(db) as db: - # Check for existing grant - result = await db.execute( - select(AccessGrant).filter_by( - resource_type=resource_type, - resource_id=resource_id, - principal_type=principal_type, - principal_id=principal_id, - permission=permission, - ) - ) - existing = result.scalars().first() - if existing: - return AccessGrantModel.model_validate(existing) - - grant = AccessGrant( - id=str(uuid.uuid4()), - resource_type=resource_type, - resource_id=resource_id, - principal_type=principal_type, - principal_id=principal_id, - permission=permission, - created_at=int(time.time()), - ) - db.add(grant) - await db.commit() - return AccessGrantModel.model_validate(grant) - - async def revoke_access( - self, - resource_type: str, - resource_id: str, - principal_type: str, - principal_id: str, - permission: str, - db: Optional[AsyncSession] = None, - ) -> bool: - """Remove a single access grant.""" - async with get_async_db_context(db) as db: - result = await db.execute( - delete(AccessGrant).filter_by( - resource_type=resource_type, - resource_id=resource_id, - principal_type=principal_type, - principal_id=principal_id, - permission=permission, - ) - ) - await db.commit() - return result.rowcount > 0 - async def revoke_all_access( self, resource_type: str, @@ -402,44 +229,6 @@ class AccessGrantsTable: await db.commit() return result.rowcount - async def set_access_control( - self, - resource_type: str, - resource_id: str, - access_control: Optional[dict], - db: Optional[AsyncSession] = None, - ) -> list[AccessGrantModel]: - """ - Replace all grants for a resource from an access_control JSON dict. - This is the primary bridge for backward compat with the frontend. - """ - async with get_async_db_context(db) as db: - # Delete all existing grants for this resource - await db.execute( - delete(AccessGrant).filter_by( - resource_type=resource_type, - resource_id=resource_id, - ) - ) - - # Convert JSON to grant dicts - grant_dicts = access_control_to_grants(resource_type, resource_id, access_control) - - # Insert new grants - results = [] - for grant_dict in grant_dicts: - grant = AccessGrant( - id=str(uuid.uuid4()), - **grant_dict, - created_at=int(time.time()), - ) - db.add(grant) - results.append(grant) - - await db.commit() - - return [AccessGrantModel.model_validate(g) for g in results] - async def set_access_grants( self, resource_type: str, @@ -477,27 +266,6 @@ class AccessGrantsTable: await db.commit() return [AccessGrantModel.model_validate(g) for g in results] - async def get_access_control( - self, - resource_type: str, - resource_id: str, - db: Optional[AsyncSession] = None, - ) -> Optional[dict]: - """ - Reconstruct the old-style access_control JSON dict from grants. - For backward compat with the frontend. - """ - async with get_async_db_context(db) as db: - result = await db.execute( - select(AccessGrant).filter_by( - resource_type=resource_type, - resource_id=resource_id, - ) - ) - grants = result.scalars().all() - grant_models = [AccessGrantModel.model_validate(g) for g in grants] - return grants_to_access_control(grant_models) - async def get_grants_by_resource( self, resource_type: str, diff --git a/backend/open_webui/models/auths.py b/backend/open_webui/models/auths.py index 629c5eb6c5..80df2e4a4a 100644 --- a/backend/open_webui/models/auths.py +++ b/backend/open_webui/models/auths.py @@ -163,18 +163,6 @@ class AuthsTable: return return resolved - async def authenticate_user_by_api_key( - self, - api_key: str, - db: AsyncSession | None = None, - ) -> UserModel | None: - """Look up the user that owns the given API key.""" - log.info('authenticate_user_by_api_key') - if not api_key: - return - # delegate to the Users model for the actual lookup - return await Users.get_user_by_api_key(api_key, db=db) - async def authenticate_user_by_email( self, email: str, diff --git a/backend/open_webui/models/calendar.py b/backend/open_webui/models/calendar.py index 52470f1178..56979cad34 100644 --- a/backend/open_webui/models/calendar.py +++ b/backend/open_webui/models/calendar.py @@ -822,21 +822,6 @@ class CalendarEventAttendeeTable: await db.commit() return CalendarEventAttendeeModel.model_validate(att) - async def get_attendees_by_event( - self, event_id: str, db: Optional[AsyncSession] = None - ) -> list[CalendarEventAttendeeModel]: - async with get_async_db_context(db) as db: - result = await db.execute(select(CalendarEventAttendee).filter(CalendarEventAttendee.event_id == event_id)) - return [CalendarEventAttendeeModel.model_validate(r) for r in result.scalars().all()] - - async def get_events_by_attendee(self, user_id: str, db: Optional[AsyncSession] = None) -> list[str]: - """Return event IDs where user is an attendee.""" - async with get_async_db_context(db) as db: - result = await db.execute( - select(CalendarEventAttendee.event_id).filter(CalendarEventAttendee.user_id == user_id) - ) - return [r[0] for r in result.all()] - Calendars = CalendarTable() CalendarEvents = CalendarEventTable() diff --git a/backend/open_webui/models/channels.py b/backend/open_webui/models/channels.py index c30e2347b9..d2235390c9 100644 --- a/backend/open_webui/models/channels.py +++ b/backend/open_webui/models/channels.py @@ -563,26 +563,6 @@ class ChannelTable: await db.commit() return channel_member - async def leave_channel(self, channel_id: str, user_id: str, db: Optional[AsyncSession] = None) -> bool: - async with get_async_db_context(db) as db: - result = await db.execute( - select(ChannelMember).filter( - ChannelMember.channel_id == channel_id, - ChannelMember.user_id == user_id, - ) - ) - membership = result.scalars().first() - if not membership: - return False - - membership.status = 'left' - membership.is_active = False - membership.left_at = int(time.time_ns()) - membership.updated_at = int(time.time_ns()) - - await db.commit() - return True - async def get_member_by_channel_and_user_id( self, channel_id: str, user_id: str, db: Optional[AsyncSession] = None ) -> Optional[ChannelMemberModel]: @@ -604,30 +584,6 @@ class ChannelTable: memberships = result.scalars().all() return [ChannelMemberModel.model_validate(membership) for membership in memberships] - async def pin_channel( - self, - channel_id: str, - user_id: str, - is_pinned: bool, - db: Optional[AsyncSession] = None, - ) -> bool: - async with get_async_db_context(db) as db: - result = await db.execute( - select(ChannelMember).filter( - ChannelMember.channel_id == channel_id, - ChannelMember.user_id == user_id, - ) - ) - membership = result.scalars().first() - if not membership: - return False - - membership.is_channel_pinned = is_pinned - membership.updated_at = int(time.time_ns()) - - await db.commit() - return True - async def update_member_last_read_at( self, channel_id: str, user_id: str, db: Optional[AsyncSession] = None ) -> bool: @@ -695,23 +651,6 @@ class ChannelTable: except Exception: return None - async def get_channels_by_file_id(self, file_id: str, db: Optional[AsyncSession] = None) -> list[ChannelModel]: - async with get_async_db_context(db) as db: - result = await db.execute(select(ChannelFile).filter(ChannelFile.file_id == file_id)) - channel_files = result.scalars().all() - channel_ids = [cf.channel_id for cf in channel_files] - result = await db.execute(select(Channel).filter(Channel.id.in_(channel_ids))) - channels = result.scalars().all() - grants_map = await AccessGrants.get_grants_by_resources('channel', channel_ids, db=db) - return [ - await self._to_channel_model( - channel, - access_grants=grants_map.get(channel.id, []), - db=db, - ) - for channel in channels - ] - async def get_channels_by_file_id_and_user_id( self, file_id: str, user_id: str, db: Optional[AsyncSession] = None ) -> list[ChannelModel]: @@ -898,17 +837,6 @@ class ChannelTable: except Exception: return False - async def remove_file_from_channel_by_id( - self, channel_id: str, file_id: str, db: Optional[AsyncSession] = None - ) -> bool: - try: - async with get_async_db_context(db) as db: - await db.execute(delete(ChannelFile).filter_by(channel_id=channel_id, file_id=file_id)) - await db.commit() - return True - except Exception: - return False - async def delete_channel_by_id(self, id: str, db: Optional[AsyncSession] = None) -> bool: async with get_async_db_context(db) as db: await AccessGrants.revoke_all_access('channel', id, db=db) diff --git a/backend/open_webui/models/chat_messages.py b/backend/open_webui/models/chat_messages.py index 4977df70c3..0e6c0fb691 100644 --- a/backend/open_webui/models/chat_messages.py +++ b/backend/open_webui/models/chat_messages.py @@ -491,12 +491,6 @@ class ChatMessageTable: chat_ids = result.all() return [chat_id for chat_id, _ in chat_ids] - async def delete_messages_by_chat_id(self, chat_id: str, db: Optional[AsyncSession] = None) -> bool: - async with get_async_db_context(db) as db: - await db.execute(delete(ChatMessage).filter_by(chat_id=chat_id)) - await db.commit() - return True - async def delete_message_ids_by_chat_id( self, chat_id: str, @@ -749,21 +743,6 @@ class ChatMessageTable: 'active_days': len(active_days), } - async def get_user_first_message_created_at( - self, - user_id: str, - db: Optional[AsyncSession] = None, - ) -> Optional[int]: - async with get_async_db_context(db) as db: - result = await db.execute( - select(func.min(ChatMessage.created_at)).filter( - ChatMessage.user_id == user_id, - ChatMessage.created_at.isnot(None), - ) - ) - value = result.scalar() - return int(value) if value else None - async def get_user_daily_usage( self, user_id: str, diff --git a/backend/open_webui/models/chats.py b/backend/open_webui/models/chats.py index 9854af6d3a..e9947c7c70 100644 --- a/backend/open_webui/models/chats.py +++ b/backend/open_webui/models/chats.py @@ -12,7 +12,7 @@ from open_webui.internal.db import Base, JSONField, get_async_db_context from open_webui.models.automations import AutomationRun from open_webui.models.chat_messages import ChatMessage, ChatMessages from open_webui.models.folders import Folders -from open_webui.models.tags import Tag, TagModel, Tags +from open_webui.models.tags import Tag, Tags from open_webui.utils.misc import get_output_text, sanitize_data_for_db, sanitize_text_for_db from pydantic import BaseModel, ConfigDict, field_validator from sqlalchemy import ( @@ -289,14 +289,6 @@ class ChatTitleIdResponse(BaseModel): active: bool = False -class SharedChatResponse(BaseModel): - id: str - title: str - share_id: str | None = None - updated_at: int - created_at: int - - class ChatListResponse(BaseModel): items: list[ChatModel] total: int @@ -1356,19 +1348,6 @@ class ChatTable: result = await session.execute(stmt.where(Chat.meta['internal'].as_boolean().is_not(True))) return result.scalar() or 0 - async def get_shared_chat_list_by_user_id( - self, - user_id: str, - filter: dict | None = None, - skip: int = 0, - limit: int = 50, - db: AsyncSession | None = None, - ) -> list[SharedChatResponse]: - """Delegate to SharedChats for listing shared chats by user.""" - from open_webui.models.shared_chats import SharedChats - - return await SharedChats.get_by_user_id(user_id, filter=filter, skip=skip, limit=limit, db=db) - async def get_chat_list_by_user_id( self, user_id: str, @@ -1474,20 +1453,6 @@ class ChatTable: for chat in all_chats ] - async def get_chat_list_by_chat_ids( - self, - chat_ids: list[str], - skip: int = 0, - limit: int = 50, - db: AsyncSession | None = None, - ) -> list[ChatModel]: - async with get_async_db_context(db) as session: - stmt = select(Chat).filter(Chat.id.in_(chat_ids)).filter_by(archived=False) - stmt = stmt.where(Chat.meta['internal'].as_boolean().is_not(True)) - result = await session.execute(stmt.order_by(Chat.updated_at.desc())) - all_chats = result.scalars().all() - return [ChatModel.model_validate(chat) for chat in all_chats] - async def get_chat_metas_by_chat_ids( self, chat_ids: list[str], @@ -2134,16 +2099,6 @@ class ChatTable: except Exception: return None - async def get_chat_tags_by_id_and_user_id( - self, id: str, user_id: str, db: AsyncSession | None = None - ) -> list[TagModel]: - async with get_async_db_context(db) as session: - stmt = select(Chat.meta).where(Chat.id == id) - result = await session.execute(stmt) - meta = result.scalar_one_or_none() - tag_ids = (meta or {}).get('tags', []) - return await Tags.get_tags_by_ids_and_user_id(tag_ids, user_id, db=session) - async def get_chat_list_by_user_id_and_tag_name( self, user_id: str, @@ -2280,17 +2235,6 @@ class ChatTable: orphans = [tag_id for tag_id in tag_ids if counts.get(tag_id, 0) <= threshold] await Tags.delete_tags_by_ids_and_user_id(orphans, user_id, db=session) - async def count_chats_by_folder_id_and_user_id( - self, folder_id: str, user_id: str, db: AsyncSession | None = None - ) -> int: - async with get_async_db_context(db) as session: - stmt = select(func.count(Chat.id)).filter_by(user_id=user_id, folder_id=folder_id) - result = await session.execute(stmt.where(Chat.meta['internal'].as_boolean().is_not(True))) - count = result.scalar() - - log.info("Count of chats for folder '%s': %s", folder_id, count) - return count - async def count_chats_by_folder_ids_and_user_id( self, folder_ids: list[str], user_id: str, db: AsyncSession | None = None ) -> int: @@ -2498,15 +2442,6 @@ class ChatTable: all_chat_files = result.scalars().all() return [ChatFileModel.model_validate(chat_file) for chat_file in all_chat_files] - async def delete_chat_file(self, chat_id: str, file_id: str, db: AsyncSession | None = None) -> bool: - try: - async with get_async_db_context(db) as session: - await session.execute(delete(ChatFile).filter_by(chat_id=chat_id, file_id=file_id)) - await session.commit() - return True - except Exception: - return False - async def get_shared_chat_ids_by_file_id(self, file_id: str, db: AsyncSession | None = None) -> list[str]: """Return IDs of chats that contain this file and have an active share link.""" async with get_async_db_context(db) as session: diff --git a/backend/open_webui/models/feedbacks.py b/backend/open_webui/models/feedbacks.py index ca1fe39b38..e21bb0c3d4 100644 --- a/backend/open_webui/models/feedbacks.py +++ b/backend/open_webui/models/feedbacks.py @@ -197,21 +197,6 @@ class FeedbackTable: except Exception: return None - async def get_feedbacks_by_chat_id(self, chat_id: str, db: Optional[AsyncSession] = None) -> list[FeedbackModel]: - """Get all feedbacks for a specific chat.""" - try: - async with get_async_db_context(db) as db: - # meta.chat_id stores the chat reference - result = await db.execute( - select(Feedback) - .filter(Feedback.meta['chat_id'].as_string() == chat_id) - .order_by(Feedback.created_at.desc()) - ) - feedbacks = result.scalars().all() - return [FeedbackModel.model_validate(fb) for fb in feedbacks] - except Exception: - return [] - async def get_feedback_items( self, filter: dict = {}, @@ -422,11 +407,6 @@ class FeedbackTable: for date_str, counts in sorted(daily_counts.items()) ] - async def get_feedbacks_by_type(self, type: str, db: Optional[AsyncSession] = None) -> list[FeedbackModel]: - async with get_async_db_context(db) as db: - result = await db.execute(select(Feedback).filter_by(type=type).order_by(Feedback.updated_at.desc())) - return [FeedbackModel.model_validate(feedback) for feedback in result.scalars().all()] - async def get_feedbacks_by_user_id( self, user_id: str, diff --git a/backend/open_webui/models/files.py b/backend/open_webui/models/files.py index d1f61c7cc7..c39d2985fd 100644 --- a/backend/open_webui/models/files.py +++ b/backend/open_webui/models/files.py @@ -179,27 +179,6 @@ class FilesTable: except Exception: return None - async def get_file_metadata_by_id(self, id: str, db: AsyncSession | None = None) -> FileMetadataResponse | None: - async with get_async_db_context(db) as db: - try: - file = await db.get(File, id) - if not file: - return None - return FileMetadataResponse( - id=file.id, - hash=file.hash, - meta=file.meta, - created_at=file.created_at, - updated_at=file.updated_at, - ) - except Exception: - return None - - async def get_files(self, db: AsyncSession | None = None) -> list[FileModel]: - async with get_async_db_context(db) as db: - result = await db.execute(select(File)) - return [FileModel.model_validate(file) for file in result.scalars().all()] - async def count_files_by_user_id( self, user_id: str | None = None, @@ -226,31 +205,6 @@ class FilesTable: result = await db.execute(select(File).filter(File.id.in_(ids)).order_by(File.updated_at.desc())) return [FileModel.model_validate(file) for file in result.scalars().all()] - async def get_file_metadatas_by_ids( - self, ids: list[str], db: AsyncSession | None = None - ) -> list[FileMetadataResponse]: - async with get_async_db_context(db) as db: - result = await db.execute( - select(File.id, File.hash, File.meta, File.created_at, File.updated_at) - .filter(File.id.in_(ids)) - .order_by(File.updated_at.desc()) - ) - return [ - FileMetadataResponse( - id=row.id, - hash=row.hash, - meta=row.meta, - created_at=row.created_at, - updated_at=row.updated_at, - ) - for row in result.all() - ] - - async def get_files_by_user_id(self, user_id: str, db: AsyncSession | None = None) -> list[FileModel]: - async with get_async_db_context(db) as db: - result = await db.execute(select(File).filter_by(user_id=user_id)) - return [FileModel.model_validate(file) for file in result.scalars().all()] - async def get_file_list( self, user_id: str | None = None, diff --git a/backend/open_webui/models/folders.py b/backend/open_webui/models/folders.py index 24bb5e5af0..e68a77d5ab 100644 --- a/backend/open_webui/models/folders.py +++ b/backend/open_webui/models/folders.py @@ -442,22 +442,5 @@ class FolderTable: results = list(results.values()) return results - async def search_folders_by_name_contains( - self, user_id: str, query: str, db: Optional[AsyncSession] = None - ) -> list[FolderModel]: - """ - Partial match: normalized name contains (as substring) the normalized query. - """ - normalized_query = self.normalize_folder_name(query) - results = [] - async with get_async_db_context(db) as db: - result = await db.execute(select(Folder).filter_by(user_id=user_id)) - folders = result.scalars().all() - for folder in folders: - norm_name = self.normalize_folder_name(folder.name) - if normalized_query in norm_name: - results.append(FolderModel.model_validate(folder)) - return results - Folders = FolderTable() diff --git a/backend/open_webui/models/functions.py b/backend/open_webui/models/functions.py index 573880d521..2fa0d19053 100644 --- a/backend/open_webui/models/functions.py +++ b/backend/open_webui/models/functions.py @@ -269,11 +269,6 @@ class FunctionsTable: result = await db.execute(select(Function).filter_by(type=type)) return [FunctionModel.model_validate(function) for function in result.scalars().all()] - async def get_global_filter_functions(self, db: AsyncSession | None = None) -> list[FunctionModel]: - async with get_async_db_context(db) as db: - result = await db.execute(select(Function).filter_by(type='filter', is_active=True, is_global=True)) - return [FunctionModel.model_validate(function) for function in result.scalars().all()] - async def get_active_function_ids_by_type( self, type: str, db: AsyncSession | None = None ) -> list[tuple[str, bool]]: @@ -286,11 +281,6 @@ class FunctionsTable: """Return (id, is_global) for active filters without fetching plugin source.""" return await self.get_active_function_ids_by_type('filter', db=db) - async def get_global_action_functions(self, db: AsyncSession | None = None) -> list[FunctionModel]: - async with get_async_db_context(db) as db: - result = await db.execute(select(Function).filter_by(type='action', is_active=True, is_global=True)) - return [FunctionModel.model_validate(function) for function in result.scalars().all()] - async def get_function_valves_by_id(self, id: str, db: AsyncSession | None = None) -> dict | None: async with get_async_db_context(db) as db: try: diff --git a/backend/open_webui/models/groups.py b/backend/open_webui/models/groups.py index 611afe576d..85e89a5c25 100644 --- a/backend/open_webui/models/groups.py +++ b/backend/open_webui/models/groups.py @@ -128,11 +128,6 @@ class GroupUpdateForm(GroupForm): pass -class GroupListResponse(BaseModel): - items: list[GroupResponse] = [] - total: int = 0 - - class GroupTable: def _ensure_default_share_config(self, group_data: dict) -> dict: """Ensure the group data dict has a default share config if not already set.""" @@ -247,61 +242,6 @@ class GroupTable: for group, count in rows ] - async def search_groups( - self, - filter: Optional[dict] = None, - skip: int = 0, - limit: int = 30, - db: Optional[AsyncSession] = None, - ) -> GroupListResponse: - async with get_async_db_context(db) as db: - stmt = select(Group) - - if filter: - if 'query' in filter: - stmt = stmt.filter(Group.name.ilike(f'%{filter["query"]}%')) - if 'member_id' in filter: - stmt = stmt.filter( - Group.id.in_(select(GroupMember.group_id).where(GroupMember.user_id == filter['member_id'])) - ) - - if 'share' in filter: - share_value = filter['share'] - stmt = stmt.filter(Group.data.op('->>')('share') == str(share_value)) - - # Get total count - count_result = await db.execute(select(func.count()).select_from(stmt.subquery())) - total = count_result.scalar() - - member_count = ( - select(func.count(GroupMember.user_id)) - .where(GroupMember.group_id == Group.id) - .correlate(Group) - .scalar_subquery() - .label('member_count') - ) - result = await db.execute( - select(Group, member_count) - .where(Group.id.in_(select(stmt.subquery().c.id))) - .order_by(Group.updated_at.desc()) - .offset(skip) - .limit(limit) - ) - rows = result.all() - - return { - 'items': [ - GroupResponse.model_validate( - { - **GroupModel.model_validate(group).model_dump(), - 'member_count': count or 0, - } - ) - for group, count in rows - ], - 'total': total, - } - async def get_groups_by_member_id(self, user_id: str, db: Optional[AsyncSession] = None) -> list[GroupModel]: async with get_async_db_context(db) as db: result = await db.execute( @@ -352,22 +292,6 @@ class GroupTable: return [m[0] for m in members] - async def get_group_user_ids_by_ids( - self, group_ids: list[str], db: Optional[AsyncSession] = None - ) -> dict[str, list[str]]: - async with get_async_db_context(db) as db: - result = await db.execute( - select(GroupMember.group_id, GroupMember.user_id).filter(GroupMember.group_id.in_(group_ids)) - ) - members = result.all() - - group_user_ids: dict[str, list[str]] = {group_id: [] for group_id in group_ids} - - for group_id, user_id in members: - group_user_ids[group_id].append(user_id) - - return group_user_ids - async def set_group_user_ids_by_id( self, group_id: str, user_ids: list[str], db: Optional[AsyncSession] = None ) -> None: @@ -397,18 +321,6 @@ class GroupTable: count = result.scalar() return count if count else 0 - async def get_group_member_counts_by_ids(self, ids: list[str], db: Optional[AsyncSession] = None) -> dict[str, int]: - if not ids: - return {} - async with get_async_db_context(db) as db: - result = await db.execute( - select(GroupMember.group_id, func.count(GroupMember.user_id)) - .filter(GroupMember.group_id.in_(ids)) - .group_by(GroupMember.group_id) - ) - rows = result.all() - return {group_id: count for group_id, count in rows} - async def update_group_by_id( self, id: str, @@ -441,16 +353,6 @@ class GroupTable: except Exception: return False - async def delete_all_groups(self, db: Optional[AsyncSession] = None) -> bool: - async with get_async_db_context(db) as db: - try: - await db.execute(delete(Group)) - await db.commit() - - return True - except Exception: - return False - async def remove_user_from_all_groups(self, user_id: str, db: Optional[AsyncSession] = None) -> bool: async with get_async_db_context(db) as db: try: diff --git a/backend/open_webui/models/memories.py b/backend/open_webui/models/memories.py index e04f16b962..1baea78ea6 100644 --- a/backend/open_webui/models/memories.py +++ b/backend/open_webui/models/memories.py @@ -123,25 +123,6 @@ class MemoriesTable: except Exception: return None - async def get_memory_by_id(self, id: str, db: AsyncSession | None = None) -> MemoryModel | None: - async with get_async_db_context(db) as db: - try: - memory = await db.get(Memory, id) - return MemoryModel.model_validate(memory) if memory else None - except Exception: - return None - - async def delete_memory_by_id(self, id: str, db: AsyncSession | None = None) -> bool: - async with get_async_db_context(db) as db: - try: - await db.execute(delete(Memory).filter_by(id=id)) - await db.commit() - - return True - - except Exception: - return False - async def delete_memories_by_user_id(self, user_id: str, db: AsyncSession | None = None) -> bool: async with get_async_db_context(db) as db: try: diff --git a/backend/open_webui/models/messages.py b/backend/open_webui/models/messages.py index 82ac3336dd..bded4ebffd 100644 --- a/backend/open_webui/models/messages.py +++ b/backend/open_webui/models/messages.py @@ -273,11 +273,6 @@ class MessageTable: ) return messages - async def get_reply_user_ids_by_message_id(self, id: str, db: Optional[AsyncSession] = None) -> list[str]: - async with get_async_db_context(db) as db: - result = await db.execute(select(Message.user_id).filter_by(parent_id=id)) - return [row[0] for row in result.all()] - async def get_messages_by_channel_id( self, channel_id: str, @@ -573,18 +568,6 @@ class MessageTable: await db.commit() return True - async def delete_reactions_by_id(self, id: str, db: Optional[AsyncSession] = None) -> bool: - async with get_async_db_context(db) as db: - await db.execute(delete(MessageReaction).filter_by(message_id=id)) - await db.commit() - return True - - async def delete_replies_by_id(self, id: str, db: Optional[AsyncSession] = None) -> bool: - async with get_async_db_context(db) as db: - await db.execute(delete(Message).filter_by(parent_id=id)) - await db.commit() - return True - async def delete_message_by_id(self, id: str, db: Optional[AsyncSession] = None) -> bool: async with get_async_db_context(db) as db: await db.execute(delete(Message).filter_by(id=id)) diff --git a/backend/open_webui/models/notes.py b/backend/open_webui/models/notes.py index e9c06021e1..c967480263 100644 --- a/backend/open_webui/models/notes.py +++ b/backend/open_webui/models/notes.py @@ -186,19 +186,6 @@ class NoteTable: await AccessGrants.set_access_grants('note', note.id, form_data.access_grants, db=db) return await self._to_note_model(new_note, db=db) - async def get_notes(self, skip: int = 0, limit: int = 50, db: Optional[AsyncSession] = None) -> list[NoteModel]: - async with get_async_db_context(db) as db: - stmt = select(Note).order_by(Note.updated_at.desc()) - if skip is not None: - stmt = stmt.offset(skip) - if limit is not None: - stmt = stmt.limit(limit) - result = await db.execute(stmt) - notes = result.scalars().all() - note_ids = [note.id for note in notes] - grants_map = await AccessGrants.get_grants_by_resources('note', note_ids, db=db) - return [await self._to_note_model(note, access_grants=grants_map.get(note.id, []), db=db) for note in notes] - async def search_notes( self, user_id: str, diff --git a/backend/open_webui/models/oauth_sessions.py b/backend/open_webui/models/oauth_sessions.py index 25a50ced55..d0ef4194f9 100644 --- a/backend/open_webui/models/oauth_sessions.py +++ b/backend/open_webui/models/oauth_sessions.py @@ -305,17 +305,6 @@ class OAuthSessionTable: log.error(f'Error deleting OAuth session: {e}') return False - async def delete_sessions_by_user_id(self, user_id: str, db: Optional[AsyncSession] = None) -> bool: - """Delete all OAuth sessions for a user""" - try: - async with get_async_db_context(db) as db: - await db.execute(delete(OAuthSession).filter_by(user_id=user_id)) - await db.commit() - return True - except Exception as e: - log.error(f'Error deleting OAuth sessions by user ID: {e}') - return False - async def delete_sessions_by_user_id_and_provider( self, user_id: str, provider: str, db: Optional[AsyncSession] = None ) -> bool: @@ -329,16 +318,5 @@ class OAuthSessionTable: log.error(f'Error deleting OAuth sessions for user {user_id} and provider {provider}: {e}') return False - async def delete_sessions_by_provider(self, provider: str, db: Optional[AsyncSession] = None) -> bool: - """Delete all OAuth sessions for a provider""" - try: - async with get_async_db_context(db) as db: - await db.execute(delete(OAuthSession).filter_by(provider=provider)) - await db.commit() - return True - except Exception as e: - log.error(f'Error deleting OAuth sessions by provider {provider}: {e}') - return False - OAuthSessions = OAuthSessionTable() diff --git a/backend/open_webui/models/prompt_history.py b/backend/open_webui/models/prompt_history.py index 074bb5d039..063bc5e196 100644 --- a/backend/open_webui/models/prompt_history.py +++ b/backend/open_webui/models/prompt_history.py @@ -8,7 +8,7 @@ from typing import Optional from open_webui.internal.db import Base, get_async_db_context from open_webui.models.users import UserResponse, Users from pydantic import BaseModel, ConfigDict -from sqlalchemy import JSON, BigInteger, Column, Index, Text, delete, func, select +from sqlalchemy import JSON, BigInteger, Column, Index, Text, delete, select from sqlalchemy.ext.asyncio import AsyncSession #################### @@ -133,18 +133,6 @@ class PromptHistoryTable: return PromptHistoryModel.model_validate(entry) return None - async def get_history_count( - self, - prompt_id: str, - db: Optional[AsyncSession] = None, - ) -> int: - """Get the number of history entries for a prompt.""" - async with get_async_db_context(db) as db: - result = await db.execute( - select(func.count()).select_from(PromptHistory).filter(PromptHistory.prompt_id == prompt_id) - ) - return result.scalar() - async def compute_diff( self, from_id: str, diff --git a/backend/open_webui/models/prompts.py b/backend/open_webui/models/prompts.py index 54e67ac2f4..1694d80171 100644 --- a/backend/open_webui/models/prompts.py +++ b/backend/open_webui/models/prompts.py @@ -409,74 +409,6 @@ class PromptsTable: return PromptListResponse(items=prompts, total=total) - async def update_prompt_by_command( - self, - command: str, - form_data: PromptForm, - user_id: str, - db: AsyncSession | None = None, - ) -> PromptModel | None: - if not command: - return None - try: # database transaction - async with get_async_db_context(db) as session: - result = await session.execute(select(Prompt).filter_by(command=command)) - prompt = result.scalars().first() - if not prompt: - return None - - latest_history = await PromptHistories.get_latest_history_entry(prompt.id, db=session) - parent_id = latest_history.id if latest_history else None - current_access_grants = await self._get_access_grants(prompt.id, db=session) - - # Check if content changed to decide on history creation - content_changed = ( - prompt.name != form_data.name - or prompt.content != form_data.content - or form_data.access_grants is not None - ) - - # Update prompt fields - prompt.name = form_data.name - prompt.content = form_data.content - prompt.data = form_data.data or prompt.data - prompt.meta = form_data.meta or prompt.meta - prompt.updated_at = int(time.time()) - if form_data.access_grants is not None: - await AccessGrants.set_access_grants('prompt', prompt.id, form_data.access_grants, db=session) - current_access_grants = await self._get_access_grants(prompt.id, db=session) - - await session.commit() - - # Create history entry only if content changed - if content_changed: - snapshot = { - 'name': form_data.name, - 'content': form_data.content, - 'command': command, - 'data': form_data.data or {}, - 'meta': form_data.meta or {}, - 'access_grants': [grant.model_dump() for grant in current_access_grants], - } - - history_entry = await PromptHistories.create_history_entry( - prompt_id=prompt.id, - snapshot=snapshot, - user_id=user_id, - parent_id=parent_id, - commit_message=form_data.commit_message, - db=db, - ) - - # Set as production if flag is True (default) - if form_data.is_production and history_entry: - prompt.version_id = history_entry.id - await session.commit() - - return await self._to_prompt_model(prompt, db=session) - except Exception: - return None - async def update_prompt_by_id( self, prompt_id: str, @@ -641,23 +573,6 @@ class PromptsTable: except Exception: return None - async def delete_prompt_by_command(self, command: str, db: AsyncSession | None = None) -> bool: - """Permanently delete a prompt and its history.""" - try: - async with get_async_db_context(db) as session: - result = await session.execute(select(Prompt).filter_by(command=command)) - prompt = result.scalars().first() - if prompt: - await PromptHistories.delete_history_by_prompt_id(prompt.id, db=session) - await AccessGrants.revoke_all_access('prompt', prompt.id, db=session) - - await session.delete(prompt) - await session.commit() - return True - return False - except Exception: - return False - async def delete_prompt_by_id(self, prompt_id: str, db: AsyncSession | None = None) -> bool: """Permanently delete a prompt and its history.""" try: diff --git a/backend/open_webui/models/shared_chats.py b/backend/open_webui/models/shared_chats.py index a6ceebb8b0..e0f3b8ba9c 100644 --- a/backend/open_webui/models/shared_chats.py +++ b/backend/open_webui/models/shared_chats.py @@ -119,17 +119,6 @@ class SharedChatsTable: return SharedChatModel.model_validate(shared_chat) return None - async def get_by_chat_id(self, chat_id: str, db: Optional[AsyncSession] = None) -> Optional[SharedChatModel]: - """Get the shared chat for a given original chat. Returns the most recent one.""" - async with get_async_db_context(db) as db: - result = await db.execute( - select(SharedChat).filter_by(chat_id=chat_id).order_by(SharedChat.updated_at.desc()).limit(1) - ) - shared_chat = result.scalars().first() - if shared_chat: - return SharedChatModel.model_validate(shared_chat) - return None - async def get_by_user_id( self, user_id: str, diff --git a/backend/open_webui/models/skills.py b/backend/open_webui/models/skills.py index cff4778394..0aa5f03472 100644 --- a/backend/open_webui/models/skills.py +++ b/backend/open_webui/models/skills.py @@ -154,15 +154,6 @@ class SkillsTable: except Exception: return None - async def get_skill_by_name(self, name: str, db: Optional[AsyncSession] = None) -> Optional[SkillModel]: - try: - async with get_async_db_context(db) as db: - result = await db.execute(select(Skill).filter_by(name=name)) - skill = result.scalars().first() - return await self._to_skill_model(skill, db=db) if skill else None - except Exception: - return None - async def get_skills( self, user_id: str | None = None, diff --git a/backend/open_webui/models/tags.py b/backend/open_webui/models/tags.py index 9f24d274bb..61917a1274 100644 --- a/backend/open_webui/models/tags.py +++ b/backend/open_webui/models/tags.py @@ -50,24 +50,6 @@ class TagChatIdForm(BaseModel): class TagTable: - async def insert_new_tag( - self, - name: str, - user_id: str, - db: AsyncSession | None = None, - ) -> TagModel | None: - """Create a new tag, deriving the id from the name.""" - async with get_async_db_context(db) as db: - tag_id = name.replace(' ', '_').lower() - try: - record = Tag(id=tag_id, user_id=user_id, name=name) - db.add(record) - await db.commit() - return TagModel.model_validate(record) if record else None - except Exception as e: - log.exception('Error inserting tag %r: %s', name, e) - return None # insertion failed - async def get_tag_by_name_and_user_id( self, name: str, user_id: str, db: AsyncSession | None = None ) -> TagModel | None: diff --git a/backend/open_webui/models/users.py b/backend/open_webui/models/users.py index abbbe122ea..e2df82142b 100644 --- a/backend/open_webui/models/users.py +++ b/backend/open_webui/models/users.py @@ -559,15 +559,6 @@ class UsersTable: row = (await session.execute(stmt)).scalars().first() return UserModel.model_validate(row) if row else None - async def get_num_users_active_today(self, db: AsyncSession | None = None) -> int | None: - async with get_async_db_context(db) as session: - current_timestamp = int(time.time()) - today_midnight_timestamp = current_timestamp - (current_timestamp % 86400) - result = await session.execute( - select(func.count()).select_from(User).where(User.last_active_at > today_midnight_timestamp) - ) - return result.scalar() - async def update_user_role_by_id(self, id: str, role: str, db: AsyncSession | None = None) -> UserModel | None: async with get_async_db_context(db) as session: user = await session.get(User, id)