chore: format
This commit is contained in:
1 parent
1f49ebf431
commit
6fce92aa12
139 files changed
+92569
-3249
No files matched your search
@@ -119,7 +119,13 @@ class AuthsTable:
|
||||
session.add(credential)
|
||||
|
||||
created_user = await Users.insert_new_user(
|
||||
new_id, name, email, profile_image_url, role, oauth=oauth, db=session,
|
||||
new_id,
|
||||
name,
|
||||
email,
|
||||
profile_image_url,
|
||||
role,
|
||||
oauth=oauth,
|
||||
db=session,
|
||||
)
|
||||
# persist both records and reload generated defaults
|
||||
await session.commit()
|
||||
@@ -127,7 +133,10 @@ class AuthsTable:
|
||||
return created_user if credential and created_user else None
|
||||
|
||||
async def authenticate_user(
|
||||
self, email: str, verify_password: callable, db: AsyncSession | None = None,
|
||||
self,
|
||||
email: str,
|
||||
verify_password: callable,
|
||||
db: AsyncSession | None = None,
|
||||
) -> UserModel | None:
|
||||
"""Verify email + password credentials and return the matching user."""
|
||||
log.info('authenticate_user: %s', email)
|
||||
@@ -144,7 +153,9 @@ class AuthsTable:
|
||||
return resolved
|
||||
|
||||
async def authenticate_user_by_api_key(
|
||||
self, api_key: str, db: AsyncSession | None = None,
|
||||
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')
|
||||
@@ -163,17 +174,19 @@ class AuthsTable:
|
||||
# single JOIN avoids N+1 — returns (Auth, User) tuple or None
|
||||
async with get_async_db_context(db) as session:
|
||||
joined_query = (
|
||||
select(Auth, User)
|
||||
.join(User, Auth.id == User.id)
|
||||
.where(Auth.email == email, Auth.active.is_(True))
|
||||
select(Auth, User).join(User, Auth.id == User.id).where(Auth.email == email, Auth.active.is_(True))
|
||||
)
|
||||
match = (await session.execute(joined_query)).first()
|
||||
if not match:
|
||||
return
|
||||
_, found_user = match
|
||||
return UserModel.model_validate(found_user)
|
||||
|
||||
async def update_email_by_id(
|
||||
self, user_id: str, email: str, db: AsyncSession | None = None,
|
||||
self,
|
||||
user_id: str,
|
||||
email: str,
|
||||
db: AsyncSession | None = None,
|
||||
) -> bool:
|
||||
"""Set a new email on the auth record and propagate to the user row."""
|
||||
async with get_async_db_context(db) as session:
|
||||
@@ -185,8 +198,12 @@ class AuthsTable:
|
||||
await Users.update_user_by_id(user_id, {'email': email}, db=session)
|
||||
return True
|
||||
# --- password modification ---
|
||||
|
||||
async def update_user_password_by_id(
|
||||
self, user_id: str, new_password: str, db: AsyncSession | None = None,
|
||||
self,
|
||||
user_id: str,
|
||||
new_password: str,
|
||||
db: AsyncSession | None = None,
|
||||
) -> bool:
|
||||
"""Set a new password hash for an existing user."""
|
||||
async with get_async_db_context(db) as session:
|
||||
@@ -198,7 +215,9 @@ class AuthsTable:
|
||||
return True
|
||||
|
||||
async def delete_auth_by_id(
|
||||
self, id: str, db: AsyncSession | None = None,
|
||||
self,
|
||||
id: str,
|
||||
db: AsyncSession | None = None,
|
||||
) -> bool:
|
||||
"""Remove a user and their auth credential in one transaction."""
|
||||
async with get_async_db_context(db) as session:
|
||||
|
||||
@@ -6,6 +6,7 @@ import json
|
||||
import logging
|
||||
import time
|
||||
import uuid
|
||||
|
||||
# local imports
|
||||
from open_webui.internal.db import Base, JSONField, get_async_db_context
|
||||
from open_webui.models.automations import AutomationRun
|
||||
@@ -358,9 +359,7 @@ class ChatTable:
|
||||
) -> list[ChatModel]:
|
||||
async with get_async_db_context(db) as session:
|
||||
# Validate folder_id references — clear any that don't exist
|
||||
folder_ids = {
|
||||
f.folder_id for f in chat_import_forms if f.folder_id
|
||||
}
|
||||
folder_ids = {f.folder_id for f in chat_import_forms if f.folder_id}
|
||||
existing = set()
|
||||
for fid in folder_ids:
|
||||
if await Folders.get_folder_by_id_and_user_id(fid, user_id, db=session):
|
||||
@@ -402,7 +401,10 @@ class ChatTable:
|
||||
return [ChatModel.model_validate(chat) for chat in chats]
|
||||
|
||||
async def update_chat_by_id(
|
||||
self, id: str, chat: dict, db: AsyncSession | None = None,
|
||||
self,
|
||||
id: str,
|
||||
chat: dict,
|
||||
db: AsyncSession | None = None,
|
||||
) -> ChatModel | None:
|
||||
"""Persist updated chat content, sanitizing null bytes."""
|
||||
try: # load the chat record for in-place mutation
|
||||
@@ -512,9 +514,7 @@ class ChatTable:
|
||||
except Exception as e:
|
||||
log.warning('Backfill failed for message %s in chat %s: %s', message_id, chat_id, e)
|
||||
|
||||
async def reconcile_messages_by_chat_id(
|
||||
self, chat_id: str, user_id: str, messages: dict[str, dict]
|
||||
) -> None:
|
||||
async def reconcile_messages_by_chat_id(self, chat_id: str, user_id: str, messages: dict[str, dict]) -> None:
|
||||
"""Sync ``chat_message`` rows with the committed JSON blob.
|
||||
|
||||
Upserts current messages via ``backfill_messages_by_chat_id``
|
||||
@@ -692,9 +692,12 @@ class ChatTable:
|
||||
await session.commit()
|
||||
await session.refresh(chat)
|
||||
return ChatModel.model_validate(chat) # return the updated original
|
||||
|
||||
# refresh helper
|
||||
async def update_shared_chat_by_chat_id(
|
||||
self, chat_id: str, db: AsyncSession | None = None,
|
||||
self,
|
||||
chat_id: str,
|
||||
db: AsyncSession | None = None,
|
||||
) -> ChatModel | None:
|
||||
"""Refresh the shared snapshot with current chat content."""
|
||||
from open_webui.models.shared_chats import SharedChats
|
||||
@@ -953,9 +956,12 @@ class ChatTable:
|
||||
)
|
||||
all_chats = result.scalars().all()
|
||||
return [ChatModel.model_validate(chat) for chat in all_chats]
|
||||
|
||||
# retrieve conversation
|
||||
async def get_chat_by_id(
|
||||
self, id: str, db: AsyncSession | None = None,
|
||||
self,
|
||||
id: str,
|
||||
db: AsyncSession | None = None,
|
||||
) -> ChatModel | None:
|
||||
"""Fetch a chat by PK, auto-sanitizing null bytes on read."""
|
||||
try:
|
||||
@@ -1034,6 +1040,7 @@ class ChatTable:
|
||||
result = await session.execute(select(Chat).order_by(Chat.updated_at.desc()))
|
||||
all_chats = result.scalars().all()
|
||||
return [ChatModel.model_validate(chat) for chat in all_chats]
|
||||
|
||||
# list user conversations
|
||||
async def get_chats_by_user_id(
|
||||
self,
|
||||
@@ -1082,6 +1089,7 @@ class ChatTable:
|
||||
'total': total,
|
||||
}
|
||||
)
|
||||
|
||||
# list pinned chats
|
||||
async def get_pinned_chats_by_user_id(
|
||||
self, user_id: str, db: AsyncSession | None = None
|
||||
@@ -1112,6 +1120,7 @@ class ChatTable:
|
||||
select(Chat).filter_by(user_id=user_id, archived=True).order_by(Chat.updated_at.desc())
|
||||
)
|
||||
return [ChatModel.model_validate(chat) for chat in result.scalars().all()]
|
||||
|
||||
# search user conversations
|
||||
async def get_chats_by_user_id_and_search_text(
|
||||
self,
|
||||
|
||||
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import time
|
||||
|
||||
# local imports
|
||||
from open_webui.internal.db import Base, JSONField, get_async_db_context
|
||||
from open_webui.utils.misc import sanitize_metadata
|
||||
@@ -46,6 +47,7 @@ class FileModel(BaseModel):
|
||||
created_at: int | None # timestamp in epoch
|
||||
updated_at: int | None # timestamp in epoch
|
||||
|
||||
|
||||
# --- metadata structures ---
|
||||
class FileMeta(BaseModel):
|
||||
name: str | None = None
|
||||
@@ -150,7 +152,9 @@ class FilesTable:
|
||||
return None # insertion failed
|
||||
|
||||
async def get_file_by_id(
|
||||
self, id: str, db: AsyncSession | None = None,
|
||||
self,
|
||||
id: str,
|
||||
db: AsyncSession | None = None,
|
||||
) -> FileModel | None:
|
||||
"""Look up a file by its primary key."""
|
||||
try:
|
||||
@@ -409,9 +413,7 @@ class FilesTable:
|
||||
|
||||
# Subquery: file IDs already linked to this knowledge base
|
||||
linked_ids = (
|
||||
select(KnowledgeFile.file_id)
|
||||
.filter(KnowledgeFile.knowledge_id == knowledge_id)
|
||||
.correlate(None)
|
||||
select(KnowledgeFile.file_id).filter(KnowledgeFile.knowledge_id == knowledge_id).correlate(None)
|
||||
)
|
||||
|
||||
stmt = (
|
||||
@@ -424,10 +426,7 @@ class FilesTable:
|
||||
.order_by(File.created_at.desc())
|
||||
)
|
||||
result = await db.execute(stmt)
|
||||
return [
|
||||
FileModelResponse.model_validate(f, from_attributes=True)
|
||||
for f in result.scalars().all()
|
||||
]
|
||||
return [FileModelResponse.model_validate(f, from_attributes=True) for f in result.scalars().all()]
|
||||
except Exception as e:
|
||||
log.warning(f'Error fetching pending files for knowledge {knowledge_id}: {e}')
|
||||
return []
|
||||
|
||||
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import time
|
||||
|
||||
# local imports
|
||||
from open_webui.internal.db import Base, JSONField, get_async_db_context
|
||||
from open_webui.models.users import UserModel, UserResponse, Users
|
||||
@@ -52,6 +53,7 @@ class FunctionModel(BaseModel):
|
||||
|
||||
model_config = ConfigDict(from_attributes=True) # allows ORM model binding
|
||||
|
||||
|
||||
# --- form / schema definitions ---
|
||||
class FunctionWithValvesModel(BaseModel):
|
||||
id: str
|
||||
|
||||
@@ -34,6 +34,7 @@ class MemoryModel(BaseModel):
|
||||
created_at: int # timestamp in epoch
|
||||
model_config = ConfigDict(from_attributes=True) # allows ORM mapping
|
||||
|
||||
|
||||
class MemoriesTable:
|
||||
async def insert_new_memory(
|
||||
self,
|
||||
|
||||
@@ -51,8 +51,7 @@ class ModelMeta(BaseModel):
|
||||
if v not in _warned_profile_urls:
|
||||
_warned_profile_urls.add(v)
|
||||
log.warning(
|
||||
'Clearing invalid profile_image_url stored in DB '
|
||||
'(likely a legacy SVG data-URI): %.80s…',
|
||||
'Clearing invalid profile_image_url stored in DB (likely a legacy SVG data-URI): %.80s…',
|
||||
v,
|
||||
)
|
||||
return None
|
||||
@@ -74,7 +73,6 @@ class ModelMeta(BaseModel):
|
||||
|
||||
|
||||
class Model(Base):
|
||||
|
||||
"""Workspace model entry — wraps an upstream LLM with custom params and metadata."""
|
||||
|
||||
__tablename__ = 'model'
|
||||
@@ -195,9 +193,7 @@ class ModelsTable:
|
||||
models: list[ModelModel] = []
|
||||
for model in all_models:
|
||||
try:
|
||||
models.append(
|
||||
await self._to_model_model(model, access_grants=grants_map.get(model.id, []), db=db)
|
||||
)
|
||||
models.append(await self._to_model_model(model, access_grants=grants_map.get(model.id, []), db=db))
|
||||
except Exception as exc:
|
||||
log.error('Skipping model %r during get_all_models due to error: %s', model.id, exc)
|
||||
return models
|
||||
|
||||
@@ -55,6 +55,8 @@ class PromptModel(BaseModel):
|
||||
access_grants: list[AccessGrantModel] = Field(default_factory=list)
|
||||
|
||||
model_config = ConfigDict(from_attributes=True) # allows ORM model binding
|
||||
|
||||
|
||||
# --- form / schema definitions ---
|
||||
# Forms
|
||||
####################
|
||||
@@ -107,26 +109,36 @@ class PromptsTable:
|
||||
)
|
||||
return PromptModel.model_validate(prompt_data)
|
||||
|
||||
async def insert_new_prompt(self, user_id: str, form_data: PromptForm, db: AsyncSession | None = None) -> PromptModel | None:
|
||||
async def insert_new_prompt(
|
||||
self, user_id: str, form_data: PromptForm, db: AsyncSession | None = None
|
||||
) -> PromptModel | None:
|
||||
now = int(time.time())
|
||||
prompt_id = str(uuid.uuid4())
|
||||
|
||||
async with get_async_db_context(db) as session:
|
||||
try:
|
||||
record = Prompt(
|
||||
id=prompt_id, user_id=user_id,
|
||||
command=form_data.command, name=form_data.name,
|
||||
id=prompt_id,
|
||||
user_id=user_id,
|
||||
command=form_data.command,
|
||||
name=form_data.name,
|
||||
content=form_data.content,
|
||||
data=form_data.data or {}, meta=form_data.meta or {},
|
||||
tags=form_data.tags or [], is_active=True,
|
||||
created_at=now, updated_at=now,
|
||||
data=form_data.data or {},
|
||||
meta=form_data.meta or {},
|
||||
tags=form_data.tags or [],
|
||||
is_active=True,
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
)
|
||||
session.add(record)
|
||||
await session.commit()
|
||||
await session.refresh(record) # populate generated defaults
|
||||
|
||||
await AccessGrants.set_access_grants(
|
||||
'prompt', prompt_id, form_data.access_grants, db=session,
|
||||
'prompt',
|
||||
prompt_id,
|
||||
form_data.access_grants,
|
||||
db=session,
|
||||
) # persist sharing rules
|
||||
|
||||
if not record: # shouldn't happen, but guard anyway
|
||||
@@ -145,8 +157,10 @@ class PromptsTable:
|
||||
}
|
||||
|
||||
history_entry = await PromptHistories.create_history_entry(
|
||||
prompt_id=prompt_id, snapshot=snapshot,
|
||||
user_id=user_id, parent_id=None,
|
||||
prompt_id=prompt_id,
|
||||
snapshot=snapshot,
|
||||
user_id=user_id,
|
||||
parent_id=None,
|
||||
commit_message=form_data.commit_message or 'Initial version',
|
||||
db=session,
|
||||
) # creates the first version entry
|
||||
@@ -178,9 +192,7 @@ class PromptsTable:
|
||||
async def get_prompt_by_command(self, command: str, db: AsyncSession | None = None) -> PromptModel | None:
|
||||
"""Look up a prompt by its unique slash-command string."""
|
||||
async with get_async_db_context(db) as session:
|
||||
match = (await session.execute(
|
||||
select(Prompt).where(Prompt.command == command)
|
||||
)).scalars().first()
|
||||
match = (await session.execute(select(Prompt).where(Prompt.command == command))).scalars().first()
|
||||
if match is None:
|
||||
return
|
||||
return await self._to_prompt_model(match, db=session)
|
||||
@@ -190,9 +202,15 @@ class PromptsTable:
|
||||
async def get_prompts(self, db: AsyncSession | None = None) -> list[PromptUserResponse]:
|
||||
"""Return all active prompts ordered by most recently updated."""
|
||||
async with get_async_db_context(db) as session:
|
||||
active = (await session.execute(
|
||||
select(Prompt).where(Prompt.is_active.is_(True)).order_by(Prompt.updated_at.desc())
|
||||
)).scalars().all()
|
||||
active = (
|
||||
(
|
||||
await session.execute(
|
||||
select(Prompt).where(Prompt.is_active.is_(True)).order_by(Prompt.updated_at.desc())
|
||||
)
|
||||
)
|
||||
.scalars()
|
||||
.all()
|
||||
)
|
||||
|
||||
user_ids = list(set(p.user_id for p in active))
|
||||
prompt_ids = [p.id for p in active]
|
||||
@@ -600,10 +618,13 @@ class PromptsTable:
|
||||
|
||||
return await self._to_prompt_model(prompt, db=session)
|
||||
except Exception as e: # connection error
|
||||
log.error(f"Failed to restore prompt version: {e}")
|
||||
log.error(f'Failed to restore prompt version: {e}')
|
||||
return None # restoration failed
|
||||
|
||||
async def toggle_prompt_active(
|
||||
self, prompt_id: str, db: AsyncSession | None = None,
|
||||
self,
|
||||
prompt_id: str,
|
||||
db: AsyncSession | None = None,
|
||||
) -> PromptModel | None:
|
||||
"""Flip the is_active flag on a prompt."""
|
||||
if not prompt_id:
|
||||
@@ -654,7 +675,7 @@ class PromptsTable:
|
||||
return True
|
||||
return False
|
||||
except Exception as err:
|
||||
log.error(f"Failed to delete prompt: {err}")
|
||||
log.error(f'Failed to delete prompt: {err}')
|
||||
return False # deletion failed
|
||||
|
||||
async def get_tags(self, db: AsyncSession | None = None) -> list[str]:
|
||||
|
||||
@@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
import logging
|
||||
import time
|
||||
import uuid
|
||||
|
||||
# local imports
|
||||
from open_webui.internal.db import Base, JSONField, get_async_db_context
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
@@ -37,6 +38,7 @@ class TagModel(BaseModel):
|
||||
meta: dict | None = None
|
||||
model_config = ConfigDict(from_attributes=True) # allows ORM model binding
|
||||
|
||||
|
||||
# --- tag schema forms ---
|
||||
# Forms
|
||||
####################
|
||||
@@ -49,7 +51,10 @@ class TagChatIdForm(BaseModel):
|
||||
|
||||
class TagTable:
|
||||
async def insert_new_tag(
|
||||
self, name: str, user_id: str, db: AsyncSession | None = None,
|
||||
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:
|
||||
|
||||
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import time
|
||||
|
||||
# local imports
|
||||
from open_webui.internal.db import Base, JSONField, get_async_db_context
|
||||
from open_webui.models.access_grants import AccessGrantModel, AccessGrants
|
||||
@@ -50,6 +51,7 @@ class ToolModel(BaseModel):
|
||||
|
||||
model_config = ConfigDict(from_attributes=True) # enables ORM mapping
|
||||
|
||||
|
||||
# --- tool request forms ---
|
||||
# Forms
|
||||
####################
|
||||
@@ -138,7 +140,9 @@ class ToolsTable:
|
||||
return None # creation failed
|
||||
|
||||
async def get_tool_by_id(
|
||||
self, id: str, db: AsyncSession | None = None,
|
||||
self,
|
||||
id: str,
|
||||
db: AsyncSession | None = None,
|
||||
) -> ToolModel | None:
|
||||
"""Fetch a single tool by primary key, including access grants."""
|
||||
try: # single PK lookup + access grants
|
||||
@@ -150,18 +154,14 @@ class ToolsTable:
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
async def get_tools_by_ids(
|
||||
self, tool_ids: list[str], db: AsyncSession | None = None
|
||||
) -> dict[str, ToolModel]:
|
||||
async def get_tools_by_ids(self, tool_ids: list[str], db: AsyncSession | None = None) -> dict[str, ToolModel]:
|
||||
"""Batch-fetch multiple tools by ID, returning a dict keyed by tool ID."""
|
||||
if not tool_ids:
|
||||
return {}
|
||||
async with get_async_db_context(db) as db:
|
||||
result = await db.execute(select(Tool).where(Tool.id.in_(tool_ids)))
|
||||
tools = result.scalars().all()
|
||||
grants_map = await AccessGrants.get_grants_by_resources(
|
||||
'tool', [tool.id for tool in tools], db=db
|
||||
)
|
||||
grants_map = await AccessGrants.get_grants_by_resources('tool', [tool.id for tool in tools], db=db)
|
||||
return {
|
||||
tool.id: await self._to_tool_model(tool, access_grants=grants_map.get(tool.id, []), db=db)
|
||||
for tool in tools
|
||||
|
||||
@@ -46,11 +46,11 @@ class UserSettings(BaseModel):
|
||||
class User(Base): # identity & profile
|
||||
"""One row per registered account — profile, role, and settings."""
|
||||
|
||||
__tablename__: str = 'user' # Identity & Credentials
|
||||
__tablename__: str = 'user' # Identity & Credentials
|
||||
id = Column(String, primary_key=True, unique=True) # unique user id
|
||||
email = Column(String, unique=True) # user email address
|
||||
username = Column(String(50), nullable=True) # custom handle
|
||||
role = Column(String, default="pending") # permissions role
|
||||
role = Column(String, default='pending') # permissions role
|
||||
name = Column(String, nullable=False) # display name
|
||||
|
||||
# Profile
|
||||
@@ -117,20 +117,16 @@ class UserModel(BaseModel):
|
||||
model_config = ConfigDict(
|
||||
from_attributes=True,
|
||||
)
|
||||
|
||||
# validation schema logic
|
||||
# --- model validators ---
|
||||
@model_validator(mode='after')
|
||||
def _ensure_profile_image(self) -> 'UserModel':
|
||||
"""Assign a generated avatar when no profile image is provided."""
|
||||
self.profile_image_url = (
|
||||
self.profile_image_url
|
||||
or _DEFAULT_PROFILE_IMAGE_URL.format(user_id=self.id)
|
||||
)
|
||||
self.profile_image_url = self.profile_image_url or _DEFAULT_PROFILE_IMAGE_URL.format(user_id=self.id)
|
||||
return self
|
||||
|
||||
|
||||
|
||||
|
||||
class UserStatusModel(UserModel):
|
||||
is_active: bool = False
|
||||
|
||||
@@ -303,31 +299,37 @@ class UsersTable:
|
||||
await session.commit()
|
||||
await session.refresh(result)
|
||||
return user if result else None
|
||||
|
||||
# database read methods
|
||||
# --- read / lookup operations ---
|
||||
async def get_user_by_id(
|
||||
self, id: str, db: AsyncSession | None = None,
|
||||
self,
|
||||
id: str,
|
||||
db: AsyncSession | None = None,
|
||||
) -> UserModel | None:
|
||||
"""Fetch a single user by primary key."""
|
||||
async with get_async_db_context(db) as session:
|
||||
user = await session.get(User, id)
|
||||
return UserModel.model_validate(user) if user else None
|
||||
|
||||
# api key auth helper
|
||||
async def get_user_by_api_key(
|
||||
self, api_key: str, db: AsyncSession | None = None,
|
||||
self,
|
||||
api_key: str,
|
||||
db: AsyncSession | None = None,
|
||||
) -> UserModel | None:
|
||||
"""Resolve a user from their API key via a JOIN on the api_key table."""
|
||||
async with get_async_db_context(db) as session:
|
||||
result = await session.execute(
|
||||
select(User)
|
||||
.join(ApiKey, User.id == ApiKey.user_id)
|
||||
.where(ApiKey.key == api_key),
|
||||
select(User).join(ApiKey, User.id == ApiKey.user_id).where(ApiKey.key == api_key),
|
||||
)
|
||||
user = result.scalars().first()
|
||||
return UserModel.model_validate(user) if user else None
|
||||
|
||||
async def get_user_by_email(
|
||||
self, email: str, db: AsyncSession | None = None,
|
||||
self,
|
||||
email: str,
|
||||
db: AsyncSession | None = None,
|
||||
) -> UserModel | None:
|
||||
"""Case-insensitive email lookup using SQL lower()."""
|
||||
async with get_async_db_context(db) as session:
|
||||
@@ -342,7 +344,10 @@ class UsersTable:
|
||||
|
||||
# --- oauth & integrations ---
|
||||
async def get_user_by_oauth_sub(
|
||||
self, provider: str, sub: str, db: AsyncSession | None = None,
|
||||
self,
|
||||
provider: str,
|
||||
sub: str,
|
||||
db: AsyncSession | None = None,
|
||||
) -> UserModel | None:
|
||||
"""Look up a user by OAuth provider + subject claim (dialect-aware JSON filter)."""
|
||||
async with get_async_db_context(db) as session:
|
||||
@@ -358,7 +363,10 @@ class UsersTable:
|
||||
return UserModel.model_validate(row) if row else None
|
||||
|
||||
async def get_user_by_scim_external_id(
|
||||
self, provider: str, external_id: str, db: AsyncSession | None = None,
|
||||
self,
|
||||
provider: str,
|
||||
external_id: str,
|
||||
db: AsyncSession | None = None,
|
||||
) -> UserModel | None:
|
||||
"""Look up a user by SCIM provider + external ID (dialect-aware JSON filter)."""
|
||||
async with get_async_db_context(db) as session:
|
||||
@@ -373,10 +381,12 @@ class UsersTable:
|
||||
row = (await session.execute(query)).scalars().first()
|
||||
return UserModel.model_validate(row) if row else None
|
||||
|
||||
|
||||
async def get_users(
|
||||
self, filter: dict | None = None, skip: int | None = None,
|
||||
limit: int | None = None, db: AsyncSession | None = None,
|
||||
self,
|
||||
filter: dict | None = None,
|
||||
skip: int | None = None,
|
||||
limit: int | None = None,
|
||||
db: AsyncSession | None = None,
|
||||
) -> dict:
|
||||
"""Paginated user listing with optional filters for role, group, and channel."""
|
||||
async with get_async_db_context(db) as session:
|
||||
@@ -531,11 +541,13 @@ class UsersTable:
|
||||
result = await session.execute(select(User).filter(User.id.in_(user_ids)))
|
||||
users = result.scalars().all()
|
||||
return [UserModel.model_validate(user) for user in users]
|
||||
|
||||
# count registered accounts
|
||||
async def get_num_users(self, db: AsyncSession | None = None) -> int | None:
|
||||
async with get_async_db_context(db) as session:
|
||||
result = await session.execute(select(func.count()).select_from(User))
|
||||
return result.scalar()
|
||||
|
||||
# check user existence
|
||||
async def has_users(self, db: AsyncSession | None = None) -> bool:
|
||||
async with get_async_db_context(db) as session:
|
||||
@@ -589,7 +601,10 @@ class UsersTable:
|
||||
return UserModel.model_validate(user)
|
||||
|
||||
async def update_user_profile_image_url_by_id(
|
||||
self, id: str, profile_image_url: str, db: AsyncSession | None = None,
|
||||
self,
|
||||
id: str,
|
||||
profile_image_url: str,
|
||||
db: AsyncSession | None = None,
|
||||
) -> UserModel | None:
|
||||
async with get_async_db_context(db) as session:
|
||||
user = await session.get(User, id)
|
||||
@@ -650,6 +665,7 @@ class UsersTable:
|
||||
await session.commit()
|
||||
await session.refresh(user)
|
||||
return UserModel.model_validate(user)
|
||||
|
||||
# settings update helper
|
||||
async def update_user_settings_by_id(
|
||||
self, id: str, updated: dict, db: AsyncSession | None = None
|
||||
@@ -745,4 +761,3 @@ class UsersTable:
|
||||
|
||||
|
||||
Users = UsersTable() # singleton user repository
|
||||
|
||||
Reference in new issue
Block a user