mirror of
https://github.com/open-webui/open-webui.git
synced 2026-08-28 05:27:35 +00:00
refactor: remove unused database table methods
Fifty methods across the database table classes have no call site: no router, no util, no socket handler and no other model reaches them. They cover reads, deletes and access control writes that were either replaced by a different query or never wired up. Two access control converters and two response models were reachable only from those methods, so they go in the same pass rather than being left for the next one. One of the two response models was a shadow copy of a live model with the same name in another module, which is a trap worth closing. These are internal data access helpers, so no HTTP or socket surface changes. This removes around 800 lines of query code that still has to be read and kept correct every time those files are touched, and anything genuinely needed later is cheaper to write against the schema as it stands then than to carry as code nothing exercises.
This commit is contained in:
parent
8a42aa53e8
commit
837d8b53ef
21 changed files with 2 additions and 825 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue