refac: async db
This commit is contained in:
@@ -11,8 +11,8 @@ from open_webui.models.groups import Groups
|
||||
from open_webui.models.users import Users
|
||||
from open_webui.models.feedbacks import Feedbacks
|
||||
from open_webui.utils.auth import get_admin_user
|
||||
from open_webui.internal.db import get_session
|
||||
from sqlalchemy.orm import Session
|
||||
from open_webui.internal.db import get_async_session
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -59,10 +59,10 @@ async def get_model_analytics(
|
||||
end_date: Optional[int] = Query(None, description='End timestamp (epoch)'),
|
||||
group_id: Optional[str] = Query(None, description='Filter by user group ID'),
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""Get message counts per model."""
|
||||
counts = ChatMessages.get_message_count_by_model(start_date=start_date, end_date=end_date, group_id=group_id, db=db)
|
||||
counts = await ChatMessages.get_message_count_by_model(start_date=start_date, end_date=end_date, group_id=group_id, db=db)
|
||||
models = [
|
||||
ModelAnalyticsEntry(model_id=model_id, count=count)
|
||||
for model_id, count in sorted(counts.items(), key=lambda x: -x[1])
|
||||
@@ -77,17 +77,17 @@ async def get_user_analytics(
|
||||
group_id: Optional[str] = Query(None, description='Filter by user group ID'),
|
||||
limit: int = Query(50, description='Max users to return'),
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""Get message counts and token usage per user with user info."""
|
||||
counts = ChatMessages.get_message_count_by_user(start_date=start_date, end_date=end_date, group_id=group_id, db=db)
|
||||
token_usage = ChatMessages.get_token_usage_by_user(
|
||||
counts = await ChatMessages.get_message_count_by_user(start_date=start_date, end_date=end_date, group_id=group_id, db=db)
|
||||
token_usage = await ChatMessages.get_token_usage_by_user(
|
||||
start_date=start_date, end_date=end_date, group_id=group_id, db=db
|
||||
)
|
||||
|
||||
# Get user info for top users
|
||||
top_user_ids = [uid for uid, _ in sorted(counts.items(), key=lambda x: -x[1])[:limit]]
|
||||
user_info = {u.id: u for u in Users.get_users_by_user_ids(top_user_ids, db=db)}
|
||||
user_info = {u.id: u for u in await Users.get_users_by_user_ids(top_user_ids, db=db)}
|
||||
|
||||
users = []
|
||||
for user_id in top_user_ids:
|
||||
@@ -118,13 +118,13 @@ async def get_messages(
|
||||
skip: int = Query(0),
|
||||
limit: int = Query(50, le=100),
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""Query messages with filters."""
|
||||
if chat_id:
|
||||
return ChatMessages.get_messages_by_chat_id(chat_id=chat_id, db=db)
|
||||
return await ChatMessages.get_messages_by_chat_id(chat_id=chat_id, db=db)
|
||||
elif model_id:
|
||||
return ChatMessages.get_messages_by_model_id(
|
||||
return await ChatMessages.get_messages_by_model_id(
|
||||
model_id=model_id,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
@@ -133,7 +133,7 @@ async def get_messages(
|
||||
db=db,
|
||||
)
|
||||
elif user_id:
|
||||
return ChatMessages.get_messages_by_user_id(user_id=user_id, skip=skip, limit=limit, db=db)
|
||||
return await ChatMessages.get_messages_by_user_id(user_id=user_id, skip=skip, limit=limit, db=db)
|
||||
else:
|
||||
# Return empty if no filter specified
|
||||
return []
|
||||
@@ -152,16 +152,16 @@ async def get_summary(
|
||||
end_date: Optional[int] = Query(None, description='End timestamp (epoch)'),
|
||||
group_id: Optional[str] = Query(None, description='Filter by user group ID'),
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""Get summary statistics for the dashboard."""
|
||||
model_counts = ChatMessages.get_message_count_by_model(
|
||||
model_counts = await ChatMessages.get_message_count_by_model(
|
||||
start_date=start_date, end_date=end_date, group_id=group_id, db=db
|
||||
)
|
||||
user_counts = ChatMessages.get_message_count_by_user(
|
||||
user_counts = await ChatMessages.get_message_count_by_user(
|
||||
start_date=start_date, end_date=end_date, group_id=group_id, db=db
|
||||
)
|
||||
chat_counts = ChatMessages.get_message_count_by_chat(
|
||||
chat_counts = await ChatMessages.get_message_count_by_chat(
|
||||
start_date=start_date, end_date=end_date, group_id=group_id, db=db
|
||||
)
|
||||
|
||||
@@ -189,13 +189,13 @@ async def get_daily_stats(
|
||||
group_id: Optional[str] = Query(None, description='Filter by user group ID'),
|
||||
granularity: str = Query('daily', description="Granularity: 'hourly' or 'daily'"),
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""Get message counts grouped by model for time-series chart."""
|
||||
if granularity == 'hourly':
|
||||
counts = ChatMessages.get_hourly_message_counts_by_model(start_date=start_date, end_date=end_date, db=db)
|
||||
counts = await ChatMessages.get_hourly_message_counts_by_model(start_date=start_date, end_date=end_date, db=db)
|
||||
else:
|
||||
counts = ChatMessages.get_daily_message_counts_by_model(
|
||||
counts = await ChatMessages.get_daily_message_counts_by_model(
|
||||
start_date=start_date, end_date=end_date, group_id=group_id, db=db
|
||||
)
|
||||
return DailyStatsResponse(
|
||||
@@ -224,10 +224,10 @@ async def get_token_usage(
|
||||
end_date: Optional[int] = Query(None),
|
||||
group_id: Optional[str] = Query(None, description='Filter by user group ID'),
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""Get token usage aggregated by model."""
|
||||
usage = ChatMessages.get_token_usage_by_model(start_date=start_date, end_date=end_date, group_id=group_id, db=db)
|
||||
usage = await ChatMessages.get_token_usage_by_model(start_date=start_date, end_date=end_date, group_id=group_id, db=db)
|
||||
|
||||
models = [
|
||||
TokenUsageEntry(model_id=model_id, **data)
|
||||
@@ -271,12 +271,12 @@ async def get_model_chats(
|
||||
skip: int = Query(0),
|
||||
limit: int = Query(50, le=100),
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""Get chats that used a specific model, with preview and feedback info."""
|
||||
|
||||
# Get chat IDs that used this model
|
||||
chat_ids = ChatMessages.get_chat_ids_by_model_id(
|
||||
chat_ids = await ChatMessages.get_chat_ids_by_model_id(
|
||||
model_id=model_id,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
@@ -291,7 +291,7 @@ async def get_model_chats(
|
||||
# Get chat details from messages only
|
||||
chats_data = []
|
||||
for chat_id in chat_ids:
|
||||
messages = ChatMessages.get_messages_by_chat_id(chat_id, db=db)
|
||||
messages = await ChatMessages.get_messages_by_chat_id(chat_id, db=db)
|
||||
if not messages:
|
||||
continue
|
||||
|
||||
@@ -312,7 +312,7 @@ async def get_model_chats(
|
||||
# Get user info
|
||||
user_name = None
|
||||
if user_id:
|
||||
user_info = Users.get_user_by_id(user_id, db=db)
|
||||
user_info = await Users.get_user_by_id(user_id, db=db)
|
||||
user_name = user_info.name if user_info else None
|
||||
|
||||
# Timestamps from messages
|
||||
@@ -357,12 +357,12 @@ async def get_model_overview(
|
||||
model_id: str,
|
||||
days: int = Query(30, description='Number of days of history (0 for all)'),
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""Get model overview with feedback history and chat tags."""
|
||||
|
||||
# Get chat IDs that used this model
|
||||
chat_ids = ChatMessages.get_chat_ids_by_model_id(
|
||||
chat_ids = await ChatMessages.get_chat_ids_by_model_id(
|
||||
model_id=model_id,
|
||||
start_date=None,
|
||||
end_date=None,
|
||||
@@ -381,7 +381,7 @@ async def get_model_overview(
|
||||
start_dt = now - timedelta(days=days)
|
||||
|
||||
for chat_id in chat_ids:
|
||||
feedbacks = Feedbacks.get_feedbacks_by_chat_id(chat_id, db=db)
|
||||
feedbacks = await Feedbacks.get_feedbacks_by_chat_id(chat_id, db=db)
|
||||
for fb in feedbacks:
|
||||
if fb.data and 'rating' in fb.data:
|
||||
rating = fb.data['rating']
|
||||
@@ -425,7 +425,7 @@ async def get_model_overview(
|
||||
# Get chat tags
|
||||
tag_counts: dict[str, int] = defaultdict(int)
|
||||
for chat_id in chat_ids:
|
||||
chat = Chats.get_chat_by_id(chat_id, db=db)
|
||||
chat = await Chats.get_chat_by_id(chat_id, db=db)
|
||||
if chat and chat.meta:
|
||||
for tag in chat.meta.get('tags', []):
|
||||
tag_counts[tag] += 1
|
||||
|
||||
@@ -330,7 +330,7 @@ async def speech(request: Request, user=Depends(get_verified_user)):
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if user.role != 'admin' and not has_permission(user.id, 'chat.tts', request.app.state.config.USER_PERMISSIONS):
|
||||
if user.role != 'admin' and not await has_permission(user.id, 'chat.tts', request.app.state.config.USER_PERMISSIONS):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
@@ -1208,13 +1208,13 @@ def split_audio(file_path, max_bytes, format='mp3', bitrate='32k'):
|
||||
|
||||
|
||||
@router.post('/transcriptions')
|
||||
def transcription(
|
||||
async def transcription(
|
||||
request: Request,
|
||||
file: UploadFile = File(...),
|
||||
language: Optional[str] = Form(None),
|
||||
user=Depends(get_verified_user),
|
||||
):
|
||||
if user.role != 'admin' and not has_permission(user.id, 'chat.stt', request.app.state.config.USER_PERMISSIONS):
|
||||
if user.role != 'admin' and not await has_permission(user.id, 'chat.stt', request.app.state.config.USER_PERMISSIONS):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
|
||||
@@ -70,8 +70,8 @@ from open_webui.utils.auth import (
|
||||
get_password_hash,
|
||||
get_http_authorization_cred,
|
||||
)
|
||||
from open_webui.internal.db import get_session
|
||||
from sqlalchemy.orm import Session
|
||||
from open_webui.internal.db import get_async_session
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from open_webui.utils.webhook import post_webhook
|
||||
from open_webui.utils.access_control import get_permissions, has_permission
|
||||
from open_webui.utils.groups import apply_default_group_assignment
|
||||
@@ -96,7 +96,7 @@ log = logging.getLogger(__name__)
|
||||
signin_rate_limiter = RateLimiter(redis_client=get_redis_client(), limit=5 * 3, window=60 * 3)
|
||||
|
||||
|
||||
def create_session_response(request: Request, user, db, response: Response = None, set_cookie: bool = False) -> dict:
|
||||
async def create_session_response(request: Request, user, db, response: Response = None, set_cookie: bool = False) -> dict:
|
||||
"""
|
||||
Create JWT token and build session response for a user.
|
||||
Shared helper for signin, signup, ldap_auth, add_user, and token_exchange endpoints.
|
||||
@@ -131,7 +131,7 @@ def create_session_response(request: Request, user, db, response: Response = Non
|
||||
**({'max_age': max_age} if max_age is not None else {}),
|
||||
)
|
||||
|
||||
user_permissions = get_permissions(user.id, request.app.state.config.USER_PERMISSIONS, db=db)
|
||||
user_permissions = await get_permissions(user.id, request.app.state.config.USER_PERMISSIONS, db=db)
|
||||
|
||||
return {
|
||||
'token': token,
|
||||
@@ -167,7 +167,7 @@ async def get_session_user(
|
||||
request: Request,
|
||||
response: Response,
|
||||
user=Depends(get_current_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
auth_header = request.headers.get('Authorization')
|
||||
auth_token = get_http_authorization_cred(auth_header)
|
||||
@@ -197,7 +197,7 @@ async def get_session_user(
|
||||
**({'max_age': max_age} if max_age is not None else {}),
|
||||
)
|
||||
|
||||
user_permissions = get_permissions(user.id, request.app.state.config.USER_PERMISSIONS, db=db)
|
||||
user_permissions = await get_permissions(user.id, request.app.state.config.USER_PERMISSIONS, db=db)
|
||||
|
||||
return {
|
||||
'token': token,
|
||||
@@ -227,10 +227,10 @@ async def get_session_user(
|
||||
async def update_profile(
|
||||
form_data: UpdateProfileForm,
|
||||
session_user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if session_user:
|
||||
user = Users.update_user_by_id(
|
||||
user = await Users.update_user_by_id(
|
||||
session_user.id,
|
||||
form_data.model_dump(),
|
||||
db=db,
|
||||
@@ -256,10 +256,10 @@ class UpdateTimezoneForm(BaseModel):
|
||||
async def update_timezone(
|
||||
form_data: UpdateTimezoneForm,
|
||||
session_user=Depends(get_current_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if session_user:
|
||||
Users.update_user_by_id(
|
||||
await Users.update_user_by_id(
|
||||
session_user.id,
|
||||
{'timezone': form_data.timezone},
|
||||
db=db,
|
||||
@@ -278,12 +278,12 @@ async def update_timezone(
|
||||
async def update_password(
|
||||
form_data: UpdatePasswordForm,
|
||||
session_user=Depends(get_current_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if WEBUI_AUTH_TRUSTED_EMAIL_HEADER:
|
||||
raise HTTPException(400, detail=ERROR_MESSAGES.ACTION_PROHIBITED)
|
||||
if session_user:
|
||||
user = Auths.authenticate_user(
|
||||
user = await Auths.authenticate_user(
|
||||
session_user.email,
|
||||
lambda pw: verify_password(form_data.password, pw),
|
||||
db=db,
|
||||
@@ -295,7 +295,7 @@ async def update_password(
|
||||
except Exception as e:
|
||||
raise HTTPException(400, detail=str(e))
|
||||
hashed = get_password_hash(form_data.new_password)
|
||||
return Auths.update_user_password_by_id(user.id, hashed, db=db)
|
||||
return await Auths.update_user_password_by_id(user.id, hashed, db=db)
|
||||
else:
|
||||
raise HTTPException(400, detail=ERROR_MESSAGES.INCORRECT_PASSWORD)
|
||||
else:
|
||||
@@ -310,7 +310,7 @@ async def ldap_auth(
|
||||
request: Request,
|
||||
response: Response,
|
||||
form_data: LdapForm,
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
# Security checks FIRST - before loading any config
|
||||
if not request.app.state.config.ENABLE_LDAP:
|
||||
@@ -476,12 +476,12 @@ async def ldap_auth(
|
||||
if not await asyncio.to_thread(connection_user.bind):
|
||||
raise HTTPException(400, 'Authentication failed.')
|
||||
|
||||
user = Users.get_user_by_email(email, db=db)
|
||||
user = await Users.get_user_by_email(email, db=db)
|
||||
if not user:
|
||||
try:
|
||||
# Insert with default role first to avoid TOCTOU race on
|
||||
# first-user registration. Matches signup_handler pattern.
|
||||
user = Auths.insert_new_auth(
|
||||
user = await Auths.insert_new_auth(
|
||||
email=email,
|
||||
password=str(uuid.uuid4()),
|
||||
name=cn,
|
||||
@@ -494,11 +494,11 @@ async def ldap_auth(
|
||||
|
||||
# Atomically check if this is the only user *after* the
|
||||
# insert. Only the single user present should become admin.
|
||||
if Users.get_num_users(db=db) == 1:
|
||||
Users.update_user_role_by_id(user.id, 'admin', db=db)
|
||||
user = Users.get_user_by_id(user.id, db=db)
|
||||
if await Users.get_num_users(db=db) == 1:
|
||||
await Users.update_user_role_by_id(user.id, 'admin', db=db)
|
||||
user = await Users.get_user_by_id(user.id, db=db)
|
||||
|
||||
apply_default_group_assignment(
|
||||
await apply_default_group_assignment(
|
||||
request.app.state.config.DEFAULT_GROUP_ID,
|
||||
user.id,
|
||||
db=db,
|
||||
@@ -510,19 +510,19 @@ async def ldap_auth(
|
||||
log.error(f'LDAP user creation error: {str(err)}')
|
||||
raise HTTPException(500, detail='Internal error occurred during LDAP user creation.')
|
||||
|
||||
user = Auths.authenticate_user_by_email(email, db=db)
|
||||
user = await Auths.authenticate_user_by_email(email, db=db)
|
||||
|
||||
if user:
|
||||
if ENABLE_LDAP_GROUP_MANAGEMENT and user_groups:
|
||||
if ENABLE_LDAP_GROUP_CREATION:
|
||||
Groups.create_groups_by_group_names(user.id, user_groups, db=db)
|
||||
await Groups.create_groups_by_group_names(user.id, user_groups, db=db)
|
||||
try:
|
||||
Groups.sync_groups_by_group_names(user.id, user_groups, db=db)
|
||||
await Groups.sync_groups_by_group_names(user.id, user_groups, db=db)
|
||||
log.info(f'Successfully synced groups for user {user.id}: {user_groups}')
|
||||
except Exception as e:
|
||||
log.error(f'Failed to sync groups for user {user.id}: {e}')
|
||||
|
||||
return create_session_response(request, user, db, response, set_cookie=True)
|
||||
return await create_session_response(request, user, db, response, set_cookie=True)
|
||||
else:
|
||||
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
|
||||
else:
|
||||
@@ -542,7 +542,7 @@ async def signin(
|
||||
request: Request,
|
||||
response: Response,
|
||||
form_data: SigninForm,
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if not ENABLE_PASSWORD_AUTH:
|
||||
raise HTTPException(
|
||||
@@ -564,7 +564,7 @@ async def signin(
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
if not Users.get_user_by_email(email.lower(), db=db):
|
||||
if not await Users.get_user_by_email(email.lower(), db=db):
|
||||
await signup_handler(
|
||||
request,
|
||||
email,
|
||||
@@ -573,20 +573,20 @@ async def signin(
|
||||
db=db,
|
||||
)
|
||||
|
||||
user = Auths.authenticate_user_by_email(email, db=db)
|
||||
user = await Auths.authenticate_user_by_email(email, db=db)
|
||||
if user:
|
||||
if WEBUI_AUTH_TRUSTED_GROUPS_HEADER:
|
||||
group_names = request.headers.get(WEBUI_AUTH_TRUSTED_GROUPS_HEADER, '').split(',')
|
||||
group_names = [name.strip() for name in group_names if name.strip()]
|
||||
|
||||
if group_names:
|
||||
Groups.sync_groups_by_group_names(user.id, group_names, db=db)
|
||||
await Groups.sync_groups_by_group_names(user.id, group_names, db=db)
|
||||
|
||||
if WEBUI_AUTH_TRUSTED_ROLE_HEADER:
|
||||
trusted_role = request.headers.get(WEBUI_AUTH_TRUSTED_ROLE_HEADER, '').lower().strip()
|
||||
if trusted_role in {'admin', 'user', 'pending'}:
|
||||
if user.role != trusted_role:
|
||||
Users.update_user_role_by_id(user.id, trusted_role, db=db)
|
||||
await Users.update_user_role_by_id(user.id, trusted_role, db=db)
|
||||
elif trusted_role:
|
||||
log.warning(f'Ignoring invalid trusted role header value: {trusted_role}')
|
||||
|
||||
@@ -594,14 +594,14 @@ async def signin(
|
||||
admin_email = 'admin@localhost'
|
||||
admin_password = 'admin'
|
||||
|
||||
if Users.get_user_by_email(admin_email.lower(), db=db):
|
||||
user = Auths.authenticate_user(
|
||||
if await Users.get_user_by_email(admin_email.lower(), db=db):
|
||||
user = await Auths.authenticate_user(
|
||||
admin_email.lower(),
|
||||
lambda pw: verify_password(admin_password, pw),
|
||||
db=db,
|
||||
)
|
||||
else:
|
||||
if Users.has_users(db=db):
|
||||
if await Users.has_users(db=db):
|
||||
raise HTTPException(400, detail=ERROR_MESSAGES.EXISTING_USERS)
|
||||
|
||||
await signup_handler(
|
||||
@@ -612,7 +612,7 @@ async def signin(
|
||||
db=db,
|
||||
)
|
||||
|
||||
user = Auths.authenticate_user(
|
||||
user = await Auths.authenticate_user(
|
||||
admin_email.lower(),
|
||||
lambda pw: verify_password(admin_password, pw),
|
||||
db=db,
|
||||
@@ -633,14 +633,14 @@ async def signin(
|
||||
# decode safely — ignore incomplete UTF-8 sequences
|
||||
form_data.password = password_bytes.decode('utf-8', errors='ignore')
|
||||
|
||||
user = Auths.authenticate_user(
|
||||
user = await Auths.authenticate_user(
|
||||
form_data.email.lower(),
|
||||
lambda pw: verify_password(form_data.password, pw),
|
||||
db=db,
|
||||
)
|
||||
|
||||
if user:
|
||||
return create_session_response(request, user, db, response, set_cookie=True)
|
||||
return await create_session_response(request, user, db, response, set_cookie=True)
|
||||
else:
|
||||
raise HTTPException(400, detail=ERROR_MESSAGES.INVALID_CRED)
|
||||
|
||||
@@ -657,7 +657,7 @@ async def signup_handler(
|
||||
name: str,
|
||||
profile_image_url: str = '/user.png',
|
||||
*,
|
||||
db: Session,
|
||||
db: AsyncSession,
|
||||
) -> UserModel:
|
||||
"""
|
||||
Core user-creation logic shared by the signup endpoint and
|
||||
@@ -671,7 +671,7 @@ async def signup_handler(
|
||||
# first-user registration can all see an empty table and each get admin.
|
||||
hashed = get_password_hash(password)
|
||||
|
||||
user = Auths.insert_new_auth(
|
||||
user = await Auths.insert_new_auth(
|
||||
email=email.lower(),
|
||||
password=hashed,
|
||||
name=name,
|
||||
@@ -684,9 +684,9 @@ async def signup_handler(
|
||||
|
||||
# Atomically check if this is the only user *after* the insert.
|
||||
# Only the single user present at this point should become admin.
|
||||
if Users.get_num_users(db=db) == 1:
|
||||
Users.update_user_role_by_id(user.id, 'admin', db=db)
|
||||
user = Users.get_user_by_id(user.id, db=db)
|
||||
if await Users.get_num_users(db=db) == 1:
|
||||
await Users.update_user_role_by_id(user.id, 'admin', db=db)
|
||||
user = await Users.get_user_by_id(user.id, db=db)
|
||||
request.app.state.config.ENABLE_SIGNUP = False
|
||||
|
||||
if request.app.state.config.WEBHOOK_URL:
|
||||
@@ -701,7 +701,7 @@ async def signup_handler(
|
||||
},
|
||||
)
|
||||
|
||||
apply_default_group_assignment(
|
||||
await apply_default_group_assignment(
|
||||
request.app.state.config.DEFAULT_GROUP_ID,
|
||||
user.id,
|
||||
db=db,
|
||||
@@ -715,9 +715,9 @@ async def signup(
|
||||
request: Request,
|
||||
response: Response,
|
||||
form_data: SignupForm,
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
has_users = Users.has_users(db=db)
|
||||
has_users = await Users.has_users(db=db)
|
||||
|
||||
if WEBUI_AUTH:
|
||||
if not request.app.state.config.ENABLE_SIGNUP or not request.app.state.config.ENABLE_LOGIN_FORM:
|
||||
@@ -730,7 +730,7 @@ async def signup(
|
||||
if not validate_email_format(form_data.email.lower()):
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.INVALID_EMAIL_FORMAT)
|
||||
|
||||
if Users.get_user_by_email(form_data.email.lower(), db=db):
|
||||
if await Users.get_user_by_email(form_data.email.lower(), db=db):
|
||||
raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN)
|
||||
|
||||
try:
|
||||
@@ -747,7 +747,7 @@ async def signup(
|
||||
form_data.profile_image_url,
|
||||
db=db,
|
||||
)
|
||||
return create_session_response(request, user, db, response, set_cookie=True)
|
||||
return await create_session_response(request, user, db, response, set_cookie=True)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as err:
|
||||
@@ -756,7 +756,7 @@ async def signup(
|
||||
|
||||
|
||||
@router.get('/signout')
|
||||
async def signout(request: Request, response: Response, db: Session = Depends(get_session)):
|
||||
async def signout(request: Request, response: Response, db: AsyncSession = Depends(get_async_session)):
|
||||
# get auth token from headers or cookies
|
||||
token = None
|
||||
auth_header = request.headers.get('Authorization')
|
||||
@@ -777,7 +777,7 @@ async def signout(request: Request, response: Response, db: Session = Depends(ge
|
||||
if oauth_session_id:
|
||||
response.delete_cookie('oauth_session_id')
|
||||
|
||||
session = OAuthSessions.get_session_by_id(oauth_session_id, db=db)
|
||||
session = await OAuthSessions.get_session_by_id(oauth_session_id, db=db)
|
||||
|
||||
# If a custom end_session_endpoint is configured (e.g. AWS Cognito), redirect
|
||||
# there directly instead of attempting OIDC discovery.
|
||||
@@ -852,12 +852,12 @@ async def add_user(
|
||||
request: Request,
|
||||
form_data: AddUserForm,
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if not validate_email_format(form_data.email.lower()):
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.INVALID_EMAIL_FORMAT)
|
||||
|
||||
if Users.get_user_by_email(form_data.email.lower(), db=db):
|
||||
if await Users.get_user_by_email(form_data.email.lower(), db=db):
|
||||
raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN)
|
||||
|
||||
try:
|
||||
@@ -867,7 +867,7 @@ async def add_user(
|
||||
raise HTTPException(400, detail=str(e))
|
||||
|
||||
hashed = get_password_hash(form_data.password)
|
||||
user = Auths.insert_new_auth(
|
||||
user = await Auths.insert_new_auth(
|
||||
form_data.email.lower(),
|
||||
hashed,
|
||||
form_data.name,
|
||||
@@ -877,7 +877,7 @@ async def add_user(
|
||||
)
|
||||
|
||||
if user:
|
||||
apply_default_group_assignment(
|
||||
await apply_default_group_assignment(
|
||||
request.app.state.config.DEFAULT_GROUP_ID,
|
||||
user.id,
|
||||
db=db,
|
||||
@@ -909,7 +909,7 @@ async def add_user(
|
||||
|
||||
|
||||
@router.get('/admin/details')
|
||||
async def get_admin_details(request: Request, user=Depends(get_current_user), db: Session = Depends(get_session)):
|
||||
async def get_admin_details(request: Request, user=Depends(get_current_user), db: AsyncSession = Depends(get_async_session)):
|
||||
if request.app.state.config.SHOW_ADMIN_DETAILS:
|
||||
admin_email = request.app.state.config.ADMIN_EMAIL
|
||||
admin_name = None
|
||||
@@ -917,11 +917,11 @@ async def get_admin_details(request: Request, user=Depends(get_current_user), db
|
||||
log.info(f'Admin details - Email: {admin_email}, Name: {admin_name}')
|
||||
|
||||
if admin_email:
|
||||
admin = Users.get_user_by_email(admin_email, db=db)
|
||||
admin = await Users.get_user_by_email(admin_email, db=db)
|
||||
if admin:
|
||||
admin_name = admin.name
|
||||
else:
|
||||
admin = Users.get_first_user(db=db)
|
||||
admin = await Users.get_first_user(db=db)
|
||||
if admin:
|
||||
admin_email = admin.email
|
||||
admin_name = admin.name
|
||||
@@ -1173,10 +1173,10 @@ async def update_ldap_config(request: Request, form_data: LdapConfigForm, user=D
|
||||
|
||||
# create api key
|
||||
@router.post('/api_key', response_model=ApiKey)
|
||||
async def generate_api_key(request: Request, user=Depends(get_current_user), db: Session = Depends(get_session)):
|
||||
async def generate_api_key(request: Request, user=Depends(get_current_user), db: AsyncSession = Depends(get_async_session)):
|
||||
if not request.app.state.config.ENABLE_API_KEYS or (
|
||||
user.role != 'admin'
|
||||
and not has_permission(user.id, 'features.api_keys', request.app.state.config.USER_PERMISSIONS)
|
||||
and not await has_permission(user.id, 'features.api_keys', request.app.state.config.USER_PERMISSIONS)
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
@@ -1184,7 +1184,7 @@ async def generate_api_key(request: Request, user=Depends(get_current_user), db:
|
||||
)
|
||||
|
||||
api_key = create_api_key()
|
||||
success = Users.update_user_api_key_by_id(user.id, api_key, db=db)
|
||||
success = await Users.update_user_api_key_by_id(user.id, api_key, db=db)
|
||||
|
||||
if success:
|
||||
return {
|
||||
@@ -1196,14 +1196,14 @@ async def generate_api_key(request: Request, user=Depends(get_current_user), db:
|
||||
|
||||
# delete api key
|
||||
@router.delete('/api_key', response_model=bool)
|
||||
async def delete_api_key(user=Depends(get_current_user), db: Session = Depends(get_session)):
|
||||
return Users.delete_user_api_key_by_id(user.id, db=db)
|
||||
async def delete_api_key(user=Depends(get_current_user), db: AsyncSession = Depends(get_async_session)):
|
||||
return await Users.delete_user_api_key_by_id(user.id, db=db)
|
||||
|
||||
|
||||
# get api key
|
||||
@router.get('/api_key', response_model=ApiKey)
|
||||
async def get_api_key(user=Depends(get_current_user), db: Session = Depends(get_session)):
|
||||
api_key = Users.get_user_api_key_by_id(user.id, db=db)
|
||||
async def get_api_key(user=Depends(get_current_user), db: AsyncSession = Depends(get_async_session)):
|
||||
api_key = await Users.get_user_api_key_by_id(user.id, db=db)
|
||||
if api_key:
|
||||
return {
|
||||
'api_key': api_key,
|
||||
@@ -1227,7 +1227,7 @@ async def token_exchange(
|
||||
response: Response,
|
||||
provider: str,
|
||||
form_data: TokenExchangeForm,
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""
|
||||
Exchange an external OAuth provider token for an OpenWebUI JWT.
|
||||
@@ -1296,14 +1296,14 @@ async def token_exchange(
|
||||
email = email.lower()
|
||||
|
||||
# Try to find the user by OAuth sub
|
||||
user = Users.get_user_by_oauth_sub(provider, sub, db=db)
|
||||
user = await Users.get_user_by_oauth_sub(provider, sub, db=db)
|
||||
|
||||
if not user and OAUTH_MERGE_ACCOUNTS_BY_EMAIL.value:
|
||||
# Try to find by email if merge is enabled
|
||||
user = Users.get_user_by_email(email, db=db)
|
||||
user = await Users.get_user_by_email(email, db=db)
|
||||
if user:
|
||||
# Link the OAuth sub to this user
|
||||
Users.update_user_oauth_by_id(user.id, provider, sub, db=db)
|
||||
await Users.update_user_oauth_by_id(user.id, provider, sub, db=db)
|
||||
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
@@ -1311,4 +1311,4 @@ async def token_exchange(
|
||||
detail='User not found. Please sign in via the web interface first.',
|
||||
)
|
||||
|
||||
return create_session_response(request, user, db)
|
||||
return await create_session_response(request, user, db)
|
||||
|
||||
@@ -3,7 +3,7 @@ import logging
|
||||
|
||||
from typing import Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from open_webui.models.automations import (
|
||||
Automations,
|
||||
@@ -23,7 +23,7 @@ from open_webui.utils.automations import (
|
||||
)
|
||||
from open_webui.utils.auth import get_verified_user, get_admin_user
|
||||
from open_webui.utils.access_control import has_permission
|
||||
from open_webui.internal.db import get_session
|
||||
from open_webui.internal.db import get_async_session
|
||||
from open_webui.constants import ERROR_MESSAGES
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -38,8 +38,8 @@ PAGE_ITEM_COUNT = 30
|
||||
############################
|
||||
|
||||
|
||||
def check_automations_permission(request, user):
|
||||
if user.role != 'admin' and not has_permission(
|
||||
async def check_automations_permission(request, user):
|
||||
if user.role != 'admin' and not await has_permission(
|
||||
user.id, 'features.automations', request.app.state.config.USER_PERMISSIONS
|
||||
):
|
||||
raise HTTPException(
|
||||
@@ -61,7 +61,7 @@ def check_automation_access(automation, user):
|
||||
)
|
||||
|
||||
|
||||
def check_automation_limits(request, user, rrule_str: str, db, is_create: bool = False):
|
||||
async def check_automation_limits(request, user, rrule_str: str, db, is_create: bool = False):
|
||||
"""Enforce global automation limits. Admins bypass all checks."""
|
||||
if user.role == 'admin':
|
||||
return
|
||||
@@ -71,7 +71,7 @@ def check_automation_limits(request, user, rrule_str: str, db, is_create: bool =
|
||||
max_count = request.app.state.config.AUTOMATION_MAX_COUNT
|
||||
if max_count:
|
||||
max_count = int(max_count)
|
||||
if max_count > 0 and Automations.count_by_user(user.id, db=db) >= max_count:
|
||||
if max_count > 0 and await Automations.count_by_user(user.id, db=db) >= max_count:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f'Automation limit reached ({max_count})',
|
||||
@@ -90,9 +90,9 @@ def check_automation_limits(request, user, rrule_str: str, db, is_create: bool =
|
||||
)
|
||||
|
||||
|
||||
def enrich_automation(automation: AutomationModel, db: Session, tz: str = None) -> AutomationResponse:
|
||||
async def enrich_automation(automation: AutomationModel, db: AsyncSession, tz: str = None) -> AutomationResponse:
|
||||
"""Full enrichment for single-item views (includes next_runs computation)."""
|
||||
last_run = AutomationRuns.get_latest(automation.id, db=db)
|
||||
last_run = await AutomationRuns.get_latest(automation.id, db=db)
|
||||
return AutomationResponse(
|
||||
**automation.model_dump(),
|
||||
last_run=last_run,
|
||||
@@ -112,14 +112,14 @@ async def get_automation_items(
|
||||
status: Optional[str] = None,
|
||||
page: Optional[int] = 1,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
check_automations_permission(request, user)
|
||||
await check_automations_permission(request, user)
|
||||
limit = PAGE_ITEM_COUNT
|
||||
page = max(1, page)
|
||||
skip = (page - 1) * limit
|
||||
|
||||
result = Automations.search_automations(
|
||||
result = await Automations.search_automations(
|
||||
user_id=user.id,
|
||||
query=query,
|
||||
status=status,
|
||||
@@ -130,7 +130,7 @@ async def get_automation_items(
|
||||
|
||||
# Batch-fetch latest runs in a single query instead of N+1
|
||||
ids = [item.id for item in result.items]
|
||||
latest_runs = AutomationRuns.get_latest_batch(ids, db=db) if ids else {}
|
||||
latest_runs = await AutomationRuns.get_latest_batch(ids, db=db) if ids else {}
|
||||
|
||||
return {
|
||||
'items': [
|
||||
@@ -154,9 +154,9 @@ async def create_new_automation(
|
||||
request: Request,
|
||||
form_data: AutomationForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
check_automations_permission(request, user)
|
||||
await check_automations_permission(request, user)
|
||||
try:
|
||||
validate_rrule(form_data.data.rrule)
|
||||
except ValueError as e:
|
||||
@@ -165,7 +165,7 @@ async def create_new_automation(
|
||||
detail=str(e),
|
||||
)
|
||||
|
||||
check_automation_limits(request, user, form_data.data.rrule, db, is_create=True)
|
||||
await check_automation_limits(request, user, form_data.data.rrule, db, is_create=True)
|
||||
|
||||
# Validate terminal server exists if linked
|
||||
if form_data.data.terminal and form_data.data.terminal.server_id:
|
||||
@@ -177,8 +177,8 @@ async def create_new_automation(
|
||||
)
|
||||
|
||||
tz = user.timezone
|
||||
automation = Automations.insert(user.id, form_data, next_run_ns(form_data.data.rrule, tz=tz), db=db)
|
||||
return enrich_automation(automation, db, tz=tz)
|
||||
automation = await Automations.insert(user.id, form_data, next_run_ns(form_data.data.rrule, tz=tz), db=db)
|
||||
return await enrich_automation(automation, db, tz=tz)
|
||||
|
||||
|
||||
############################
|
||||
@@ -191,12 +191,12 @@ async def get_automation_by_id(
|
||||
request: Request,
|
||||
id: str,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
check_automations_permission(request, user)
|
||||
automation = Automations.get_by_id(id, db=db)
|
||||
await check_automations_permission(request, user)
|
||||
automation = await Automations.get_by_id(id, db=db)
|
||||
check_automation_access(automation, user)
|
||||
return enrich_automation(automation, db, tz=user.timezone)
|
||||
return await enrich_automation(automation, db, tz=user.timezone)
|
||||
|
||||
|
||||
############################
|
||||
@@ -210,10 +210,10 @@ async def update_automation_by_id(
|
||||
id: str,
|
||||
form_data: AutomationForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
check_automations_permission(request, user)
|
||||
automation = Automations.get_by_id(id, db=db)
|
||||
await check_automations_permission(request, user)
|
||||
automation = await Automations.get_by_id(id, db=db)
|
||||
check_automation_access(automation, user)
|
||||
|
||||
try:
|
||||
@@ -224,7 +224,7 @@ async def update_automation_by_id(
|
||||
detail=str(e),
|
||||
)
|
||||
|
||||
check_automation_limits(request, user, form_data.data.rrule, db, is_create=False)
|
||||
await check_automation_limits(request, user, form_data.data.rrule, db, is_create=False)
|
||||
|
||||
# Validate terminal server exists if linked
|
||||
if form_data.data.terminal and form_data.data.terminal.server_id:
|
||||
@@ -236,8 +236,8 @@ async def update_automation_by_id(
|
||||
)
|
||||
|
||||
tz = user.timezone
|
||||
updated = Automations.update_by_id(id, form_data, next_run_ns(form_data.data.rrule, tz=tz), db=db)
|
||||
return enrich_automation(updated, db, tz=tz)
|
||||
updated = await Automations.update_by_id(id, form_data, next_run_ns(form_data.data.rrule, tz=tz), db=db)
|
||||
return await enrich_automation(updated, db, tz=tz)
|
||||
|
||||
|
||||
############################
|
||||
@@ -250,13 +250,13 @@ async def toggle_automation_by_id(
|
||||
request: Request,
|
||||
id: str,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
check_automations_permission(request, user)
|
||||
automation = Automations.get_by_id(id, db=db)
|
||||
await check_automations_permission(request, user)
|
||||
automation = await Automations.get_by_id(id, db=db)
|
||||
check_automation_access(automation, user)
|
||||
toggled = Automations.toggle(id, next_run_ns(automation.data['rrule'], tz=user.timezone), db=db)
|
||||
return enrich_automation(toggled, db, tz=user.timezone)
|
||||
toggled = await Automations.toggle(id, next_run_ns(automation.data['rrule'], tz=user.timezone), db=db)
|
||||
return await enrich_automation(toggled, db, tz=user.timezone)
|
||||
|
||||
|
||||
############################
|
||||
@@ -269,13 +269,13 @@ async def run_automation_by_id(
|
||||
request: Request,
|
||||
id: str,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
check_automations_permission(request, user)
|
||||
automation = Automations.get_by_id(id, db=db)
|
||||
await check_automations_permission(request, user)
|
||||
automation = await Automations.get_by_id(id, db=db)
|
||||
check_automation_access(automation, user)
|
||||
asyncio.create_task(execute_automation(request.app, automation))
|
||||
return enrich_automation(automation, db, tz=user.timezone)
|
||||
return await enrich_automation(automation, db, tz=user.timezone)
|
||||
|
||||
|
||||
############################
|
||||
@@ -288,13 +288,13 @@ async def delete_automation_by_id(
|
||||
request: Request,
|
||||
id: str,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
check_automations_permission(request, user)
|
||||
automation = Automations.get_by_id(id, db=db)
|
||||
await check_automations_permission(request, user)
|
||||
automation = await Automations.get_by_id(id, db=db)
|
||||
check_automation_access(automation, user)
|
||||
AutomationRuns.delete_by_automation(id, db=db)
|
||||
return Automations.delete(id, db=db)
|
||||
await AutomationRuns.delete_by_automation(id, db=db)
|
||||
return await Automations.delete(id, db=db)
|
||||
|
||||
|
||||
############################
|
||||
@@ -309,9 +309,9 @@ async def get_automation_runs(
|
||||
skip: int = 0,
|
||||
limit: int = 50,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
check_automations_permission(request, user)
|
||||
automation = Automations.get_by_id(id, db=db)
|
||||
await check_automations_permission(request, user)
|
||||
automation = await Automations.get_by_id(id, db=db)
|
||||
check_automation_access(automation, user)
|
||||
return AutomationRuns.get_by_automation(id, skip=skip, limit=limit, db=db)
|
||||
return await AutomationRuns.get_by_automation(id, skip=skip, limit=limit, db=db)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+132
-132
@@ -1,7 +1,7 @@
|
||||
import json
|
||||
import logging
|
||||
from typing import Optional
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
import asyncio
|
||||
from fastapi.responses import StreamingResponse
|
||||
|
||||
@@ -25,7 +25,7 @@ from open_webui.models.chats import (
|
||||
)
|
||||
from open_webui.models.tags import TagModel, Tags
|
||||
from open_webui.models.folders import Folders
|
||||
from open_webui.internal.db import get_session
|
||||
from open_webui.internal.db import get_async_session
|
||||
|
||||
from open_webui.config import ENABLE_ADMIN_CHAT_ACCESS, ENABLE_ADMIN_EXPORT
|
||||
from open_webui.constants import ERROR_MESSAGES
|
||||
@@ -49,19 +49,19 @@ router = APIRouter()
|
||||
|
||||
@router.get('/', response_model=list[ChatTitleIdResponse])
|
||||
@router.get('/list', response_model=list[ChatTitleIdResponse])
|
||||
def get_session_user_chat_list(
|
||||
async def get_session_user_chat_list(
|
||||
user=Depends(get_verified_user),
|
||||
page: Optional[int] = None,
|
||||
include_pinned: Optional[bool] = False,
|
||||
include_folders: Optional[bool] = False,
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
try:
|
||||
if page is not None:
|
||||
limit = 60
|
||||
skip = (page - 1) * limit
|
||||
|
||||
return Chats.get_chat_title_id_list_by_user_id(
|
||||
return await Chats.get_chat_title_id_list_by_user_id(
|
||||
user.id,
|
||||
include_folders=include_folders,
|
||||
include_pinned=include_pinned,
|
||||
@@ -70,7 +70,7 @@ def get_session_user_chat_list(
|
||||
db=db,
|
||||
)
|
||||
else:
|
||||
return Chats.get_chat_title_id_list_by_user_id(
|
||||
return await Chats.get_chat_title_id_list_by_user_id(
|
||||
user.id,
|
||||
include_folders=include_folders,
|
||||
include_pinned=include_pinned,
|
||||
@@ -88,17 +88,17 @@ def get_session_user_chat_list(
|
||||
|
||||
|
||||
@router.get('/stats/usage', response_model=ChatUsageStatsListResponse)
|
||||
def get_session_user_chat_usage_stats(
|
||||
async def get_session_user_chat_usage_stats(
|
||||
items_per_page: Optional[int] = 50,
|
||||
page: Optional[int] = 1,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
try:
|
||||
limit = items_per_page
|
||||
skip = (page - 1) * limit
|
||||
|
||||
result = Chats.get_chats_by_user_id(user.id, skip=skip, limit=limit, db=db)
|
||||
result = await Chats.get_chats_by_user_id(user.id, skip=skip, limit=limit, db=db)
|
||||
|
||||
chats = result.items
|
||||
total = result.total
|
||||
@@ -332,11 +332,11 @@ def _process_chat_for_export(chat) -> Optional[ChatStatsExport]:
|
||||
return None
|
||||
|
||||
|
||||
def calculate_chat_stats(user_id, skip=0, limit=10, filter=None):
|
||||
async def calculate_chat_stats(user_id, skip=0, limit=10, filter=None):
|
||||
if filter is None:
|
||||
filter = {}
|
||||
|
||||
result = Chats.get_chats_by_user_id(
|
||||
result = await Chats.get_chats_by_user_id(
|
||||
user_id,
|
||||
skip=skip,
|
||||
limit=limit,
|
||||
@@ -352,12 +352,12 @@ def calculate_chat_stats(user_id, skip=0, limit=10, filter=None):
|
||||
return chat_stats_export_list, result.total
|
||||
|
||||
|
||||
def generate_chat_stats_jsonl_generator(user_id, filter):
|
||||
async def generate_chat_stats_jsonl_generator(user_id, filter):
|
||||
"""
|
||||
Synchronous generator for streaming chat stats export.
|
||||
Async generator for streaming chat stats export.
|
||||
|
||||
NOTE: We intentionally do NOT pass a shared db session here. Instead, we let
|
||||
each batch create its own short-lived session via get_db_context(None).
|
||||
each batch create its own short-lived session via get_async_db_context(None).
|
||||
This is critical for SQLite in low-resource environments because:
|
||||
1. SQLite uses file-level locking
|
||||
2. Holding a session open for the entire streaming duration blocks other requests
|
||||
@@ -368,12 +368,12 @@ def generate_chat_stats_jsonl_generator(user_id, filter):
|
||||
|
||||
while True:
|
||||
# Each batch gets its own session that closes after the query
|
||||
result = Chats.get_chats_by_user_id(
|
||||
result = await Chats.get_chats_by_user_id(
|
||||
user_id,
|
||||
filter=filter,
|
||||
skip=skip,
|
||||
limit=limit,
|
||||
db=None, # Let get_db_context create a fresh session per batch
|
||||
db=None, # Let get_async_db_context create a fresh session per batch
|
||||
)
|
||||
if not result.items:
|
||||
break
|
||||
@@ -421,7 +421,7 @@ async def export_chat_stats(
|
||||
limit = CHAT_EXPORT_PAGE_ITEM_COUNT
|
||||
skip = (page - 1) * limit
|
||||
|
||||
chat_stats_export_list, total = await asyncio.to_thread(calculate_chat_stats, user.id, skip, limit, filter)
|
||||
chat_stats_export_list, total = await calculate_chat_stats(user.id, skip, limit, filter)
|
||||
|
||||
return ChatStatsExportList(items=chat_stats_export_list, total=total, page=page)
|
||||
|
||||
@@ -440,7 +440,7 @@ async def export_single_chat_stats(
|
||||
request: Request,
|
||||
chat_id: str,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""
|
||||
Export stats for exactly one chat by ID.
|
||||
@@ -454,7 +454,7 @@ async def export_single_chat_stats(
|
||||
)
|
||||
|
||||
try:
|
||||
chat = Chats.get_chat_by_id(chat_id, db=db)
|
||||
chat = await Chats.get_chat_by_id(chat_id, db=db)
|
||||
|
||||
if not chat:
|
||||
raise HTTPException(
|
||||
@@ -469,8 +469,8 @@ async def export_single_chat_stats(
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
# Process the chat for export
|
||||
chat_stats = await asyncio.to_thread(_process_chat_for_export, chat)
|
||||
# Process the chat for export (pure computation, no DB)
|
||||
chat_stats = _process_chat_for_export(chat)
|
||||
|
||||
if not chat_stats:
|
||||
raise HTTPException(
|
||||
@@ -491,15 +491,15 @@ async def export_single_chat_stats(
|
||||
async def delete_all_user_chats(
|
||||
request: Request,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if user.role == 'user' and not has_permission(user.id, 'chat.delete', request.app.state.config.USER_PERMISSIONS):
|
||||
if user.role == 'user' and not await has_permission(user.id, 'chat.delete', request.app.state.config.USER_PERMISSIONS):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
result = Chats.delete_chats_by_user_id(user.id, db=db)
|
||||
result = await Chats.delete_chats_by_user_id(user.id, db=db)
|
||||
return result
|
||||
|
||||
|
||||
@@ -516,7 +516,7 @@ async def get_user_chat_list_by_user_id(
|
||||
order_by: Optional[str] = None,
|
||||
direction: Optional[str] = None,
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if not ENABLE_ADMIN_CHAT_ACCESS:
|
||||
raise HTTPException(
|
||||
@@ -538,7 +538,7 @@ async def get_user_chat_list_by_user_id(
|
||||
if direction:
|
||||
filter['direction'] = direction
|
||||
|
||||
return Chats.get_chat_list_by_user_id(user_id, include_archived=True, filter=filter, skip=skip, limit=limit, db=db)
|
||||
return await Chats.get_chat_list_by_user_id(user_id, include_archived=True, filter=filter, skip=skip, limit=limit, db=db)
|
||||
|
||||
|
||||
############################
|
||||
@@ -550,10 +550,10 @@ async def get_user_chat_list_by_user_id(
|
||||
async def create_new_chat(
|
||||
form_data: ChatForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
try:
|
||||
chat = Chats.insert_new_chat(user.id, form_data, db=db)
|
||||
chat = await Chats.insert_new_chat(user.id, form_data, db=db)
|
||||
return ChatResponse(**chat.model_dump())
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
@@ -569,10 +569,10 @@ async def create_new_chat(
|
||||
async def import_chats(
|
||||
form_data: ChatsImportForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
try:
|
||||
chats = Chats.import_chats(user.id, form_data.chats, db=db)
|
||||
chats = await Chats.import_chats(user.id, form_data.chats, db=db)
|
||||
return chats
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
@@ -585,11 +585,11 @@ async def import_chats(
|
||||
|
||||
|
||||
@router.get('/search', response_model=list[ChatTitleIdResponse])
|
||||
def search_user_chats(
|
||||
async def search_user_chats(
|
||||
text: str,
|
||||
page: Optional[int] = None,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if page is None:
|
||||
page = 1
|
||||
@@ -599,7 +599,7 @@ def search_user_chats(
|
||||
|
||||
chat_list = [
|
||||
ChatTitleIdResponse(**chat.model_dump())
|
||||
for chat in Chats.get_chats_by_user_id_and_search_text(user.id, text, skip=skip, limit=limit, db=db)
|
||||
for chat in await Chats.get_chats_by_user_id_and_search_text(user.id, text, skip=skip, limit=limit, db=db)
|
||||
]
|
||||
|
||||
# Delete tag if no chat is found
|
||||
@@ -607,9 +607,9 @@ def search_user_chats(
|
||||
if page == 1 and len(words) == 1 and words[0].startswith('tag:'):
|
||||
tag_id = words[0].replace('tag:', '')
|
||||
if len(chat_list) == 0:
|
||||
if Tags.get_tag_by_name_and_user_id(tag_id, user.id, db=db):
|
||||
if await Tags.get_tag_by_name_and_user_id(tag_id, user.id, db=db):
|
||||
log.debug(f'deleting tag: {tag_id}')
|
||||
Tags.delete_tag_by_name_and_user_id(tag_id, user.id, db=db)
|
||||
await Tags.delete_tag_by_name_and_user_id(tag_id, user.id, db=db)
|
||||
|
||||
return chat_list
|
||||
|
||||
@@ -620,15 +620,15 @@ def search_user_chats(
|
||||
|
||||
|
||||
@router.get('/folder/{folder_id}', response_model=list[ChatResponse])
|
||||
async def get_chats_by_folder_id(folder_id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
async def get_chats_by_folder_id(folder_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
folder_ids = [folder_id]
|
||||
children_folders = Folders.get_children_folders_by_id_and_user_id(folder_id, user.id, db=db)
|
||||
children_folders = await Folders.get_children_folders_by_id_and_user_id(folder_id, user.id, db=db)
|
||||
if children_folders:
|
||||
folder_ids.extend([folder.id for folder in children_folders])
|
||||
|
||||
return [
|
||||
ChatResponse(**chat.model_dump())
|
||||
for chat in Chats.get_chats_by_folder_ids_and_user_id(folder_ids, user.id, db=db)
|
||||
for chat in await Chats.get_chats_by_folder_ids_and_user_id(folder_ids, user.id, db=db)
|
||||
]
|
||||
|
||||
|
||||
@@ -637,13 +637,13 @@ async def get_chat_list_by_folder_id(
|
||||
folder_id: str,
|
||||
page: Optional[int] = 1,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
try:
|
||||
limit = 10
|
||||
skip = (page - 1) * limit
|
||||
|
||||
chats = Chats.get_chats_by_folder_id_and_user_id(folder_id, user.id, skip=skip, limit=limit, db=db)
|
||||
chats = await Chats.get_chats_by_folder_id_and_user_id(folder_id, user.id, skip=skip, limit=limit, db=db)
|
||||
return [
|
||||
{'title': chat.title, 'id': chat.id, 'updated_at': chat.updated_at, 'last_read_at': chat.last_read_at}
|
||||
for chat in chats
|
||||
@@ -660,8 +660,8 @@ async def get_chat_list_by_folder_id(
|
||||
|
||||
|
||||
@router.get('/pinned', response_model=list[ChatTitleIdResponse])
|
||||
async def get_user_pinned_chats(user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
return Chats.get_pinned_chats_by_user_id(user.id, db=db)
|
||||
async def get_user_pinned_chats(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
return await Chats.get_pinned_chats_by_user_id(user.id, db=db)
|
||||
|
||||
|
||||
############################
|
||||
@@ -670,8 +670,8 @@ async def get_user_pinned_chats(user=Depends(get_verified_user), db: Session = D
|
||||
|
||||
|
||||
@router.get('/all', response_model=list[ChatResponse])
|
||||
async def get_user_chats(user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
result = Chats.get_chats_by_user_id(user.id, db=db)
|
||||
async def get_user_chats(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
result = await Chats.get_chats_by_user_id(user.id, db=db)
|
||||
return [ChatResponse(**chat.model_dump()) for chat in result.items]
|
||||
|
||||
|
||||
@@ -681,8 +681,8 @@ async def get_user_chats(user=Depends(get_verified_user), db: Session = Depends(
|
||||
|
||||
|
||||
@router.get('/all/archived', response_model=list[ChatResponse])
|
||||
async def get_user_archived_chats(user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
return [ChatResponse(**chat.model_dump()) for chat in Chats.get_archived_chats_by_user_id(user.id, db=db)]
|
||||
async def get_user_archived_chats(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
return [ChatResponse(**chat.model_dump()) for chat in await Chats.get_archived_chats_by_user_id(user.id, db=db)]
|
||||
|
||||
|
||||
############################
|
||||
@@ -691,9 +691,9 @@ async def get_user_archived_chats(user=Depends(get_verified_user), db: Session =
|
||||
|
||||
|
||||
@router.get('/all/tags', response_model=list[TagModel])
|
||||
async def get_all_user_tags(user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
async def get_all_user_tags(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
try:
|
||||
tags = Tags.get_tags_by_user_id(user.id, db=db)
|
||||
tags = await Tags.get_tags_by_user_id(user.id, db=db)
|
||||
return tags
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
@@ -706,13 +706,13 @@ async def get_all_user_tags(user=Depends(get_verified_user), db: Session = Depen
|
||||
|
||||
|
||||
@router.get('/all/db', response_model=list[ChatResponse])
|
||||
async def get_all_user_chats_in_db(user=Depends(get_admin_user), db: Session = Depends(get_session)):
|
||||
async def get_all_user_chats_in_db(user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
|
||||
if not ENABLE_ADMIN_EXPORT:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
return [ChatResponse(**chat.model_dump()) for chat in Chats.get_chats(db=db)]
|
||||
return [ChatResponse(**chat.model_dump()) for chat in await Chats.get_chats(db=db)]
|
||||
|
||||
|
||||
############################
|
||||
@@ -727,7 +727,7 @@ async def get_archived_session_user_chat_list(
|
||||
order_by: Optional[str] = None,
|
||||
direction: Optional[str] = None,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if page is None:
|
||||
page = 1
|
||||
@@ -743,7 +743,7 @@ async def get_archived_session_user_chat_list(
|
||||
if direction:
|
||||
filter['direction'] = direction
|
||||
|
||||
return Chats.get_archived_chat_list_by_user_id(
|
||||
return await Chats.get_archived_chat_list_by_user_id(
|
||||
user.id,
|
||||
filter=filter,
|
||||
skip=skip,
|
||||
@@ -758,8 +758,8 @@ async def get_archived_session_user_chat_list(
|
||||
|
||||
|
||||
@router.post('/archive/all', response_model=bool)
|
||||
async def archive_all_chats(user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
return Chats.archive_all_chats_by_user_id(user.id, db=db)
|
||||
async def archive_all_chats(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
return await Chats.archive_all_chats_by_user_id(user.id, db=db)
|
||||
|
||||
|
||||
############################
|
||||
@@ -768,8 +768,8 @@ async def archive_all_chats(user=Depends(get_verified_user), db: Session = Depen
|
||||
|
||||
|
||||
@router.post('/unarchive/all', response_model=bool)
|
||||
async def unarchive_all_chats(user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
return Chats.unarchive_all_chats_by_user_id(user.id, db=db)
|
||||
async def unarchive_all_chats(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
return await Chats.unarchive_all_chats_by_user_id(user.id, db=db)
|
||||
|
||||
|
||||
############################
|
||||
@@ -784,7 +784,7 @@ async def get_shared_session_user_chat_list(
|
||||
order_by: Optional[str] = None,
|
||||
direction: Optional[str] = None,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if page is None:
|
||||
page = 1
|
||||
@@ -800,7 +800,7 @@ async def get_shared_session_user_chat_list(
|
||||
if direction:
|
||||
filter['direction'] = direction
|
||||
|
||||
return Chats.get_shared_chat_list_by_user_id(
|
||||
return await Chats.get_shared_chat_list_by_user_id(
|
||||
user.id,
|
||||
filter=filter,
|
||||
skip=skip,
|
||||
@@ -815,14 +815,14 @@ async def get_shared_session_user_chat_list(
|
||||
|
||||
|
||||
@router.get('/share/{share_id}', response_model=Optional[ChatResponse])
|
||||
async def get_shared_chat_by_id(share_id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
async def get_shared_chat_by_id(share_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
if user.role == 'pending':
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND)
|
||||
|
||||
if user.role == 'user' or (user.role == 'admin' and not ENABLE_ADMIN_CHAT_ACCESS):
|
||||
chat = Chats.get_chat_by_share_id(share_id, db=db)
|
||||
chat = await Chats.get_chat_by_share_id(share_id, db=db)
|
||||
elif user.role == 'admin' and ENABLE_ADMIN_CHAT_ACCESS:
|
||||
chat = Chats.get_chat_by_id(share_id, db=db)
|
||||
chat = await Chats.get_chat_by_id(share_id, db=db)
|
||||
|
||||
if chat:
|
||||
return ChatResponse(**chat.model_dump())
|
||||
@@ -849,11 +849,11 @@ class TagFilterForm(TagForm):
|
||||
async def get_user_chat_list_by_tag_name(
|
||||
form_data: TagFilterForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
chats = Chats.get_chat_list_by_user_id_and_tag_name(user.id, form_data.name, form_data.skip, form_data.limit, db=db)
|
||||
chats = await Chats.get_chat_list_by_user_id_and_tag_name(user.id, form_data.name, form_data.skip, form_data.limit, db=db)
|
||||
if len(chats) == 0:
|
||||
Tags.delete_tag_by_name_and_user_id(form_data.name, user.id, db=db)
|
||||
await Tags.delete_tag_by_name_and_user_id(form_data.name, user.id, db=db)
|
||||
|
||||
return chats
|
||||
|
||||
@@ -864,8 +864,8 @@ async def get_user_chat_list_by_tag_name(
|
||||
|
||||
|
||||
@router.get('/{id}', response_model=Optional[ChatResponse])
|
||||
async def get_chat_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
async def get_chat_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
|
||||
if chat:
|
||||
return ChatResponse(**chat.model_dump())
|
||||
@@ -884,12 +884,12 @@ async def update_chat_by_id(
|
||||
id: str,
|
||||
form_data: ChatForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
if chat:
|
||||
updated_chat = {**chat.chat, **form_data.chat}
|
||||
chat = Chats.update_chat_by_id(id, updated_chat, db=db)
|
||||
chat = await Chats.update_chat_by_id(id, updated_chat, db=db)
|
||||
return ChatResponse(**chat.model_dump())
|
||||
else:
|
||||
raise HTTPException(
|
||||
@@ -911,9 +911,9 @@ async def update_chat_message_by_id(
|
||||
message_id: str,
|
||||
form_data: MessageForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
chat = Chats.get_chat_by_id(id, db=db)
|
||||
chat = await Chats.get_chat_by_id(id, db=db)
|
||||
|
||||
if not chat:
|
||||
raise HTTPException(
|
||||
@@ -927,7 +927,7 @@ async def update_chat_message_by_id(
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
chat = Chats.upsert_message_to_chat_by_id_and_message_id(
|
||||
chat = await Chats.upsert_message_to_chat_by_id_and_message_id(
|
||||
id,
|
||||
message_id,
|
||||
{
|
||||
@@ -935,7 +935,7 @@ async def update_chat_message_by_id(
|
||||
},
|
||||
)
|
||||
|
||||
event_emitter = get_event_emitter(
|
||||
event_emitter = await get_event_emitter(
|
||||
{
|
||||
'user_id': user.id,
|
||||
'chat_id': id,
|
||||
@@ -973,9 +973,9 @@ async def send_chat_message_event_by_id(
|
||||
message_id: str,
|
||||
form_data: EventForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
chat = Chats.get_chat_by_id(id, db=db)
|
||||
chat = await Chats.get_chat_by_id(id, db=db)
|
||||
|
||||
if not chat:
|
||||
raise HTTPException(
|
||||
@@ -989,7 +989,7 @@ async def send_chat_message_event_by_id(
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
event_emitter = get_event_emitter(
|
||||
event_emitter = await get_event_emitter(
|
||||
{
|
||||
'user_id': user.id,
|
||||
'chat_id': id,
|
||||
@@ -1017,36 +1017,36 @@ async def delete_chat_by_id(
|
||||
request: Request,
|
||||
id: str,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if user.role == 'admin':
|
||||
chat = Chats.get_chat_by_id(id, db=db)
|
||||
chat = await Chats.get_chat_by_id(id, db=db)
|
||||
if not chat:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
Chats.delete_orphan_tags_for_user(chat.meta.get('tags', []), user.id, threshold=1, db=db)
|
||||
await Chats.delete_orphan_tags_for_user(chat.meta.get('tags', []), user.id, threshold=1, db=db)
|
||||
|
||||
result = Chats.delete_chat_by_id(id, db=db)
|
||||
result = await Chats.delete_chat_by_id(id, db=db)
|
||||
|
||||
return result
|
||||
else:
|
||||
if not has_permission(user.id, 'chat.delete', request.app.state.config.USER_PERMISSIONS):
|
||||
if not await has_permission(user.id, 'chat.delete', request.app.state.config.USER_PERMISSIONS):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
if not chat:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
Chats.delete_orphan_tags_for_user(chat.meta.get('tags', []), user.id, threshold=1, db=db)
|
||||
await Chats.delete_orphan_tags_for_user(chat.meta.get('tags', []), user.id, threshold=1, db=db)
|
||||
|
||||
result = Chats.delete_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
result = await Chats.delete_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
return result
|
||||
|
||||
|
||||
@@ -1056,8 +1056,8 @@ async def delete_chat_by_id(
|
||||
|
||||
|
||||
@router.get('/{id}/pinned', response_model=Optional[bool])
|
||||
async def get_pinned_status_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
async def get_pinned_status_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
if chat:
|
||||
return chat.pinned
|
||||
else:
|
||||
@@ -1070,10 +1070,10 @@ async def get_pinned_status_by_id(id: str, user=Depends(get_verified_user), db:
|
||||
|
||||
|
||||
@router.post('/{id}/pin', response_model=Optional[ChatResponse])
|
||||
async def pin_chat_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
async def pin_chat_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
if chat:
|
||||
chat = Chats.toggle_chat_pinned_by_id(id, db=db)
|
||||
chat = await Chats.toggle_chat_pinned_by_id(id, db=db)
|
||||
return chat
|
||||
else:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT())
|
||||
@@ -1093,9 +1093,9 @@ async def clone_chat_by_id(
|
||||
form_data: CloneForm,
|
||||
id: str,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
if chat:
|
||||
updated_chat = {
|
||||
**chat.chat,
|
||||
@@ -1104,7 +1104,7 @@ async def clone_chat_by_id(
|
||||
'title': form_data.title if form_data.title else f'Clone of {chat.title}',
|
||||
}
|
||||
|
||||
chats = Chats.import_chats(
|
||||
chats = await Chats.import_chats(
|
||||
user.id,
|
||||
[
|
||||
ChatImportForm(
|
||||
@@ -1137,11 +1137,11 @@ async def clone_chat_by_id(
|
||||
|
||||
|
||||
@router.post('/{id}/clone/shared', response_model=Optional[ChatResponse])
|
||||
async def clone_shared_chat_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
async def clone_shared_chat_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
if user.role == 'admin':
|
||||
chat = Chats.get_chat_by_id(id, db=db)
|
||||
chat = await Chats.get_chat_by_id(id, db=db)
|
||||
else:
|
||||
chat = Chats.get_chat_by_share_id(id, db=db)
|
||||
chat = await Chats.get_chat_by_share_id(id, db=db)
|
||||
|
||||
if chat:
|
||||
updated_chat = {
|
||||
@@ -1151,7 +1151,7 @@ async def clone_shared_chat_by_id(id: str, user=Depends(get_verified_user), db:
|
||||
'title': f'Clone of {chat.title}',
|
||||
}
|
||||
|
||||
chats = Chats.import_chats(
|
||||
chats = await Chats.import_chats(
|
||||
user.id,
|
||||
[
|
||||
ChatImportForm(
|
||||
@@ -1184,18 +1184,18 @@ async def clone_shared_chat_by_id(id: str, user=Depends(get_verified_user), db:
|
||||
|
||||
|
||||
@router.post('/{id}/archive', response_model=Optional[ChatResponse])
|
||||
async def archive_chat_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
async def archive_chat_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
if chat:
|
||||
chat = Chats.toggle_chat_archive_by_id(id, db=db)
|
||||
chat = await Chats.toggle_chat_archive_by_id(id, db=db)
|
||||
|
||||
tag_ids = chat.meta.get('tags', [])
|
||||
if chat.archived:
|
||||
# Archived chats are excluded from count — clean up orphans
|
||||
Chats.delete_orphan_tags_for_user(tag_ids, user.id, db=db)
|
||||
await Chats.delete_orphan_tags_for_user(tag_ids, user.id, db=db)
|
||||
else:
|
||||
# Unarchived — ensure tag rows exist
|
||||
Tags.ensure_tags_exist(tag_ids, user.id, db=db)
|
||||
await Tags.ensure_tags_exist(tag_ids, user.id, db=db)
|
||||
|
||||
return ChatResponse(**chat.model_dump())
|
||||
else:
|
||||
@@ -1212,24 +1212,24 @@ async def share_chat_by_id(
|
||||
request: Request,
|
||||
id: str,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if (user.role != 'admin') and (
|
||||
not has_permission(user.id, 'chat.share', request.app.state.config.USER_PERMISSIONS)
|
||||
not await has_permission(user.id, 'chat.share', request.app.state.config.USER_PERMISSIONS)
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
|
||||
if chat:
|
||||
if chat.share_id:
|
||||
shared_chat = Chats.update_shared_chat_by_chat_id(chat.id, db=db)
|
||||
shared_chat = await Chats.update_shared_chat_by_chat_id(chat.id, db=db)
|
||||
return ChatResponse(**shared_chat.model_dump())
|
||||
|
||||
shared_chat = Chats.insert_shared_chat_by_chat_id(chat.id, db=db)
|
||||
shared_chat = await Chats.insert_shared_chat_by_chat_id(chat.id, db=db)
|
||||
if not shared_chat:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -1250,14 +1250,14 @@ async def share_chat_by_id(
|
||||
|
||||
|
||||
@router.delete('/{id}/share', response_model=Optional[bool])
|
||||
async def delete_shared_chat_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
async def delete_shared_chat_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
if chat:
|
||||
if not chat.share_id:
|
||||
return False
|
||||
|
||||
result = Chats.delete_shared_chat_by_chat_id(id, db=db)
|
||||
update_result = Chats.update_chat_share_id_by_id(id, None, db=db)
|
||||
result = await Chats.delete_shared_chat_by_chat_id(id, db=db)
|
||||
update_result = await Chats.update_chat_share_id_by_id(id, None, db=db)
|
||||
|
||||
return result and update_result != None
|
||||
else:
|
||||
@@ -1281,11 +1281,11 @@ async def update_chat_folder_id_by_id(
|
||||
id: str,
|
||||
form_data: ChatFolderIdForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
if chat:
|
||||
chat = Chats.update_chat_folder_id_by_id_and_user_id(id, user.id, form_data.folder_id, db=db)
|
||||
chat = await Chats.update_chat_folder_id_by_id_and_user_id(id, user.id, form_data.folder_id, db=db)
|
||||
return ChatResponse(**chat.model_dump())
|
||||
else:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT())
|
||||
@@ -1297,11 +1297,11 @@ async def update_chat_folder_id_by_id(
|
||||
|
||||
|
||||
@router.get('/{id}/tags', response_model=list[TagModel])
|
||||
async def get_chat_tags_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
async def get_chat_tags_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
if chat:
|
||||
tags = chat.meta.get('tags', [])
|
||||
return Tags.get_tags_by_ids_and_user_id(tags, user.id, db=db)
|
||||
return await Tags.get_tags_by_ids_and_user_id(tags, user.id, db=db)
|
||||
else:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND)
|
||||
|
||||
@@ -1316,9 +1316,9 @@ async def add_tag_by_id_and_tag_name(
|
||||
id: str,
|
||||
form_data: TagForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
if chat:
|
||||
tags = chat.meta.get('tags', [])
|
||||
tag_id = form_data.name.replace(' ', '_').lower()
|
||||
@@ -1330,11 +1330,11 @@ async def add_tag_by_id_and_tag_name(
|
||||
)
|
||||
|
||||
if tag_id not in tags:
|
||||
Chats.add_chat_tag_by_id_and_user_id_and_tag_name(id, user.id, form_data.name, db=db)
|
||||
await Chats.add_chat_tag_by_id_and_user_id_and_tag_name(id, user.id, form_data.name, db=db)
|
||||
|
||||
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
tags = chat.meta.get('tags', [])
|
||||
return Tags.get_tags_by_ids_and_user_id(tags, user.id, db=db)
|
||||
return await Tags.get_tags_by_ids_and_user_id(tags, user.id, db=db)
|
||||
else:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.DEFAULT())
|
||||
|
||||
@@ -1349,18 +1349,18 @@ async def delete_tag_by_id_and_tag_name(
|
||||
id: str,
|
||||
form_data: TagForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
if chat:
|
||||
Chats.delete_tag_by_id_and_user_id_and_tag_name(id, user.id, form_data.name, db=db)
|
||||
await Chats.delete_tag_by_id_and_user_id_and_tag_name(id, user.id, form_data.name, db=db)
|
||||
|
||||
if Chats.count_chats_by_tag_name_and_user_id(form_data.name, user.id, db=db) == 0:
|
||||
Tags.delete_tag_by_name_and_user_id(form_data.name, user.id, db=db)
|
||||
if await Chats.count_chats_by_tag_name_and_user_id(form_data.name, user.id, db=db) == 0:
|
||||
await Tags.delete_tag_by_name_and_user_id(form_data.name, user.id, db=db)
|
||||
|
||||
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
tags = chat.meta.get('tags', [])
|
||||
return Tags.get_tags_by_ids_and_user_id(tags, user.id, db=db)
|
||||
return await Tags.get_tags_by_ids_and_user_id(tags, user.id, db=db)
|
||||
else:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=ERROR_MESSAGES.NOT_FOUND)
|
||||
|
||||
@@ -1371,12 +1371,12 @@ async def delete_tag_by_id_and_tag_name(
|
||||
|
||||
|
||||
@router.delete('/{id}/tags/all', response_model=Optional[bool])
|
||||
async def delete_all_tags_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
chat = Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
async def delete_all_tags_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
chat = await Chats.get_chat_by_id_and_user_id(id, user.id, db=db)
|
||||
if chat:
|
||||
old_tags = chat.meta.get('tags', [])
|
||||
Chats.delete_all_tags_by_id_and_user_id(id, user.id, db=db)
|
||||
Chats.delete_orphan_tags_for_user(old_tags, user.id, db=db)
|
||||
await Chats.delete_all_tags_by_id_and_user_id(id, user.id, db=db)
|
||||
await Chats.delete_orphan_tags_for_user(old_tags, user.id, db=db)
|
||||
|
||||
return True
|
||||
else:
|
||||
|
||||
@@ -20,8 +20,8 @@ from open_webui.models.feedbacks import (
|
||||
|
||||
from open_webui.constants import ERROR_MESSAGES
|
||||
from open_webui.utils.auth import get_admin_user, get_verified_user
|
||||
from open_webui.internal.db import get_session
|
||||
from sqlalchemy.orm import Session
|
||||
from open_webui.internal.db import get_async_session
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -208,10 +208,10 @@ class LeaderboardResponse(BaseModel):
|
||||
async def get_leaderboard(
|
||||
query: Optional[str] = None,
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""Get model leaderboard with Elo ratings. Query filters by tag similarity."""
|
||||
feedbacks = Feedbacks.get_feedbacks_for_leaderboard(db=db)
|
||||
feedbacks = await Feedbacks.get_feedbacks_for_leaderboard(db=db)
|
||||
|
||||
similarities = None
|
||||
if query and query.strip():
|
||||
@@ -244,10 +244,10 @@ async def get_model_history(
|
||||
model_id: str,
|
||||
days: int = 30,
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""Get daily win/loss history for a specific model."""
|
||||
history = Feedbacks.get_model_evaluation_history(model_id=model_id, days=days, db=db)
|
||||
history = await Feedbacks.get_model_evaluation_history(model_id=model_id, days=days, db=db)
|
||||
return ModelHistoryResponse(model_id=model_id, history=history)
|
||||
|
||||
|
||||
@@ -292,24 +292,24 @@ async def update_config(
|
||||
|
||||
|
||||
@router.get('/feedbacks/models', response_model=list[str])
|
||||
async def get_feedback_model_ids(user=Depends(get_admin_user), db: Session = Depends(get_session)):
|
||||
return Feedbacks.get_distinct_model_ids(db=db)
|
||||
async def get_feedback_model_ids(user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
|
||||
return await Feedbacks.get_distinct_model_ids(db=db)
|
||||
|
||||
|
||||
@router.get('/feedbacks/all', response_model=list[FeedbackResponse])
|
||||
async def get_all_feedbacks(user=Depends(get_admin_user), db: Session = Depends(get_session)):
|
||||
feedbacks = Feedbacks.get_all_feedbacks(db=db)
|
||||
async def get_all_feedbacks(user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
|
||||
feedbacks = await Feedbacks.get_all_feedbacks(db=db)
|
||||
return feedbacks
|
||||
|
||||
|
||||
@router.get('/feedbacks/all/ids', response_model=list[FeedbackIdResponse])
|
||||
async def get_all_feedback_ids(user=Depends(get_admin_user), db: Session = Depends(get_session)):
|
||||
return Feedbacks.get_all_feedback_ids(db=db)
|
||||
async def get_all_feedback_ids(user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
|
||||
return await Feedbacks.get_all_feedback_ids(db=db)
|
||||
|
||||
|
||||
@router.delete('/feedbacks/all')
|
||||
async def delete_all_feedbacks(user=Depends(get_admin_user), db: Session = Depends(get_session)):
|
||||
success = Feedbacks.delete_all_feedbacks(db=db)
|
||||
async def delete_all_feedbacks(user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
|
||||
success = await Feedbacks.delete_all_feedbacks(db=db)
|
||||
return success
|
||||
|
||||
|
||||
@@ -317,23 +317,23 @@ async def delete_all_feedbacks(user=Depends(get_admin_user), db: Session = Depen
|
||||
async def export_all_feedbacks(
|
||||
model_id: Optional[str] = None,
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
feedbacks = Feedbacks.get_all_feedbacks(db=db)
|
||||
feedbacks = await Feedbacks.get_all_feedbacks(db=db)
|
||||
if model_id:
|
||||
feedbacks = [f for f in feedbacks if f.data and f.data.get('model_id') == model_id]
|
||||
return feedbacks
|
||||
|
||||
|
||||
@router.get('/feedbacks/user', response_model=list[FeedbackUserResponse])
|
||||
async def get_feedbacks(user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
feedbacks = Feedbacks.get_feedbacks_by_user_id(user.id, db=db)
|
||||
async def get_feedbacks(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
feedbacks = await Feedbacks.get_feedbacks_by_user_id(user.id, db=db)
|
||||
return feedbacks
|
||||
|
||||
|
||||
@router.delete('/feedbacks', response_model=bool)
|
||||
async def delete_feedbacks(user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
success = Feedbacks.delete_feedbacks_by_user_id(user.id, db=db)
|
||||
async def delete_feedbacks(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
success = await Feedbacks.delete_feedbacks_by_user_id(user.id, db=db)
|
||||
return success
|
||||
|
||||
|
||||
@@ -347,7 +347,7 @@ async def get_feedbacks(
|
||||
page: Optional[int] = 1,
|
||||
model_id: Optional[str] = None,
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
limit = PAGE_ITEM_COUNT
|
||||
|
||||
@@ -362,7 +362,7 @@ async def get_feedbacks(
|
||||
if model_id:
|
||||
filter['model_id'] = model_id
|
||||
|
||||
result = Feedbacks.get_feedback_items(filter=filter, skip=skip, limit=limit, db=db)
|
||||
result = await Feedbacks.get_feedback_items(filter=filter, skip=skip, limit=limit, db=db)
|
||||
return result
|
||||
|
||||
|
||||
@@ -371,9 +371,9 @@ async def create_feedback(
|
||||
request: Request,
|
||||
form_data: FeedbackForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
feedback = Feedbacks.insert_new_feedback(user_id=user.id, form_data=form_data, db=db)
|
||||
feedback = await Feedbacks.insert_new_feedback(user_id=user.id, form_data=form_data, db=db)
|
||||
if not feedback:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
@@ -384,11 +384,11 @@ async def create_feedback(
|
||||
|
||||
|
||||
@router.get('/feedback/{id}', response_model=FeedbackModel)
|
||||
async def get_feedback_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
async def get_feedback_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
if user.role == 'admin':
|
||||
feedback = Feedbacks.get_feedback_by_id(id=id, db=db)
|
||||
feedback = await Feedbacks.get_feedback_by_id(id=id, db=db)
|
||||
else:
|
||||
feedback = Feedbacks.get_feedback_by_id_and_user_id(id=id, user_id=user.id, db=db)
|
||||
feedback = await Feedbacks.get_feedback_by_id_and_user_id(id=id, user_id=user.id, db=db)
|
||||
|
||||
if not feedback:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND)
|
||||
@@ -401,12 +401,12 @@ async def update_feedback_by_id(
|
||||
id: str,
|
||||
form_data: FeedbackForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if user.role == 'admin':
|
||||
feedback = Feedbacks.update_feedback_by_id(id=id, form_data=form_data, db=db)
|
||||
feedback = await Feedbacks.update_feedback_by_id(id=id, form_data=form_data, db=db)
|
||||
else:
|
||||
feedback = Feedbacks.update_feedback_by_id_and_user_id(id=id, user_id=user.id, form_data=form_data, db=db)
|
||||
feedback = await Feedbacks.update_feedback_by_id_and_user_id(id=id, user_id=user.id, form_data=form_data, db=db)
|
||||
|
||||
if not feedback:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND)
|
||||
@@ -415,11 +415,11 @@ async def update_feedback_by_id(
|
||||
|
||||
|
||||
@router.delete('/feedback/{id}')
|
||||
async def delete_feedback_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
async def delete_feedback_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
if user.role == 'admin':
|
||||
success = Feedbacks.delete_feedback_by_id(id=id, db=db)
|
||||
success = await Feedbacks.delete_feedback_by_id(id=id, db=db)
|
||||
else:
|
||||
success = Feedbacks.delete_feedback_by_id_and_user_id(id=id, user_id=user.id, db=db)
|
||||
success = await Feedbacks.delete_feedback_by_id_and_user_id(id=id, user_id=user.id, db=db)
|
||||
|
||||
if not success:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND)
|
||||
|
||||
@@ -21,8 +21,8 @@ from fastapi import (
|
||||
)
|
||||
|
||||
from fastapi.responses import FileResponse, StreamingResponse
|
||||
from sqlalchemy.orm import Session
|
||||
from open_webui.internal.db import get_session, SessionLocal
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from open_webui.internal.db import get_async_session, SessionLocal
|
||||
|
||||
from open_webui.constants import ERROR_MESSAGES
|
||||
from open_webui.retrieval.vector.factory import VECTOR_DB_CLIENT
|
||||
@@ -88,16 +88,16 @@ def _is_text_file(file_path: str, chunk_size: int = 8192) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def process_uploaded_file(
|
||||
async def process_uploaded_file(
|
||||
request,
|
||||
file,
|
||||
file_path,
|
||||
file_item,
|
||||
file_metadata,
|
||||
user,
|
||||
db: Optional[Session] = None,
|
||||
db: Optional[AsyncSession] = None,
|
||||
):
|
||||
def _process_handler(db_session):
|
||||
async def _process_handler(db_session):
|
||||
try:
|
||||
content_type = file.content_type
|
||||
|
||||
@@ -141,7 +141,7 @@ def process_uploaded_file(
|
||||
|
||||
except Exception as e:
|
||||
log.error(f'Error processing file: {file_item.id}')
|
||||
Files.update_file_data_by_id(
|
||||
await Files.update_file_data_by_id(
|
||||
file_item.id,
|
||||
{
|
||||
'status': 'failed',
|
||||
@@ -158,7 +158,7 @@ def process_uploaded_file(
|
||||
|
||||
|
||||
@router.post('/', response_model=FileModelResponse)
|
||||
def upload_file(
|
||||
async def upload_file(
|
||||
request: Request,
|
||||
background_tasks: BackgroundTasks,
|
||||
file: UploadFile = File(...),
|
||||
@@ -166,9 +166,9 @@ def upload_file(
|
||||
process: bool = Query(True),
|
||||
process_in_background: bool = Query(True),
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
return upload_file_handler(
|
||||
return await upload_file_handler(
|
||||
request,
|
||||
file=file,
|
||||
metadata=metadata,
|
||||
@@ -180,7 +180,7 @@ def upload_file(
|
||||
)
|
||||
|
||||
|
||||
def upload_file_handler(
|
||||
async def upload_file_handler(
|
||||
request: Request,
|
||||
file: UploadFile = File(...),
|
||||
metadata: Optional[dict | str] = Form(None),
|
||||
@@ -188,7 +188,7 @@ def upload_file_handler(
|
||||
process_in_background: bool = Query(True),
|
||||
user=Depends(get_verified_user),
|
||||
background_tasks: Optional[BackgroundTasks] = None,
|
||||
db: Optional[Session] = None,
|
||||
db: Optional[AsyncSession] = None,
|
||||
):
|
||||
log.info(f'file.content_type: {file.content_type} {process}')
|
||||
|
||||
@@ -236,7 +236,7 @@ def upload_file_handler(
|
||||
},
|
||||
)
|
||||
|
||||
file_item = Files.insert_new_file(
|
||||
file_item = await Files.insert_new_file(
|
||||
user.id,
|
||||
FileForm(
|
||||
**{
|
||||
@@ -258,9 +258,9 @@ def upload_file_handler(
|
||||
)
|
||||
|
||||
if 'channel_id' in file_metadata:
|
||||
channel = Channels.get_channel_by_id_and_user_id(file_metadata['channel_id'], user.id, db=db)
|
||||
channel = await Channels.get_channel_by_id_and_user_id(file_metadata['channel_id'], user.id, db=db)
|
||||
if channel:
|
||||
Channels.add_file_to_channel_by_id(channel.id, file_item.id, user.id, db=db)
|
||||
await Channels.add_file_to_channel_by_id(channel.id, file_item.id, user.id, db=db)
|
||||
|
||||
if process:
|
||||
if background_tasks and process_in_background:
|
||||
@@ -275,7 +275,7 @@ def upload_file_handler(
|
||||
)
|
||||
return {'status': True, **file_item.model_dump()}
|
||||
else:
|
||||
process_uploaded_file(
|
||||
await process_uploaded_file(
|
||||
request,
|
||||
file,
|
||||
file_path,
|
||||
@@ -317,12 +317,12 @@ async def list_files(
|
||||
user=Depends(get_verified_user),
|
||||
page: int = Query(1, ge=1, description='Page number (1-indexed)'),
|
||||
content: bool = Query(True),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
skip = (page - 1) * PAGE_SIZE
|
||||
user_id = None if (user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL) else user.id
|
||||
|
||||
result = Files.get_file_list(user_id=user_id, skip=skip, limit=PAGE_SIZE, db=db)
|
||||
result = await Files.get_file_list(user_id=user_id, skip=skip, limit=PAGE_SIZE, db=db)
|
||||
|
||||
if not content:
|
||||
for file in result.items:
|
||||
@@ -347,7 +347,7 @@ async def search_files(
|
||||
skip: int = Query(0, ge=0, description='Number of files to skip'),
|
||||
limit: int = Query(100, ge=1, le=1000, description='Maximum number of files to return'),
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""
|
||||
Search for files by filename with support for wildcard patterns.
|
||||
@@ -357,7 +357,7 @@ async def search_files(
|
||||
user_id = None if (user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL) else user.id
|
||||
|
||||
# Use optimized database query with pagination
|
||||
files = Files.search_files(
|
||||
files = await Files.search_files(
|
||||
user_id=user_id,
|
||||
filename=filename,
|
||||
skip=skip,
|
||||
@@ -385,8 +385,8 @@ async def search_files(
|
||||
|
||||
|
||||
@router.delete('/all')
|
||||
async def delete_all_files(user=Depends(get_admin_user), db: Session = Depends(get_session)):
|
||||
result = Files.delete_all_files(db=db)
|
||||
async def delete_all_files(user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
|
||||
result = await Files.delete_all_files(db=db)
|
||||
if result:
|
||||
try:
|
||||
Storage.delete_all_files()
|
||||
@@ -412,8 +412,8 @@ async def delete_all_files(user=Depends(get_admin_user), db: Session = Depends(g
|
||||
|
||||
|
||||
@router.get('/{id}', response_model=Optional[FileModel])
|
||||
async def get_file_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
file = Files.get_file_by_id(id, db=db)
|
||||
async def get_file_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
file = await Files.get_file_by_id(id, db=db)
|
||||
|
||||
if not file:
|
||||
raise HTTPException(
|
||||
@@ -421,7 +421,7 @@ async def get_file_by_id(id: str, user=Depends(get_verified_user), db: Session =
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if file.user_id == user.id or user.role == 'admin' or has_access_to_file(id, 'read', user, db=db):
|
||||
if file.user_id == user.id or user.role == 'admin' or await has_access_to_file(id, 'read', user, db=db):
|
||||
return file
|
||||
else:
|
||||
raise HTTPException(
|
||||
@@ -435,9 +435,9 @@ async def get_file_process_status(
|
||||
id: str,
|
||||
stream: bool = Query(False),
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
file = Files.get_file_by_id(id, db=db)
|
||||
file = await Files.get_file_by_id(id, db=db)
|
||||
|
||||
if not file:
|
||||
raise HTTPException(
|
||||
@@ -445,7 +445,7 @@ async def get_file_process_status(
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if file.user_id == user.id or user.role == 'admin' or has_access_to_file(id, 'read', user, db=db):
|
||||
if file.user_id == user.id or user.role == 'admin' or await has_access_to_file(id, 'read', user, db=db):
|
||||
if stream:
|
||||
MAX_FILE_PROCESSING_DURATION = 3600 * 2
|
||||
|
||||
@@ -454,7 +454,7 @@ async def get_file_process_status(
|
||||
# Each poll creates its own short-lived session to avoid holding a
|
||||
# connection for hours. A WebSocket push would be more efficient.
|
||||
for _ in range(MAX_FILE_PROCESSING_DURATION):
|
||||
file_item = Files.get_file_by_id(file_id) # Creates own session
|
||||
file_item = await Files.get_file_by_id(file_id) # Creates own session
|
||||
if file_item:
|
||||
data = file_item.model_dump().get('data', {})
|
||||
status = data.get('status')
|
||||
@@ -495,8 +495,8 @@ async def get_file_process_status(
|
||||
|
||||
|
||||
@router.get('/{id}/data/content')
|
||||
async def get_file_data_content_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
file = Files.get_file_by_id(id, db=db)
|
||||
async def get_file_data_content_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
file = await Files.get_file_by_id(id, db=db)
|
||||
|
||||
if not file:
|
||||
raise HTTPException(
|
||||
@@ -504,7 +504,7 @@ async def get_file_data_content_by_id(id: str, user=Depends(get_verified_user),
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if file.user_id == user.id or user.role == 'admin' or has_access_to_file(id, 'read', user, db=db):
|
||||
if file.user_id == user.id or user.role == 'admin' or await has_access_to_file(id, 'read', user, db=db):
|
||||
return {'content': file.data.get('content', '')}
|
||||
else:
|
||||
raise HTTPException(
|
||||
@@ -523,14 +523,14 @@ class ContentForm(BaseModel):
|
||||
|
||||
|
||||
@router.post('/{id}/data/content/update')
|
||||
def update_file_data_content_by_id(
|
||||
async def update_file_data_content_by_id(
|
||||
request: Request,
|
||||
id: str,
|
||||
form_data: ContentForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
file = Files.get_file_by_id(id, db=db)
|
||||
file = await Files.get_file_by_id(id, db=db)
|
||||
|
||||
if not file:
|
||||
raise HTTPException(
|
||||
@@ -538,7 +538,7 @@ def update_file_data_content_by_id(
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if file.user_id == user.id or user.role == 'admin' or has_access_to_file(id, 'write', user, db=db):
|
||||
if file.user_id == user.id or user.role == 'admin' or await has_access_to_file(id, 'write', user, db=db):
|
||||
try:
|
||||
process_file(
|
||||
request,
|
||||
@@ -546,7 +546,7 @@ def update_file_data_content_by_id(
|
||||
user=user,
|
||||
db=db,
|
||||
)
|
||||
file = Files.get_file_by_id(id=id, db=db)
|
||||
file = await Files.get_file_by_id(id=id, db=db)
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
log.error(f'Error processing file: {file.id}')
|
||||
@@ -554,7 +554,7 @@ def update_file_data_content_by_id(
|
||||
# Propagate content change to all knowledge collections referencing
|
||||
# this file. Without this the old embeddings remain in the knowledge
|
||||
# collection and RAG returns both stale and current data (#20558).
|
||||
knowledges = Knowledges.get_knowledges_by_file_id(id, db=db)
|
||||
knowledges = await Knowledges.get_knowledges_by_file_id(id, db=db)
|
||||
for knowledge in knowledges:
|
||||
try:
|
||||
# Remove old embeddings for this file from the KB collection
|
||||
@@ -587,9 +587,9 @@ async def get_file_content_by_id(
|
||||
id: str,
|
||||
user=Depends(get_verified_user),
|
||||
attachment: bool = Query(False),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
file = Files.get_file_by_id(id, db=db)
|
||||
file = await Files.get_file_by_id(id, db=db)
|
||||
|
||||
if not file:
|
||||
raise HTTPException(
|
||||
@@ -597,7 +597,7 @@ async def get_file_content_by_id(
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if file.user_id == user.id or user.role == 'admin' or has_access_to_file(id, 'read', user, db=db):
|
||||
if file.user_id == user.id or user.role == 'admin' or await has_access_to_file(id, 'read', user, db=db):
|
||||
try:
|
||||
file_path = Storage.get_file(file.path)
|
||||
file_path = Path(file_path)
|
||||
@@ -646,8 +646,8 @@ async def get_file_content_by_id(
|
||||
|
||||
|
||||
@router.get('/{id}/content/html')
|
||||
async def get_html_file_content_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
file = Files.get_file_by_id(id, db=db)
|
||||
async def get_html_file_content_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
file = await Files.get_file_by_id(id, db=db)
|
||||
|
||||
if not file:
|
||||
raise HTTPException(
|
||||
@@ -655,14 +655,14 @@ async def get_html_file_content_by_id(id: str, user=Depends(get_verified_user),
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
file_user = Users.get_user_by_id(file.user_id, db=db)
|
||||
file_user = await Users.get_user_by_id(file.user_id, db=db)
|
||||
if not file_user or file_user.role != 'admin':
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if file.user_id == user.id or user.role == 'admin' or has_access_to_file(id, 'read', user, db=db):
|
||||
if file.user_id == user.id or user.role == 'admin' or await has_access_to_file(id, 'read', user, db=db):
|
||||
try:
|
||||
file_path = Storage.get_file(file.path)
|
||||
file_path = Path(file_path)
|
||||
@@ -693,8 +693,8 @@ async def get_html_file_content_by_id(id: str, user=Depends(get_verified_user),
|
||||
|
||||
|
||||
@router.get('/{id}/content/{file_name}')
|
||||
async def get_file_content_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
file = Files.get_file_by_id(id, db=db)
|
||||
async def get_file_content_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
file = await Files.get_file_by_id(id, db=db)
|
||||
|
||||
if not file:
|
||||
raise HTTPException(
|
||||
@@ -702,7 +702,7 @@ async def get_file_content_by_id(id: str, user=Depends(get_verified_user), db: S
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if file.user_id == user.id or user.role == 'admin' or has_access_to_file(id, 'read', user, db=db):
|
||||
if file.user_id == user.id or user.role == 'admin' or await has_access_to_file(id, 'read', user, db=db):
|
||||
file_path = file.path
|
||||
|
||||
# Handle Unicode filenames
|
||||
@@ -749,8 +749,8 @@ async def get_file_content_by_id(id: str, user=Depends(get_verified_user), db: S
|
||||
|
||||
|
||||
@router.delete('/{id}')
|
||||
async def delete_file_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
file = Files.get_file_by_id(id, db=db)
|
||||
async def delete_file_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
file = await Files.get_file_by_id(id, db=db)
|
||||
|
||||
if not file:
|
||||
raise HTTPException(
|
||||
@@ -758,12 +758,12 @@ async def delete_file_by_id(id: str, user=Depends(get_verified_user), db: Sessio
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if file.user_id == user.id or user.role == 'admin' or has_access_to_file(id, 'write', user, db=db):
|
||||
if file.user_id == user.id or user.role == 'admin' or await has_access_to_file(id, 'write', user, db=db):
|
||||
# Clean up KB associations and embeddings before deleting
|
||||
knowledges = Knowledges.get_knowledges_by_file_id(id, db=db)
|
||||
knowledges = await Knowledges.get_knowledges_by_file_id(id, db=db)
|
||||
for knowledge in knowledges:
|
||||
# Remove KB-file relationship
|
||||
Knowledges.remove_file_from_knowledge_by_id(knowledge.id, id, db=db)
|
||||
await Knowledges.remove_file_from_knowledge_by_id(knowledge.id, id, db=db)
|
||||
# Clean KB embeddings (same logic as /knowledge/{id}/file/remove)
|
||||
try:
|
||||
VECTOR_DB_CLIENT.delete(collection_name=knowledge.id, filter={'file_id': id})
|
||||
@@ -772,7 +772,7 @@ async def delete_file_by_id(id: str, user=Depends(get_verified_user), db: Sessio
|
||||
except Exception as e:
|
||||
log.debug(f'KB embedding cleanup for {knowledge.id}: {e}')
|
||||
|
||||
result = Files.delete_file_by_id(id, db=db)
|
||||
result = await Files.delete_file_by_id(id, db=db)
|
||||
if result:
|
||||
try:
|
||||
Storage.delete_file(file.path)
|
||||
|
||||
@@ -22,8 +22,8 @@ from open_webui.models.knowledge import Knowledges
|
||||
|
||||
from open_webui.config import UPLOAD_DIR
|
||||
from open_webui.constants import ERROR_MESSAGES
|
||||
from open_webui.internal.db import get_session
|
||||
from sqlalchemy.orm import Session
|
||||
from open_webui.internal.db import get_async_session
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
|
||||
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status, Request
|
||||
@@ -48,7 +48,7 @@ router = APIRouter()
|
||||
async def get_folders(
|
||||
request: Request,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if request.app.state.config.ENABLE_FOLDERS is False:
|
||||
raise HTTPException(
|
||||
@@ -56,7 +56,7 @@ async def get_folders(
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
if user.role != 'admin' and not has_permission(
|
||||
if user.role != 'admin' and not await has_permission(
|
||||
user.id,
|
||||
'features.folders',
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
@@ -67,29 +67,29 @@ async def get_folders(
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
folders = Folders.get_folders_by_user_id(user.id, db=db)
|
||||
folders = await Folders.get_folders_by_user_id(user.id, db=db)
|
||||
|
||||
# Verify folder data integrity
|
||||
folder_list = []
|
||||
for folder in folders:
|
||||
if folder.parent_id and not Folders.get_folder_by_id_and_user_id(folder.parent_id, user.id, db=db):
|
||||
folder = Folders.update_folder_parent_id_by_id_and_user_id(folder.id, user.id, None, db=db)
|
||||
if folder.parent_id and not await Folders.get_folder_by_id_and_user_id(folder.parent_id, user.id, db=db):
|
||||
folder = await Folders.update_folder_parent_id_by_id_and_user_id(folder.id, user.id, None, db=db)
|
||||
|
||||
if folder.data:
|
||||
if 'files' in folder.data:
|
||||
valid_files = []
|
||||
for file in folder.data['files']:
|
||||
if file.get('type') == 'file':
|
||||
if Files.check_access_by_user_id(file.get('id'), user.id, 'read', db=db):
|
||||
if await Files.check_access_by_user_id(file.get('id'), user.id, 'read', db=db):
|
||||
valid_files.append(file)
|
||||
elif file.get('type') == 'collection':
|
||||
if Knowledges.check_access_by_user_id(file.get('id'), user.id, 'read', db=db):
|
||||
if await Knowledges.check_access_by_user_id(file.get('id'), user.id, 'read', db=db):
|
||||
valid_files.append(file)
|
||||
else:
|
||||
valid_files.append(file)
|
||||
|
||||
folder.data['files'] = valid_files
|
||||
Folders.update_folder_by_id_and_user_id(folder.id, user.id, FolderUpdateForm(data=folder.data), db=db)
|
||||
await Folders.update_folder_by_id_and_user_id(folder.id, user.id, FolderUpdateForm(data=folder.data), db=db)
|
||||
|
||||
folder_list.append(FolderNameIdResponse(**folder.model_dump()))
|
||||
|
||||
@@ -102,12 +102,12 @@ async def get_folders(
|
||||
|
||||
|
||||
@router.post('/')
|
||||
def create_folder(
|
||||
async def create_folder(
|
||||
form_data: FolderForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
folder = Folders.get_folder_by_parent_id_and_user_id_and_name(form_data.parent_id, user.id, form_data.name, db=db)
|
||||
folder = await Folders.get_folder_by_parent_id_and_user_id_and_name(form_data.parent_id, user.id, form_data.name, db=db)
|
||||
|
||||
if folder:
|
||||
raise HTTPException(
|
||||
@@ -116,7 +116,7 @@ def create_folder(
|
||||
)
|
||||
|
||||
try:
|
||||
folder = Folders.insert_new_folder(user.id, form_data, form_data.parent_id, db=db)
|
||||
folder = await Folders.insert_new_folder(user.id, form_data, form_data.parent_id, db=db)
|
||||
return folder
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
@@ -133,8 +133,8 @@ def create_folder(
|
||||
|
||||
|
||||
@router.get('/{id}', response_model=Optional[FolderModel])
|
||||
async def get_folder_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
folder = Folders.get_folder_by_id_and_user_id(id, user.id, db=db)
|
||||
async def get_folder_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
folder = await Folders.get_folder_by_id_and_user_id(id, user.id, db=db)
|
||||
if folder:
|
||||
return folder
|
||||
else:
|
||||
@@ -154,13 +154,13 @@ async def update_folder_name_by_id(
|
||||
id: str,
|
||||
form_data: FolderUpdateForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
folder = Folders.get_folder_by_id_and_user_id(id, user.id, db=db)
|
||||
folder = await Folders.get_folder_by_id_and_user_id(id, user.id, db=db)
|
||||
if folder:
|
||||
if form_data.name is not None:
|
||||
# Check if folder with same name exists
|
||||
existing_folder = Folders.get_folder_by_parent_id_and_user_id_and_name(
|
||||
existing_folder = await Folders.get_folder_by_parent_id_and_user_id_and_name(
|
||||
folder.parent_id, user.id, form_data.name, db=db
|
||||
)
|
||||
if existing_folder and existing_folder.id != id:
|
||||
@@ -170,7 +170,7 @@ async def update_folder_name_by_id(
|
||||
)
|
||||
|
||||
try:
|
||||
folder = Folders.update_folder_by_id_and_user_id(id, user.id, form_data, db=db)
|
||||
folder = await Folders.update_folder_by_id_and_user_id(id, user.id, form_data, db=db)
|
||||
return folder
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
@@ -200,11 +200,11 @@ async def update_folder_parent_id_by_id(
|
||||
id: str,
|
||||
form_data: FolderParentIdForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
folder = Folders.get_folder_by_id_and_user_id(id, user.id, db=db)
|
||||
folder = await Folders.get_folder_by_id_and_user_id(id, user.id, db=db)
|
||||
if folder:
|
||||
existing_folder = Folders.get_folder_by_parent_id_and_user_id_and_name(
|
||||
existing_folder = await Folders.get_folder_by_parent_id_and_user_id_and_name(
|
||||
form_data.parent_id, user.id, folder.name, db=db
|
||||
)
|
||||
|
||||
@@ -215,7 +215,7 @@ async def update_folder_parent_id_by_id(
|
||||
)
|
||||
|
||||
try:
|
||||
folder = Folders.update_folder_parent_id_by_id_and_user_id(id, user.id, form_data.parent_id, db=db)
|
||||
folder = await Folders.update_folder_parent_id_by_id_and_user_id(id, user.id, form_data.parent_id, db=db)
|
||||
return folder
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
@@ -245,12 +245,12 @@ async def update_folder_is_expanded_by_id(
|
||||
id: str,
|
||||
form_data: FolderIsExpandedForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
folder = Folders.get_folder_by_id_and_user_id(id, user.id, db=db)
|
||||
folder = await Folders.get_folder_by_id_and_user_id(id, user.id, db=db)
|
||||
if folder:
|
||||
try:
|
||||
folder = Folders.update_folder_is_expanded_by_id_and_user_id(id, user.id, form_data.is_expanded, db=db)
|
||||
folder = await Folders.update_folder_is_expanded_by_id_and_user_id(id, user.id, form_data.is_expanded, db=db)
|
||||
return folder
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
@@ -277,10 +277,10 @@ async def delete_folder_by_id(
|
||||
id: str,
|
||||
delete_contents: Optional[bool] = True,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if Chats.count_chats_by_folder_id_and_user_id(id, user.id, db=db):
|
||||
chat_delete_permission = has_permission(
|
||||
if await Chats.count_chats_by_folder_id_and_user_id(id, user.id, db=db):
|
||||
chat_delete_permission = await has_permission(
|
||||
user.id, 'chat.delete', request.app.state.config.USER_PERMISSIONS, db=db
|
||||
)
|
||||
if user.role != 'admin' and not chat_delete_permission:
|
||||
@@ -290,18 +290,18 @@ async def delete_folder_by_id(
|
||||
)
|
||||
|
||||
folders = []
|
||||
folders.append(Folders.get_folder_by_id_and_user_id(id, user.id, db=db))
|
||||
folders.append(await Folders.get_folder_by_id_and_user_id(id, user.id, db=db))
|
||||
while folders:
|
||||
folder = folders.pop()
|
||||
if folder:
|
||||
try:
|
||||
folder_ids = Folders.delete_folder_by_id_and_user_id(folder.id, user.id, db=db)
|
||||
folder_ids = await Folders.delete_folder_by_id_and_user_id(folder.id, user.id, db=db)
|
||||
|
||||
for folder_id in folder_ids:
|
||||
if delete_contents:
|
||||
Chats.delete_chats_by_user_id_and_folder_id(user.id, folder_id, db=db)
|
||||
await Chats.delete_chats_by_user_id_and_folder_id(user.id, folder_id, db=db)
|
||||
else:
|
||||
Chats.move_chats_by_user_id_and_folder_id(user.id, folder_id, None, db=db)
|
||||
await Chats.move_chats_by_user_id_and_folder_id(user.id, folder_id, None, db=db)
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
@@ -313,7 +313,7 @@ async def delete_folder_by_id(
|
||||
)
|
||||
finally:
|
||||
# Get all subfolders
|
||||
subfolders = Folders.get_folders_by_parent_id_and_user_id(folder.id, user.id, db=db)
|
||||
subfolders = await Folders.get_folders_by_parent_id_and_user_id(folder.id, user.id, db=db)
|
||||
folders.extend(subfolders)
|
||||
|
||||
else:
|
||||
|
||||
@@ -26,8 +26,8 @@ from open_webui.constants import ERROR_MESSAGES
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
from open_webui.utils.auth import get_admin_user, get_verified_user
|
||||
from pydantic import BaseModel, HttpUrl
|
||||
from open_webui.internal.db import get_session
|
||||
from sqlalchemy.orm import Session
|
||||
from open_webui.internal.db import get_async_session
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -42,13 +42,13 @@ router = APIRouter()
|
||||
|
||||
|
||||
@router.get('/', response_model=list[FunctionResponse])
|
||||
async def get_functions(user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
return Functions.get_functions(db=db)
|
||||
async def get_functions(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
return await Functions.get_functions(db=db)
|
||||
|
||||
|
||||
@router.get('/list', response_model=list[FunctionUserResponse])
|
||||
async def get_function_list(user=Depends(get_admin_user), db: Session = Depends(get_session)):
|
||||
return Functions.get_function_list(db=db)
|
||||
async def get_function_list(user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
|
||||
return await Functions.get_function_list(db=db)
|
||||
|
||||
|
||||
############################
|
||||
@@ -60,9 +60,9 @@ async def get_function_list(user=Depends(get_admin_user), db: Session = Depends(
|
||||
async def get_functions(
|
||||
include_valves: bool = False,
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
return Functions.get_functions(include_valves=include_valves, db=db)
|
||||
return await Functions.get_functions(include_valves=include_valves, db=db)
|
||||
|
||||
|
||||
############################
|
||||
@@ -145,12 +145,12 @@ async def sync_functions(
|
||||
request: Request,
|
||||
form_data: SyncFunctionsForm,
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
try:
|
||||
for function in form_data.functions:
|
||||
function.content = replace_imports(function.content)
|
||||
function_module, function_type, frontmatter = load_function_module_by_id(
|
||||
function_module, function_type, frontmatter = await load_function_module_by_id(
|
||||
function.id,
|
||||
content=function.content,
|
||||
)
|
||||
@@ -163,7 +163,7 @@ async def sync_functions(
|
||||
log.exception(f'Error validating valves for function {function.id}: {e}')
|
||||
raise e
|
||||
|
||||
return Functions.sync_functions(user.id, form_data.functions, db=db)
|
||||
return await Functions.sync_functions(user.id, form_data.functions, db=db)
|
||||
except Exception as e:
|
||||
log.exception(f'Failed to load a function: {e}')
|
||||
raise HTTPException(
|
||||
@@ -182,7 +182,7 @@ async def create_new_function(
|
||||
request: Request,
|
||||
form_data: FunctionForm,
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if not form_data.id.isidentifier():
|
||||
raise HTTPException(
|
||||
@@ -192,11 +192,11 @@ async def create_new_function(
|
||||
|
||||
form_data.id = form_data.id.lower()
|
||||
|
||||
function = Functions.get_function_by_id(form_data.id, db=db)
|
||||
function = await Functions.get_function_by_id(form_data.id, db=db)
|
||||
if function is None:
|
||||
try:
|
||||
form_data.content = replace_imports(form_data.content)
|
||||
function_module, function_type, frontmatter = load_function_module_by_id(
|
||||
function_module, function_type, frontmatter = await load_function_module_by_id(
|
||||
form_data.id,
|
||||
content=form_data.content,
|
||||
)
|
||||
@@ -205,13 +205,13 @@ async def create_new_function(
|
||||
FUNCTIONS = request.app.state.FUNCTIONS
|
||||
FUNCTIONS[form_data.id] = function_module
|
||||
|
||||
function = Functions.insert_new_function(user.id, function_type, form_data, db=db)
|
||||
function = await Functions.insert_new_function(user.id, function_type, form_data, db=db)
|
||||
|
||||
function_cache_dir = CACHE_DIR / 'functions' / form_data.id
|
||||
function_cache_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if function_type == 'filter' and getattr(function_module, 'toggle', None):
|
||||
Functions.update_function_metadata_by_id(form_data.id, {'toggle': True}, db=db)
|
||||
await Functions.update_function_metadata_by_id(form_data.id, {'toggle': True}, db=db)
|
||||
|
||||
if function:
|
||||
return function
|
||||
@@ -239,8 +239,8 @@ async def create_new_function(
|
||||
|
||||
|
||||
@router.get('/id/{id}', response_model=Optional[FunctionModel])
|
||||
async def get_function_by_id(id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
|
||||
function = Functions.get_function_by_id(id, db=db)
|
||||
async def get_function_by_id(id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
|
||||
function = await Functions.get_function_by_id(id, db=db)
|
||||
|
||||
if function:
|
||||
return function
|
||||
@@ -257,10 +257,10 @@ async def get_function_by_id(id: str, user=Depends(get_admin_user), db: Session
|
||||
|
||||
|
||||
@router.post('/id/{id}/toggle', response_model=Optional[FunctionModel])
|
||||
async def toggle_function_by_id(id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
|
||||
function = Functions.get_function_by_id(id, db=db)
|
||||
async def toggle_function_by_id(id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
|
||||
function = await Functions.get_function_by_id(id, db=db)
|
||||
if function:
|
||||
function = Functions.update_function_by_id(id, {'is_active': not function.is_active}, db=db)
|
||||
function = await Functions.update_function_by_id(id, {'is_active': not function.is_active}, db=db)
|
||||
|
||||
if function:
|
||||
return function
|
||||
@@ -282,10 +282,10 @@ async def toggle_function_by_id(id: str, user=Depends(get_admin_user), db: Sessi
|
||||
|
||||
|
||||
@router.post('/id/{id}/toggle/global', response_model=Optional[FunctionModel])
|
||||
async def toggle_global_by_id(id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
|
||||
function = Functions.get_function_by_id(id, db=db)
|
||||
async def toggle_global_by_id(id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
|
||||
function = await Functions.get_function_by_id(id, db=db)
|
||||
if function:
|
||||
function = Functions.update_function_by_id(id, {'is_global': not function.is_global}, db=db)
|
||||
function = await Functions.update_function_by_id(id, {'is_global': not function.is_global}, db=db)
|
||||
|
||||
if function:
|
||||
return function
|
||||
@@ -312,11 +312,11 @@ async def update_function_by_id(
|
||||
id: str,
|
||||
form_data: FunctionForm,
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
try:
|
||||
form_data.content = replace_imports(form_data.content)
|
||||
function_module, function_type, frontmatter = load_function_module_by_id(id, content=form_data.content)
|
||||
function_module, function_type, frontmatter = await load_function_module_by_id(id, content=form_data.content)
|
||||
form_data.meta.manifest = frontmatter
|
||||
|
||||
FUNCTIONS = request.app.state.FUNCTIONS
|
||||
@@ -325,10 +325,10 @@ async def update_function_by_id(
|
||||
updated = {**form_data.model_dump(exclude={'id'}), 'type': function_type}
|
||||
log.debug(updated)
|
||||
|
||||
function = Functions.update_function_by_id(id, updated, db=db)
|
||||
function = await Functions.update_function_by_id(id, updated, db=db)
|
||||
|
||||
if function_type == 'filter' and getattr(function_module, 'toggle', None):
|
||||
Functions.update_function_metadata_by_id(id, {'toggle': True}, db=db)
|
||||
await Functions.update_function_metadata_by_id(id, {'toggle': True}, db=db)
|
||||
|
||||
if function:
|
||||
return function
|
||||
@@ -355,9 +355,9 @@ async def delete_function_by_id(
|
||||
request: Request,
|
||||
id: str,
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
result = Functions.delete_function_by_id(id, db=db)
|
||||
result = await Functions.delete_function_by_id(id, db=db)
|
||||
|
||||
if result:
|
||||
FUNCTIONS = request.app.state.FUNCTIONS
|
||||
@@ -373,11 +373,11 @@ async def delete_function_by_id(
|
||||
|
||||
|
||||
@router.get('/id/{id}/valves', response_model=Optional[dict])
|
||||
async def get_function_valves_by_id(id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
|
||||
function = Functions.get_function_by_id(id, db=db)
|
||||
async def get_function_valves_by_id(id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
|
||||
function = await Functions.get_function_by_id(id, db=db)
|
||||
if function:
|
||||
try:
|
||||
valves = Functions.get_function_valves_by_id(id, db=db)
|
||||
valves = await Functions.get_function_valves_by_id(id, db=db)
|
||||
return valves
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
@@ -401,11 +401,11 @@ async def get_function_valves_spec_by_id(
|
||||
request: Request,
|
||||
id: str,
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
function = Functions.get_function_by_id(id, db=db)
|
||||
function = await Functions.get_function_by_id(id, db=db)
|
||||
if function:
|
||||
function_module, function_type, frontmatter = get_function_module_from_cache(request, id)
|
||||
function_module, function_type, frontmatter = await get_function_module_from_cache(request, id)
|
||||
|
||||
if hasattr(function_module, 'Valves'):
|
||||
Valves = function_module.Valves
|
||||
@@ -432,11 +432,11 @@ async def update_function_valves_by_id(
|
||||
id: str,
|
||||
form_data: dict,
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
function = Functions.get_function_by_id(id, db=db)
|
||||
function = await Functions.get_function_by_id(id, db=db)
|
||||
if function:
|
||||
function_module, function_type, frontmatter = get_function_module_from_cache(request, id)
|
||||
function_module, function_type, frontmatter = await get_function_module_from_cache(request, id)
|
||||
|
||||
if hasattr(function_module, 'Valves'):
|
||||
Valves = function_module.Valves
|
||||
@@ -446,7 +446,7 @@ async def update_function_valves_by_id(
|
||||
valves = Valves(**form_data)
|
||||
|
||||
valves_dict = valves.model_dump(exclude_unset=True)
|
||||
Functions.update_function_valves_by_id(id, valves_dict, db=db)
|
||||
await Functions.update_function_valves_by_id(id, valves_dict, db=db)
|
||||
return valves_dict
|
||||
except Exception as e:
|
||||
log.exception(f'Error updating function values by id {id}: {e}')
|
||||
@@ -473,11 +473,11 @@ async def update_function_valves_by_id(
|
||||
|
||||
|
||||
@router.get('/id/{id}/valves/user', response_model=Optional[dict])
|
||||
async def get_function_user_valves_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
function = Functions.get_function_by_id(id, db=db)
|
||||
async def get_function_user_valves_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
function = await Functions.get_function_by_id(id, db=db)
|
||||
if function:
|
||||
try:
|
||||
user_valves = Functions.get_user_valves_by_id_and_user_id(id, user.id, db=db)
|
||||
user_valves = await Functions.get_user_valves_by_id_and_user_id(id, user.id, db=db)
|
||||
return user_valves
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
@@ -496,11 +496,11 @@ async def get_function_user_valves_spec_by_id(
|
||||
request: Request,
|
||||
id: str,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
function = Functions.get_function_by_id(id, db=db)
|
||||
function = await Functions.get_function_by_id(id, db=db)
|
||||
if function:
|
||||
function_module, function_type, frontmatter = get_function_module_from_cache(request, id)
|
||||
function_module, function_type, frontmatter = await get_function_module_from_cache(request, id)
|
||||
|
||||
if hasattr(function_module, 'UserValves'):
|
||||
UserValves = function_module.UserValves
|
||||
@@ -522,12 +522,12 @@ async def update_function_user_valves_by_id(
|
||||
id: str,
|
||||
form_data: dict,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
function = Functions.get_function_by_id(id, db=db)
|
||||
function = await Functions.get_function_by_id(id, db=db)
|
||||
|
||||
if function:
|
||||
function_module, function_type, frontmatter = get_function_module_from_cache(request, id)
|
||||
function_module, function_type, frontmatter = await get_function_module_from_cache(request, id)
|
||||
|
||||
if hasattr(function_module, 'UserValves'):
|
||||
UserValves = function_module.UserValves
|
||||
@@ -536,7 +536,7 @@ async def update_function_user_valves_by_id(
|
||||
form_data = {k: v for k, v in form_data.items() if v is not None}
|
||||
user_valves = UserValves(**form_data)
|
||||
user_valves_dict = user_valves.model_dump(exclude_unset=True)
|
||||
Functions.update_user_valves_by_id_and_user_id(id, user.id, user_valves_dict, db=db)
|
||||
await Functions.update_user_valves_by_id_and_user_id(id, user.id, user_valves_dict, db=db)
|
||||
return user_valves_dict
|
||||
except Exception as e:
|
||||
log.exception(f'Error updating function user valves by id {id}: {e}')
|
||||
|
||||
@@ -17,8 +17,8 @@ from open_webui.config import CACHE_DIR
|
||||
from open_webui.constants import ERROR_MESSAGES
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
|
||||
from open_webui.internal.db import get_session
|
||||
from sqlalchemy.orm import Session
|
||||
from open_webui.internal.db import get_async_session
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from open_webui.utils.auth import get_admin_user, get_verified_user
|
||||
|
||||
@@ -35,7 +35,7 @@ router = APIRouter()
|
||||
async def get_groups(
|
||||
share: Optional[bool] = None,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
filter = {}
|
||||
|
||||
@@ -45,7 +45,7 @@ async def get_groups(
|
||||
if share is not None:
|
||||
filter['share'] = share
|
||||
|
||||
groups = Groups.get_groups(filter=filter, db=db)
|
||||
groups = await Groups.get_groups(filter=filter, db=db)
|
||||
|
||||
return groups
|
||||
|
||||
@@ -59,14 +59,14 @@ async def get_groups(
|
||||
async def create_new_group(
|
||||
form_data: GroupForm,
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
try:
|
||||
group = Groups.insert_new_group(user.id, form_data, db=db)
|
||||
group = await Groups.insert_new_group(user.id, form_data, db=db)
|
||||
if group:
|
||||
return GroupResponse(
|
||||
**group.model_dump(),
|
||||
member_count=Groups.get_group_member_count_by_id(group.id, db=db),
|
||||
member_count=await Groups.get_group_member_count_by_id(group.id, db=db),
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
@@ -87,12 +87,12 @@ async def create_new_group(
|
||||
|
||||
|
||||
@router.get('/id/{id}', response_model=Optional[GroupResponse])
|
||||
async def get_group_by_id(id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
|
||||
group = Groups.get_group_by_id(id, db=db)
|
||||
async def get_group_by_id(id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
|
||||
group = await Groups.get_group_by_id(id, db=db)
|
||||
if group:
|
||||
return GroupResponse(
|
||||
**group.model_dump(),
|
||||
member_count=Groups.get_group_member_count_by_id(group.id, db=db),
|
||||
member_count=await Groups.get_group_member_count_by_id(group.id, db=db),
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
@@ -102,12 +102,12 @@ async def get_group_by_id(id: str, user=Depends(get_admin_user), db: Session = D
|
||||
|
||||
|
||||
@router.get('/id/{id}/info', response_model=Optional[GroupInfoResponse])
|
||||
async def get_group_info_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
group = Groups.get_group_by_id(id, db=db)
|
||||
async def get_group_info_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
group = await Groups.get_group_by_id(id, db=db)
|
||||
if group:
|
||||
return GroupInfoResponse(
|
||||
**group.model_dump(),
|
||||
member_count=Groups.get_group_member_count_by_id(group.id, db=db),
|
||||
member_count=await Groups.get_group_member_count_by_id(group.id, db=db),
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
@@ -127,13 +127,13 @@ class GroupExportResponse(GroupResponse):
|
||||
|
||||
|
||||
@router.get('/id/{id}/export', response_model=Optional[GroupExportResponse])
|
||||
async def export_group_by_id(id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
|
||||
group = Groups.get_group_by_id(id, db=db)
|
||||
async def export_group_by_id(id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
|
||||
group = await Groups.get_group_by_id(id, db=db)
|
||||
if group:
|
||||
return GroupExportResponse(
|
||||
**group.model_dump(),
|
||||
member_count=Groups.get_group_member_count_by_id(group.id, db=db),
|
||||
user_ids=Groups.get_group_user_ids_by_id(group.id, db=db),
|
||||
member_count=await Groups.get_group_member_count_by_id(group.id, db=db),
|
||||
user_ids=await Groups.get_group_user_ids_by_id(group.id, db=db),
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
@@ -148,9 +148,9 @@ async def export_group_by_id(id: str, user=Depends(get_admin_user), db: Session
|
||||
|
||||
|
||||
@router.post('/id/{id}/users', response_model=list[UserInfoResponse])
|
||||
async def get_users_in_group(id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
|
||||
async def get_users_in_group(id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
|
||||
try:
|
||||
users = Users.get_users_by_group_id(id, db=db)
|
||||
users = await Users.get_users_by_group_id(id, db=db)
|
||||
return users
|
||||
except Exception as e:
|
||||
log.exception(f'Error adding users to group {id}: {e}')
|
||||
@@ -170,14 +170,14 @@ async def update_group_by_id(
|
||||
id: str,
|
||||
form_data: GroupUpdateForm,
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
try:
|
||||
group = Groups.update_group_by_id(id, form_data, db=db)
|
||||
group = await Groups.update_group_by_id(id, form_data, db=db)
|
||||
if group:
|
||||
return GroupResponse(
|
||||
**group.model_dump(),
|
||||
member_count=Groups.get_group_member_count_by_id(group.id, db=db),
|
||||
member_count=await Groups.get_group_member_count_by_id(group.id, db=db),
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
@@ -202,17 +202,17 @@ async def add_user_to_group(
|
||||
id: str,
|
||||
form_data: UserIdsForm,
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
try:
|
||||
if form_data.user_ids:
|
||||
form_data.user_ids = Users.get_valid_user_ids(form_data.user_ids, db=db)
|
||||
form_data.user_ids = await Users.get_valid_user_ids(form_data.user_ids, db=db)
|
||||
|
||||
group = Groups.add_users_to_group(id, form_data.user_ids, db=db)
|
||||
group = await Groups.add_users_to_group(id, form_data.user_ids, db=db)
|
||||
if group:
|
||||
return GroupResponse(
|
||||
**group.model_dump(),
|
||||
member_count=Groups.get_group_member_count_by_id(group.id, db=db),
|
||||
member_count=await Groups.get_group_member_count_by_id(group.id, db=db),
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
@@ -232,14 +232,14 @@ async def remove_users_from_group(
|
||||
id: str,
|
||||
form_data: UserIdsForm,
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
try:
|
||||
group = Groups.remove_users_from_group(id, form_data.user_ids, db=db)
|
||||
group = await Groups.remove_users_from_group(id, form_data.user_ids, db=db)
|
||||
if group:
|
||||
return GroupResponse(
|
||||
**group.model_dump(),
|
||||
member_count=Groups.get_group_member_count_by_id(group.id, db=db),
|
||||
member_count=await Groups.get_group_member_count_by_id(group.id, db=db),
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
@@ -260,9 +260,9 @@ async def remove_users_from_group(
|
||||
|
||||
|
||||
@router.delete('/id/{id}/delete', response_model=bool)
|
||||
async def delete_group_by_id(id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
|
||||
async def delete_group_by_id(id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
|
||||
try:
|
||||
result = Groups.delete_group_by_id(id, db=db)
|
||||
result = await Groups.delete_group_by_id(id, db=db)
|
||||
if result:
|
||||
return result
|
||||
else:
|
||||
|
||||
@@ -28,8 +28,8 @@ from open_webui.routers.files import upload_file_handler, get_file_content_by_id
|
||||
from open_webui.utils.auth import get_admin_user, get_verified_user
|
||||
from open_webui.utils.access_control import has_permission
|
||||
from open_webui.utils.headers import include_user_info_headers
|
||||
from open_webui.internal.db import get_session
|
||||
from sqlalchemy.orm import Session
|
||||
from open_webui.internal.db import get_async_session
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from open_webui.utils.images.comfyui import (
|
||||
ComfyUICreateImageForm,
|
||||
ComfyUIEditImageForm,
|
||||
@@ -341,7 +341,7 @@ async def verify_url(request: Request, user=Depends(get_admin_user)):
|
||||
|
||||
|
||||
@router.get('/models')
|
||||
def get_models(request: Request, user=Depends(get_verified_user)):
|
||||
async def get_models(request: Request, user=Depends(get_verified_user)):
|
||||
try:
|
||||
if request.app.state.config.IMAGE_GENERATION_ENGINE == 'openai':
|
||||
return [
|
||||
@@ -456,7 +456,7 @@ def get_image_data(data: str, headers=None):
|
||||
return None, None
|
||||
|
||||
|
||||
def upload_image(request, image_data, content_type, metadata, user, db=None):
|
||||
async def upload_image(request, image_data, content_type, metadata, user, db=None):
|
||||
image_format = mimetypes.guess_extension(content_type)
|
||||
file = UploadFile(
|
||||
file=io.BytesIO(image_data),
|
||||
@@ -465,7 +465,7 @@ def upload_image(request, image_data, content_type, metadata, user, db=None):
|
||||
'content-type': content_type,
|
||||
},
|
||||
)
|
||||
file_item = upload_file_handler(
|
||||
file_item = await upload_file_handler(
|
||||
request,
|
||||
file=file,
|
||||
metadata=metadata,
|
||||
@@ -479,7 +479,7 @@ def upload_image(request, image_data, content_type, metadata, user, db=None):
|
||||
message_id = metadata.get('message_id')
|
||||
|
||||
if chat_id and message_id:
|
||||
Chats.insert_chat_files(
|
||||
await Chats.insert_chat_files(
|
||||
chat_id=chat_id,
|
||||
message_id=message_id,
|
||||
file_ids=[file_item.id],
|
||||
@@ -499,7 +499,7 @@ async def generate_images(request: Request, form_data: CreateImageForm, user=Dep
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
if user.role != 'admin' and not has_permission(
|
||||
if user.role != 'admin' and not await has_permission(
|
||||
user.id, 'features.image_generation', request.app.state.config.USER_PERMISSIONS
|
||||
):
|
||||
raise HTTPException(
|
||||
@@ -590,7 +590,7 @@ async def image_generations(
|
||||
else:
|
||||
image_data, content_type = get_image_data(image['b64_json'])
|
||||
|
||||
_, url = upload_image(request, image_data, content_type, {**data, **metadata}, user)
|
||||
_, url = await upload_image(request, image_data, content_type, {**data, **metadata}, user)
|
||||
images.append({'url': url})
|
||||
return images
|
||||
|
||||
@@ -635,14 +635,14 @@ async def image_generations(
|
||||
if model.endswith(':predict'):
|
||||
for image in res['predictions']:
|
||||
image_data, content_type = get_image_data(image['bytesBase64Encoded'])
|
||||
_, url = upload_image(request, image_data, content_type, {**data, **metadata}, user)
|
||||
_, url = await upload_image(request, image_data, content_type, {**data, **metadata}, user)
|
||||
images.append({'url': url})
|
||||
elif model.endswith(':generateContent'):
|
||||
for image in res['candidates']:
|
||||
for part in image['content']['parts']:
|
||||
if part.get('inlineData', {}).get('data'):
|
||||
image_data, content_type = get_image_data(part['inlineData']['data'])
|
||||
_, url = upload_image(
|
||||
_, url = await upload_image(
|
||||
request,
|
||||
image_data,
|
||||
content_type,
|
||||
@@ -695,7 +695,7 @@ async def image_generations(
|
||||
headers = {'Authorization': f'Bearer {request.app.state.config.COMFYUI_API_KEY}'}
|
||||
|
||||
image_data, content_type = get_image_data(image['url'], headers)
|
||||
_, url = upload_image(
|
||||
_, url = await upload_image(
|
||||
request,
|
||||
image_data,
|
||||
content_type,
|
||||
@@ -742,7 +742,7 @@ async def image_generations(
|
||||
|
||||
for image in res['images']:
|
||||
image_data, content_type = get_image_data(image)
|
||||
_, url = upload_image(
|
||||
_, url = await upload_image(
|
||||
request,
|
||||
image_data,
|
||||
content_type,
|
||||
@@ -832,7 +832,7 @@ async def image_edits(
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=ERROR_MESSAGES.DEFAULT(e))
|
||||
|
||||
def get_image_file_item(base64_string, param_name='image'):
|
||||
async def get_image_file_item(base64_string, param_name='image'):
|
||||
data = base64_string
|
||||
header, encoded = data.split(',', 1)
|
||||
mime_type = header.split(';')[0].lstrip('data:')
|
||||
@@ -905,7 +905,7 @@ async def image_edits(
|
||||
else:
|
||||
image_data, content_type = get_image_data(image['b64_json'])
|
||||
|
||||
_, url = upload_image(request, image_data, content_type, {**data, **metadata}, user)
|
||||
_, url = await upload_image(request, image_data, content_type, {**data, **metadata}, user)
|
||||
images.append({'url': url})
|
||||
return images
|
||||
|
||||
@@ -956,7 +956,7 @@ async def image_edits(
|
||||
for part in image['content']['parts']:
|
||||
if part.get('inlineData', {}).get('data'):
|
||||
image_data, content_type = get_image_data(part['inlineData']['data'])
|
||||
_, url = upload_image(
|
||||
_, url = await upload_image(
|
||||
request,
|
||||
image_data,
|
||||
content_type,
|
||||
@@ -1036,7 +1036,7 @@ async def image_edits(
|
||||
headers = {'Authorization': f'Bearer {request.app.state.config.IMAGES_EDIT_COMFYUI_API_KEY}'}
|
||||
|
||||
image_data, content_type = get_image_data(image_url, headers)
|
||||
_, url = upload_image(
|
||||
_, url = await upload_image(
|
||||
request,
|
||||
image_data,
|
||||
content_type,
|
||||
|
||||
@@ -8,8 +8,8 @@ import io
|
||||
import zipfile
|
||||
from urllib.parse import quote
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from open_webui.internal.db import get_session
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from open_webui.internal.db import get_async_session
|
||||
from open_webui.models.groups import Groups
|
||||
from open_webui.models.knowledge import (
|
||||
KnowledgeFileListResponse,
|
||||
@@ -111,14 +111,14 @@ class KnowledgeAccessListResponse(BaseModel):
|
||||
async def get_knowledge_bases(
|
||||
page: Optional[int] = 1,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
page = max(page, 1)
|
||||
limit = PAGE_ITEM_COUNT
|
||||
skip = (page - 1) * limit
|
||||
|
||||
filter = {}
|
||||
groups = Groups.get_groups_by_member_id(user.id, db=db)
|
||||
groups = await Groups.get_groups_by_member_id(user.id, db=db)
|
||||
user_group_ids = {group.id for group in groups}
|
||||
|
||||
if not user.role == 'admin' or not BYPASS_ADMIN_ACCESS_CONTROL:
|
||||
@@ -127,11 +127,11 @@ async def get_knowledge_bases(
|
||||
|
||||
filter['user_id'] = user.id
|
||||
|
||||
result = Knowledges.search_knowledge_bases(user.id, filter=filter, skip=skip, limit=limit, db=db)
|
||||
result = await Knowledges.search_knowledge_bases(user.id, filter=filter, skip=skip, limit=limit, db=db)
|
||||
|
||||
# Batch-fetch writable knowledge IDs in a single query instead of N has_access calls
|
||||
knowledge_base_ids = [knowledge_base.id for knowledge_base in result.items]
|
||||
writable_knowledge_base_ids = AccessGrants.get_accessible_resource_ids(
|
||||
writable_knowledge_base_ids = await AccessGrants.get_accessible_resource_ids(
|
||||
user_id=user.id,
|
||||
resource_type='knowledge',
|
||||
resource_ids=knowledge_base_ids,
|
||||
@@ -162,7 +162,7 @@ async def search_knowledge_bases(
|
||||
view_option: Optional[str] = None,
|
||||
page: Optional[int] = 1,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
page = max(page, 1)
|
||||
limit = PAGE_ITEM_COUNT
|
||||
@@ -174,7 +174,7 @@ async def search_knowledge_bases(
|
||||
if view_option:
|
||||
filter['view_option'] = view_option
|
||||
|
||||
groups = Groups.get_groups_by_member_id(user.id, db=db)
|
||||
groups = await Groups.get_groups_by_member_id(user.id, db=db)
|
||||
user_group_ids = {group.id for group in groups}
|
||||
|
||||
if not user.role == 'admin' or not BYPASS_ADMIN_ACCESS_CONTROL:
|
||||
@@ -183,11 +183,11 @@ async def search_knowledge_bases(
|
||||
|
||||
filter['user_id'] = user.id
|
||||
|
||||
result = Knowledges.search_knowledge_bases(user.id, filter=filter, skip=skip, limit=limit, db=db)
|
||||
result = await Knowledges.search_knowledge_bases(user.id, filter=filter, skip=skip, limit=limit, db=db)
|
||||
|
||||
# Batch-fetch writable knowledge IDs in a single query instead of N has_access calls
|
||||
knowledge_base_ids = [knowledge_base.id for knowledge_base in result.items]
|
||||
writable_knowledge_base_ids = AccessGrants.get_accessible_resource_ids(
|
||||
writable_knowledge_base_ids = await AccessGrants.get_accessible_resource_ids(
|
||||
user_id=user.id,
|
||||
resource_type='knowledge',
|
||||
resource_ids=knowledge_base_ids,
|
||||
@@ -217,7 +217,7 @@ async def search_knowledge_files(
|
||||
query: Optional[str] = None,
|
||||
page: Optional[int] = 1,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
page = max(page, 1)
|
||||
limit = PAGE_ITEM_COUNT
|
||||
@@ -227,13 +227,13 @@ async def search_knowledge_files(
|
||||
if query:
|
||||
filter['query'] = query
|
||||
|
||||
groups = Groups.get_groups_by_member_id(user.id, db=db)
|
||||
groups = await Groups.get_groups_by_member_id(user.id, db=db)
|
||||
if groups:
|
||||
filter['group_ids'] = [group.id for group in groups]
|
||||
|
||||
filter['user_id'] = user.id
|
||||
|
||||
return Knowledges.search_knowledge_files(filter=filter, skip=skip, limit=limit, db=db)
|
||||
return await Knowledges.search_knowledge_files(filter=filter, skip=skip, limit=limit, db=db)
|
||||
|
||||
|
||||
############################
|
||||
@@ -247,11 +247,11 @@ async def create_new_knowledge(
|
||||
form_data: KnowledgeForm,
|
||||
user=Depends(get_verified_user),
|
||||
):
|
||||
# NOTE: We intentionally do NOT use Depends(get_session) here.
|
||||
# NOTE: We intentionally do NOT use Depends(get_async_session) here.
|
||||
# Database operations (has_permission, filter_allowed_access_grants, insert_new_knowledge) manage their own sessions.
|
||||
# This prevents holding a connection during embed_knowledge_base_metadata()
|
||||
# which makes external embedding API calls (1-5+ seconds).
|
||||
if user.role != 'admin' and not has_permission(
|
||||
if user.role != 'admin' and not await has_permission(
|
||||
user.id, 'workspace.knowledge', request.app.state.config.USER_PERMISSIONS
|
||||
):
|
||||
raise HTTPException(
|
||||
@@ -259,7 +259,7 @@ async def create_new_knowledge(
|
||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||
)
|
||||
|
||||
form_data.access_grants = filter_allowed_access_grants(
|
||||
form_data.access_grants = await filter_allowed_access_grants(
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
user.id,
|
||||
user.role,
|
||||
@@ -267,7 +267,7 @@ async def create_new_knowledge(
|
||||
'sharing.public_knowledge',
|
||||
)
|
||||
|
||||
knowledge = Knowledges.insert_new_knowledge(user.id, form_data)
|
||||
knowledge = await Knowledges.insert_new_knowledge(user.id, form_data)
|
||||
|
||||
if knowledge:
|
||||
# Embed knowledge base for semantic search
|
||||
@@ -294,7 +294,7 @@ async def create_new_knowledge(
|
||||
async def reindex_knowledge_files(
|
||||
request: Request,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if user.role != 'admin':
|
||||
raise HTTPException(
|
||||
@@ -302,13 +302,13 @@ async def reindex_knowledge_files(
|
||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||
)
|
||||
|
||||
knowledge_bases = Knowledges.get_knowledge_bases(db=db)
|
||||
knowledge_bases = await Knowledges.get_knowledge_bases(db=db)
|
||||
|
||||
log.info(f'Starting reindexing for {len(knowledge_bases)} knowledge bases')
|
||||
|
||||
for knowledge_base in knowledge_bases:
|
||||
try:
|
||||
files = Knowledges.get_files_by_id(knowledge_base.id, db=db)
|
||||
files = await Knowledges.get_files_by_id(knowledge_base.id, db=db)
|
||||
try:
|
||||
if VECTOR_DB_CLIENT.has_collection(collection_name=knowledge_base.id):
|
||||
VECTOR_DB_CLIENT.delete_collection(collection_name=knowledge_base.id)
|
||||
@@ -357,12 +357,12 @@ async def reindex_knowledge_base_metadata_embeddings(
|
||||
):
|
||||
"""Batch embed all existing knowledge bases. Admin only.
|
||||
|
||||
NOTE: We intentionally do NOT use Depends(get_session) here.
|
||||
NOTE: We intentionally do NOT use Depends(get_async_session) here.
|
||||
This endpoint loops through ALL knowledge bases and calls embed_knowledge_base_metadata()
|
||||
for each one, making N external embedding API calls. Holding a session during
|
||||
this entire operation would exhaust the connection pool.
|
||||
"""
|
||||
knowledge_bases = Knowledges.get_knowledge_bases()
|
||||
knowledge_bases = await Knowledges.get_knowledge_bases()
|
||||
log.info(f'Reindexing embeddings for {len(knowledge_bases)} knowledge bases')
|
||||
|
||||
success_count = 0
|
||||
@@ -385,14 +385,14 @@ class KnowledgeFilesResponse(KnowledgeResponse):
|
||||
|
||||
|
||||
@router.get('/{id}', response_model=Optional[KnowledgeFilesResponse])
|
||||
async def get_knowledge_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
knowledge = Knowledges.get_knowledge_by_id(id=id, db=db)
|
||||
async def get_knowledge_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
knowledge = await Knowledges.get_knowledge_by_id(id=id, db=db)
|
||||
|
||||
if knowledge:
|
||||
if (
|
||||
user.role == 'admin'
|
||||
or knowledge.user_id == user.id
|
||||
or AccessGrants.has_access(
|
||||
or await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='knowledge',
|
||||
resource_id=knowledge.id,
|
||||
@@ -405,7 +405,7 @@ async def get_knowledge_by_id(id: str, user=Depends(get_verified_user), db: Sess
|
||||
write_access=(
|
||||
user.id == knowledge.user_id
|
||||
or (user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL)
|
||||
or AccessGrants.has_access(
|
||||
or await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='knowledge',
|
||||
resource_id=knowledge.id,
|
||||
@@ -438,11 +438,11 @@ async def update_knowledge_by_id(
|
||||
form_data: KnowledgeForm,
|
||||
user=Depends(get_verified_user),
|
||||
):
|
||||
# NOTE: We intentionally do NOT use Depends(get_session) here.
|
||||
# NOTE: We intentionally do NOT use Depends(get_async_session) here.
|
||||
# Database operations manage their own short-lived sessions internally.
|
||||
# This prevents holding a connection during embed_knowledge_base_metadata()
|
||||
# which makes external embedding API calls (1-5+ seconds).
|
||||
knowledge = Knowledges.get_knowledge_by_id(id=id)
|
||||
knowledge = await Knowledges.get_knowledge_by_id(id=id)
|
||||
if not knowledge:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
@@ -451,7 +451,7 @@ async def update_knowledge_by_id(
|
||||
# Is the user the original creator, in a group with write access, or an admin
|
||||
if (
|
||||
knowledge.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='knowledge',
|
||||
resource_id=knowledge.id,
|
||||
@@ -464,7 +464,7 @@ async def update_knowledge_by_id(
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
form_data.access_grants = filter_allowed_access_grants(
|
||||
form_data.access_grants = await filter_allowed_access_grants(
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
user.id,
|
||||
user.role,
|
||||
@@ -472,7 +472,7 @@ async def update_knowledge_by_id(
|
||||
'sharing.public_knowledge',
|
||||
)
|
||||
|
||||
knowledge = Knowledges.update_knowledge_by_id(id=id, form_data=form_data)
|
||||
knowledge = await Knowledges.update_knowledge_by_id(id=id, form_data=form_data)
|
||||
if knowledge:
|
||||
# Re-embed knowledge base for semantic search
|
||||
await embed_knowledge_base_metadata(
|
||||
@@ -483,7 +483,7 @@ async def update_knowledge_by_id(
|
||||
)
|
||||
return KnowledgeFilesResponse(
|
||||
**knowledge.model_dump(),
|
||||
files=Knowledges.get_file_metadatas_by_id(knowledge.id),
|
||||
files=await Knowledges.get_file_metadatas_by_id(knowledge.id),
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
@@ -507,9 +507,9 @@ async def update_knowledge_access_by_id(
|
||||
id: str,
|
||||
form_data: KnowledgeAccessGrantsForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
knowledge = Knowledges.get_knowledge_by_id(id=id, db=db)
|
||||
knowledge = await Knowledges.get_knowledge_by_id(id=id, db=db)
|
||||
if not knowledge:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -518,7 +518,7 @@ async def update_knowledge_access_by_id(
|
||||
|
||||
if (
|
||||
knowledge.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='knowledge',
|
||||
resource_id=knowledge.id,
|
||||
@@ -532,7 +532,7 @@ async def update_knowledge_access_by_id(
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
form_data.access_grants = filter_allowed_access_grants(
|
||||
form_data.access_grants = await filter_allowed_access_grants(
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
user.id,
|
||||
user.role,
|
||||
@@ -540,11 +540,11 @@ async def update_knowledge_access_by_id(
|
||||
'sharing.public_knowledge',
|
||||
)
|
||||
|
||||
AccessGrants.set_access_grants('knowledge', id, form_data.access_grants, db=db)
|
||||
await AccessGrants.set_access_grants('knowledge', id, form_data.access_grants, db=db)
|
||||
|
||||
return KnowledgeFilesResponse(
|
||||
**Knowledges.get_knowledge_by_id(id=id, db=db).model_dump(),
|
||||
files=Knowledges.get_file_metadatas_by_id(id, db=db),
|
||||
**await Knowledges.get_knowledge_by_id(id=id, db=db).model_dump(),
|
||||
files=await Knowledges.get_file_metadatas_by_id(id, db=db),
|
||||
)
|
||||
|
||||
|
||||
@@ -562,9 +562,9 @@ async def get_knowledge_files_by_id(
|
||||
direction: Optional[str] = None,
|
||||
page: Optional[int] = 1,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
knowledge = Knowledges.get_knowledge_by_id(id=id, db=db)
|
||||
knowledge = await Knowledges.get_knowledge_by_id(id=id, db=db)
|
||||
if not knowledge:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
@@ -574,7 +574,7 @@ async def get_knowledge_files_by_id(
|
||||
if not (
|
||||
user.role == 'admin'
|
||||
or knowledge.user_id == user.id
|
||||
or AccessGrants.has_access(
|
||||
or await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='knowledge',
|
||||
resource_id=knowledge.id,
|
||||
@@ -602,7 +602,7 @@ async def get_knowledge_files_by_id(
|
||||
if direction:
|
||||
filter['direction'] = direction
|
||||
|
||||
return Knowledges.search_files_by_id(id, user.id, filter=filter, skip=skip, limit=limit, db=db)
|
||||
return await Knowledges.search_files_by_id(id, user.id, filter=filter, skip=skip, limit=limit, db=db)
|
||||
|
||||
|
||||
############################
|
||||
@@ -615,14 +615,14 @@ class KnowledgeFileIdForm(BaseModel):
|
||||
|
||||
|
||||
@router.post('/{id}/file/add', response_model=Optional[KnowledgeFilesResponse])
|
||||
def add_file_to_knowledge_by_id(
|
||||
async def add_file_to_knowledge_by_id(
|
||||
request: Request,
|
||||
id: str,
|
||||
form_data: KnowledgeFileIdForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
knowledge = Knowledges.get_knowledge_by_id(id=id, db=db)
|
||||
knowledge = await Knowledges.get_knowledge_by_id(id=id, db=db)
|
||||
if not knowledge:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
@@ -631,7 +631,7 @@ def add_file_to_knowledge_by_id(
|
||||
|
||||
if (
|
||||
knowledge.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='knowledge',
|
||||
resource_id=knowledge.id,
|
||||
@@ -645,7 +645,7 @@ def add_file_to_knowledge_by_id(
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
file = Files.get_file_by_id(form_data.file_id, db=db)
|
||||
file = await Files.get_file_by_id(form_data.file_id, db=db)
|
||||
if not file:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
@@ -667,7 +667,7 @@ def add_file_to_knowledge_by_id(
|
||||
)
|
||||
|
||||
# Add file to knowledge base
|
||||
Knowledges.add_file_to_knowledge_by_id(knowledge_id=id, file_id=form_data.file_id, user_id=user.id, db=db)
|
||||
await Knowledges.add_file_to_knowledge_by_id(knowledge_id=id, file_id=form_data.file_id, user_id=user.id, db=db)
|
||||
except Exception as e:
|
||||
log.debug(e)
|
||||
raise HTTPException(
|
||||
@@ -678,7 +678,7 @@ def add_file_to_knowledge_by_id(
|
||||
if knowledge:
|
||||
return KnowledgeFilesResponse(
|
||||
**knowledge.model_dump(),
|
||||
files=Knowledges.get_file_metadatas_by_id(knowledge.id, db=db),
|
||||
files=await Knowledges.get_file_metadatas_by_id(knowledge.id, db=db),
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
@@ -688,14 +688,14 @@ def add_file_to_knowledge_by_id(
|
||||
|
||||
|
||||
@router.post('/{id}/file/update', response_model=Optional[KnowledgeFilesResponse])
|
||||
def update_file_from_knowledge_by_id(
|
||||
async def update_file_from_knowledge_by_id(
|
||||
request: Request,
|
||||
id: str,
|
||||
form_data: KnowledgeFileIdForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
knowledge = Knowledges.get_knowledge_by_id(id=id, db=db)
|
||||
knowledge = await Knowledges.get_knowledge_by_id(id=id, db=db)
|
||||
if not knowledge:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
@@ -704,7 +704,7 @@ def update_file_from_knowledge_by_id(
|
||||
|
||||
if (
|
||||
knowledge.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='knowledge',
|
||||
resource_id=knowledge.id,
|
||||
@@ -718,7 +718,7 @@ def update_file_from_knowledge_by_id(
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
file = Files.get_file_by_id(form_data.file_id, db=db)
|
||||
file = await Files.get_file_by_id(form_data.file_id, db=db)
|
||||
if not file:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
@@ -726,7 +726,7 @@ def update_file_from_knowledge_by_id(
|
||||
)
|
||||
|
||||
# Validate the file actually belongs to this knowledge base
|
||||
if not Knowledges.has_file(knowledge_id=id, file_id=form_data.file_id, db=db):
|
||||
if not await Knowledges.has_file(knowledge_id=id, file_id=form_data.file_id, db=db):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
@@ -752,7 +752,7 @@ def update_file_from_knowledge_by_id(
|
||||
if knowledge:
|
||||
return KnowledgeFilesResponse(
|
||||
**knowledge.model_dump(),
|
||||
files=Knowledges.get_file_metadatas_by_id(knowledge.id, db=db),
|
||||
files=await Knowledges.get_file_metadatas_by_id(knowledge.id, db=db),
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
@@ -767,14 +767,14 @@ def update_file_from_knowledge_by_id(
|
||||
|
||||
|
||||
@router.post('/{id}/file/remove', response_model=Optional[KnowledgeFilesResponse])
|
||||
def remove_file_from_knowledge_by_id(
|
||||
async def remove_file_from_knowledge_by_id(
|
||||
id: str,
|
||||
form_data: KnowledgeFileIdForm,
|
||||
delete_file: bool = Query(True),
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
knowledge = Knowledges.get_knowledge_by_id(id=id, db=db)
|
||||
knowledge = await Knowledges.get_knowledge_by_id(id=id, db=db)
|
||||
if not knowledge:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
@@ -783,7 +783,7 @@ def remove_file_from_knowledge_by_id(
|
||||
|
||||
if (
|
||||
knowledge.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='knowledge',
|
||||
resource_id=knowledge.id,
|
||||
@@ -797,7 +797,7 @@ def remove_file_from_knowledge_by_id(
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
file = Files.get_file_by_id(form_data.file_id, db=db)
|
||||
file = await Files.get_file_by_id(form_data.file_id, db=db)
|
||||
if not file:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
@@ -805,13 +805,13 @@ def remove_file_from_knowledge_by_id(
|
||||
)
|
||||
|
||||
# Validate the file actually belongs to this knowledge base
|
||||
if not Knowledges.has_file(knowledge_id=id, file_id=form_data.file_id, db=db):
|
||||
if not await Knowledges.has_file(knowledge_id=id, file_id=form_data.file_id, db=db):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
Knowledges.remove_file_from_knowledge_by_id(knowledge_id=id, file_id=form_data.file_id, db=db)
|
||||
await Knowledges.remove_file_from_knowledge_by_id(knowledge_id=id, file_id=form_data.file_id, db=db)
|
||||
|
||||
# Remove content from the vector database
|
||||
try:
|
||||
@@ -839,12 +839,12 @@ def remove_file_from_knowledge_by_id(
|
||||
pass
|
||||
|
||||
# Delete file from database
|
||||
Files.delete_file_by_id(form_data.file_id, db=db)
|
||||
await Files.delete_file_by_id(form_data.file_id, db=db)
|
||||
|
||||
if knowledge:
|
||||
return KnowledgeFilesResponse(
|
||||
**knowledge.model_dump(),
|
||||
files=Knowledges.get_file_metadatas_by_id(knowledge.id, db=db),
|
||||
files=await Knowledges.get_file_metadatas_by_id(knowledge.id, db=db),
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
@@ -859,8 +859,8 @@ def remove_file_from_knowledge_by_id(
|
||||
|
||||
|
||||
@router.delete('/{id}/delete', response_model=bool)
|
||||
async def delete_knowledge_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
knowledge = Knowledges.get_knowledge_by_id(id=id, db=db)
|
||||
async def delete_knowledge_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
knowledge = await Knowledges.get_knowledge_by_id(id=id, db=db)
|
||||
if not knowledge:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
@@ -869,7 +869,7 @@ async def delete_knowledge_by_id(id: str, user=Depends(get_verified_user), db: S
|
||||
|
||||
if (
|
||||
knowledge.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='knowledge',
|
||||
resource_id=knowledge.id,
|
||||
@@ -886,7 +886,7 @@ async def delete_knowledge_by_id(id: str, user=Depends(get_verified_user), db: S
|
||||
log.info(f'Deleting knowledge base: {id} (name: {knowledge.name})')
|
||||
|
||||
# Get all models
|
||||
models = Models.get_all_models(db=db)
|
||||
models = await Models.get_all_models(db=db)
|
||||
log.info(f'Found {len(models)} models to check for knowledge base {id}')
|
||||
|
||||
# Update models that reference this knowledge base
|
||||
@@ -910,7 +910,7 @@ async def delete_knowledge_by_id(id: str, user=Depends(get_verified_user), db: S
|
||||
access_grants=model.access_grants,
|
||||
is_active=model.is_active,
|
||||
)
|
||||
Models.update_model_by_id(model.id, model_form, db=db)
|
||||
await Models.update_model_by_id(model.id, model_form, db=db)
|
||||
|
||||
# Clean up vector DB
|
||||
try:
|
||||
@@ -922,7 +922,7 @@ async def delete_knowledge_by_id(id: str, user=Depends(get_verified_user), db: S
|
||||
# Remove knowledge base embedding
|
||||
remove_knowledge_base_metadata_embedding(id)
|
||||
|
||||
result = Knowledges.delete_knowledge_by_id(id=id, db=db)
|
||||
result = await Knowledges.delete_knowledge_by_id(id=id, db=db)
|
||||
return result
|
||||
|
||||
|
||||
@@ -932,8 +932,8 @@ async def delete_knowledge_by_id(id: str, user=Depends(get_verified_user), db: S
|
||||
|
||||
|
||||
@router.post('/{id}/reset', response_model=Optional[KnowledgeResponse])
|
||||
async def reset_knowledge_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
knowledge = Knowledges.get_knowledge_by_id(id=id, db=db)
|
||||
async def reset_knowledge_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
knowledge = await Knowledges.get_knowledge_by_id(id=id, db=db)
|
||||
if not knowledge:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
@@ -942,7 +942,7 @@ async def reset_knowledge_by_id(id: str, user=Depends(get_verified_user), db: Se
|
||||
|
||||
if (
|
||||
knowledge.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='knowledge',
|
||||
resource_id=knowledge.id,
|
||||
@@ -977,12 +977,12 @@ async def add_files_to_knowledge_batch(
|
||||
id: str,
|
||||
form_data: list[KnowledgeFileIdForm],
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""
|
||||
Add multiple files to a knowledge base
|
||||
"""
|
||||
knowledge = Knowledges.get_knowledge_by_id(id=id, db=db)
|
||||
knowledge = await Knowledges.get_knowledge_by_id(id=id, db=db)
|
||||
if not knowledge:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
@@ -991,7 +991,7 @@ async def add_files_to_knowledge_batch(
|
||||
|
||||
if (
|
||||
knowledge.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='knowledge',
|
||||
resource_id=knowledge.id,
|
||||
@@ -1008,7 +1008,7 @@ async def add_files_to_knowledge_batch(
|
||||
# Batch-fetch all files to avoid N+1 queries
|
||||
log.info(f'files/batch/add - {len(form_data)} files')
|
||||
file_ids = [form.file_id for form in form_data]
|
||||
files = Files.get_files_by_ids(file_ids, db=db)
|
||||
files = await Files.get_files_by_ids(file_ids, db=db)
|
||||
|
||||
# Verify all requested files were found
|
||||
found_ids = {file.id for file in files}
|
||||
@@ -1034,14 +1034,14 @@ async def add_files_to_knowledge_batch(
|
||||
# Only add files that were successfully processed
|
||||
successful_file_ids = [r.file_id for r in result.results if r.status == 'completed']
|
||||
for file_id in successful_file_ids:
|
||||
Knowledges.add_file_to_knowledge_by_id(knowledge_id=id, file_id=file_id, user_id=user.id, db=db)
|
||||
await Knowledges.add_file_to_knowledge_by_id(knowledge_id=id, file_id=file_id, user_id=user.id, db=db)
|
||||
|
||||
# If there were any errors, include them in the response
|
||||
if result.errors:
|
||||
error_details = [f'{err.file_id}: {err.error}' for err in result.errors]
|
||||
return KnowledgeFilesResponse(
|
||||
**knowledge.model_dump(),
|
||||
files=Knowledges.get_file_metadatas_by_id(knowledge.id, db=db),
|
||||
files=await Knowledges.get_file_metadatas_by_id(knowledge.id, db=db),
|
||||
warnings={
|
||||
'message': 'Some files failed to process',
|
||||
'errors': error_details,
|
||||
@@ -1050,7 +1050,7 @@ async def add_files_to_knowledge_batch(
|
||||
|
||||
return KnowledgeFilesResponse(
|
||||
**knowledge.model_dump(),
|
||||
files=Knowledges.get_file_metadatas_by_id(knowledge.id, db=db),
|
||||
files=await Knowledges.get_file_metadatas_by_id(knowledge.id, db=db),
|
||||
)
|
||||
|
||||
|
||||
@@ -1060,20 +1060,20 @@ async def add_files_to_knowledge_batch(
|
||||
|
||||
|
||||
@router.get('/{id}/export')
|
||||
async def export_knowledge_by_id(id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
|
||||
async def export_knowledge_by_id(id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
|
||||
"""
|
||||
Export a knowledge base as a zip file containing .txt files.
|
||||
Admin only.
|
||||
"""
|
||||
|
||||
knowledge = Knowledges.get_knowledge_by_id(id=id, db=db)
|
||||
knowledge = await Knowledges.get_knowledge_by_id(id=id, db=db)
|
||||
if not knowledge:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
files = Knowledges.get_files_by_id(id, db=db)
|
||||
files = await Knowledges.get_files_by_id(id, db=db)
|
||||
|
||||
# Create zip file in memory
|
||||
zip_buffer = io.BytesIO()
|
||||
|
||||
@@ -7,8 +7,8 @@ from typing import Optional
|
||||
from open_webui.models.memories import Memories, MemoryModel
|
||||
from open_webui.retrieval.vector.factory import VECTOR_DB_CLIENT
|
||||
from open_webui.utils.auth import get_verified_user
|
||||
from open_webui.internal.db import get_session
|
||||
from sqlalchemy.orm import Session
|
||||
from open_webui.internal.db import get_async_session
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from open_webui.utils.access_control import has_permission
|
||||
from open_webui.constants import ERROR_MESSAGES
|
||||
@@ -29,7 +29,7 @@ router = APIRouter()
|
||||
async def get_memories(
|
||||
request: Request,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if not request.app.state.config.ENABLE_MEMORIES:
|
||||
raise HTTPException(
|
||||
@@ -37,13 +37,13 @@ async def get_memories(
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if not has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
|
||||
if not await has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
return Memories.get_memories_by_user_id(user.id, db=db)
|
||||
return await Memories.get_memories_by_user_id(user.id, db=db)
|
||||
|
||||
|
||||
############################
|
||||
@@ -65,7 +65,7 @@ async def add_memory(
|
||||
form_data: AddMemoryForm,
|
||||
user=Depends(get_verified_user),
|
||||
):
|
||||
# NOTE: We intentionally do NOT use Depends(get_session) here.
|
||||
# NOTE: We intentionally do NOT use Depends(get_async_session) here.
|
||||
# Database operations (insert_new_memory) manage their own short-lived sessions.
|
||||
# This prevents holding a connection during EMBEDDING_FUNCTION()
|
||||
# which makes external embedding API calls (1-5+ seconds).
|
||||
@@ -75,13 +75,13 @@ async def add_memory(
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if not has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
|
||||
if not await has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
memory = Memories.insert_new_memory(user.id, form_data.content)
|
||||
memory = await Memories.insert_new_memory(user.id, form_data.content)
|
||||
|
||||
vector = await request.app.state.EMBEDDING_FUNCTION(memory.content, user=user)
|
||||
|
||||
@@ -116,7 +116,7 @@ async def query_memory(
|
||||
form_data: QueryMemoryForm,
|
||||
user=Depends(get_verified_user),
|
||||
):
|
||||
# NOTE: We intentionally do NOT use Depends(get_session) here.
|
||||
# NOTE: We intentionally do NOT use Depends(get_async_session) here.
|
||||
# Database operations (get_memories_by_user_id) manage their own short-lived sessions.
|
||||
# This prevents holding a connection during EMBEDDING_FUNCTION()
|
||||
# which makes external embedding API calls (1-5+ seconds).
|
||||
@@ -126,13 +126,13 @@ async def query_memory(
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if not has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
|
||||
if not await has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
memories = Memories.get_memories_by_user_id(user.id)
|
||||
memories = await Memories.get_memories_by_user_id(user.id)
|
||||
if not memories:
|
||||
raise HTTPException(status_code=404, detail='No memories found for user')
|
||||
|
||||
@@ -157,7 +157,7 @@ async def reset_memory_from_vector_db(
|
||||
):
|
||||
"""Reset user's memory vector embeddings.
|
||||
|
||||
CRITICAL: We intentionally do NOT use Depends(get_session) here.
|
||||
CRITICAL: We intentionally do NOT use Depends(get_async_session) here.
|
||||
This endpoint generates embeddings for ALL user memories in parallel using
|
||||
asyncio.gather(). A user with 100 memories would trigger 100 embedding API
|
||||
calls simultaneously. With a session held, this could block a connection
|
||||
@@ -169,7 +169,7 @@ async def reset_memory_from_vector_db(
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if not has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
|
||||
if not await has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
@@ -177,7 +177,7 @@ async def reset_memory_from_vector_db(
|
||||
|
||||
VECTOR_DB_CLIENT.delete_collection(f'user-memory-{user.id}')
|
||||
|
||||
memories = Memories.get_memories_by_user_id(user.id)
|
||||
memories = await Memories.get_memories_by_user_id(user.id)
|
||||
|
||||
# Generate vectors in parallel
|
||||
vectors = await asyncio.gather(
|
||||
@@ -212,7 +212,7 @@ async def reset_memory_from_vector_db(
|
||||
async def delete_memory_by_user_id(
|
||||
request: Request,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if not request.app.state.config.ENABLE_MEMORIES:
|
||||
raise HTTPException(
|
||||
@@ -220,13 +220,13 @@ async def delete_memory_by_user_id(
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if not has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
|
||||
if not await has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
result = Memories.delete_memories_by_user_id(user.id, db=db)
|
||||
result = await Memories.delete_memories_by_user_id(user.id, db=db)
|
||||
|
||||
if result:
|
||||
try:
|
||||
@@ -250,7 +250,7 @@ async def update_memory_by_id(
|
||||
form_data: MemoryUpdateModel,
|
||||
user=Depends(get_verified_user),
|
||||
):
|
||||
# NOTE: We intentionally do NOT use Depends(get_session) here.
|
||||
# NOTE: We intentionally do NOT use Depends(get_async_session) here.
|
||||
# Database operations (update_memory_by_id_and_user_id) manage their own
|
||||
# short-lived sessions. This prevents holding a connection during
|
||||
# EMBEDDING_FUNCTION() which makes external API calls (1-5+ seconds).
|
||||
@@ -260,13 +260,13 @@ async def update_memory_by_id(
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if not has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
|
||||
if not await has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
memory = Memories.update_memory_by_id_and_user_id(memory_id, user.id, form_data.content)
|
||||
memory = await Memories.update_memory_by_id_and_user_id(memory_id, user.id, form_data.content)
|
||||
if memory is None:
|
||||
raise HTTPException(status_code=404, detail='Memory not found')
|
||||
|
||||
@@ -301,7 +301,7 @@ async def delete_memory_by_id(
|
||||
memory_id: str,
|
||||
request: Request,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if not request.app.state.config.ENABLE_MEMORIES:
|
||||
raise HTTPException(
|
||||
@@ -309,13 +309,13 @@ async def delete_memory_by_id(
|
||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||
)
|
||||
|
||||
if not has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
|
||||
if not await has_permission(user.id, 'features.memories', request.app.state.config.USER_PERMISSIONS):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
result = Memories.delete_memory_by_id_and_user_id(memory_id, user.id, db=db)
|
||||
result = await Memories.delete_memory_by_id_and_user_id(memory_id, user.id, db=db)
|
||||
|
||||
if result:
|
||||
VECTOR_DB_CLIENT.delete(collection_name=f'user-memory-{user.id}', ids=[memory_id])
|
||||
|
||||
@@ -35,8 +35,8 @@ from fastapi.responses import FileResponse, StreamingResponse
|
||||
from open_webui.utils.auth import get_admin_user, get_verified_user
|
||||
from open_webui.utils.access_control import has_permission, filter_allowed_access_grants
|
||||
from open_webui.config import BYPASS_ADMIN_ACCESS_CONTROL, STATIC_DIR
|
||||
from open_webui.internal.db import get_session
|
||||
from sqlalchemy.orm import Session
|
||||
from open_webui.internal.db import get_async_session
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -66,7 +66,7 @@ async def get_models(
|
||||
direction: Optional[str] = None,
|
||||
page: Optional[int] = 1,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
limit = PAGE_ITEM_COUNT
|
||||
|
||||
@@ -86,7 +86,7 @@ async def get_models(
|
||||
filter['direction'] = direction
|
||||
|
||||
# Pre-fetch user group IDs once - used for both filter and write_access check
|
||||
groups = Groups.get_groups_by_member_id(user.id, db=db)
|
||||
groups = await Groups.get_groups_by_member_id(user.id, db=db)
|
||||
user_group_ids = {group.id for group in groups}
|
||||
|
||||
if not user.role == 'admin' or not BYPASS_ADMIN_ACCESS_CONTROL:
|
||||
@@ -95,11 +95,11 @@ async def get_models(
|
||||
|
||||
filter['user_id'] = user.id
|
||||
|
||||
result = Models.search_models(user.id, filter=filter, skip=skip, limit=limit, db=db)
|
||||
result = await Models.search_models(user.id, filter=filter, skip=skip, limit=limit, db=db)
|
||||
|
||||
# Batch-fetch writable model IDs in a single query instead of N has_access calls
|
||||
model_ids = [model.id for model in result.items]
|
||||
writable_model_ids = AccessGrants.get_accessible_resource_ids(
|
||||
writable_model_ids = await AccessGrants.get_accessible_resource_ids(
|
||||
user_id=user.id,
|
||||
resource_type='model',
|
||||
resource_ids=model_ids,
|
||||
@@ -130,8 +130,8 @@ async def get_models(
|
||||
|
||||
|
||||
@router.get('/base', response_model=list[ModelResponse])
|
||||
async def get_base_models(user=Depends(get_admin_user), db: Session = Depends(get_session)):
|
||||
return Models.get_base_models(db=db)
|
||||
async def get_base_models(user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
|
||||
return await Models.get_base_models(db=db)
|
||||
|
||||
|
||||
###########################
|
||||
@@ -140,11 +140,11 @@ async def get_base_models(user=Depends(get_admin_user), db: Session = Depends(ge
|
||||
|
||||
|
||||
@router.get('/tags', response_model=list[str])
|
||||
async def get_model_tags(user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
async def get_model_tags(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
if user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL:
|
||||
models = Models.get_models(db=db)
|
||||
models = await Models.get_models(db=db)
|
||||
else:
|
||||
models = Models.get_models_by_user_id(user.id, db=db)
|
||||
models = await Models.get_models_by_user_id(user.id, db=db)
|
||||
|
||||
tags_set = set()
|
||||
for model in models:
|
||||
@@ -172,9 +172,9 @@ async def create_new_model(
|
||||
request: Request,
|
||||
form_data: ModelForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if user.role != 'admin' and not has_permission(
|
||||
if user.role != 'admin' and not await has_permission(
|
||||
user.id, 'workspace.models', request.app.state.config.USER_PERMISSIONS, db=db
|
||||
):
|
||||
raise HTTPException(
|
||||
@@ -182,7 +182,7 @@ async def create_new_model(
|
||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||
)
|
||||
|
||||
model = Models.get_model_by_id(form_data.id, db=db)
|
||||
model = await Models.get_model_by_id(form_data.id, db=db)
|
||||
if model:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
@@ -196,7 +196,7 @@ async def create_new_model(
|
||||
)
|
||||
|
||||
else:
|
||||
form_data.access_grants = filter_allowed_access_grants(
|
||||
form_data.access_grants = await filter_allowed_access_grants(
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
user.id,
|
||||
user.role,
|
||||
@@ -204,7 +204,7 @@ async def create_new_model(
|
||||
'sharing.public_models',
|
||||
)
|
||||
|
||||
model = Models.insert_new_model(form_data, user.id, db=db)
|
||||
model = await Models.insert_new_model(form_data, user.id, db=db)
|
||||
if model:
|
||||
return model
|
||||
else:
|
||||
@@ -223,9 +223,9 @@ async def create_new_model(
|
||||
async def export_models(
|
||||
request: Request,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if user.role != 'admin' and not has_permission(
|
||||
if user.role != 'admin' and not await has_permission(
|
||||
user.id,
|
||||
'workspace.models_export',
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
@@ -237,9 +237,9 @@ async def export_models(
|
||||
)
|
||||
|
||||
if user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL:
|
||||
return Models.get_models(db=db)
|
||||
return await Models.get_models(db=db)
|
||||
else:
|
||||
return Models.get_models_by_user_id(user.id, db=db)
|
||||
return await Models.get_models_by_user_id(user.id, db=db)
|
||||
|
||||
|
||||
############################
|
||||
@@ -256,9 +256,9 @@ async def import_models(
|
||||
request: Request,
|
||||
user=Depends(get_verified_user),
|
||||
form_data: ModelsImportForm = (...),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if user.role != 'admin' and not has_permission(
|
||||
if user.role != 'admin' and not await has_permission(
|
||||
user.id,
|
||||
'workspace.models_import',
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
@@ -278,7 +278,7 @@ async def import_models(
|
||||
if model_data.get('id') and is_valid_model_id(model_data.get('id'))
|
||||
]
|
||||
existing_models = {
|
||||
model.id: model for model in (Models.get_models_by_ids(model_ids, db=db) if model_ids else [])
|
||||
model.id: model for model in (await Models.get_models_by_ids(model_ids, db=db) if model_ids else [])
|
||||
}
|
||||
|
||||
for model_data in data:
|
||||
@@ -293,13 +293,13 @@ async def import_models(
|
||||
model_data['params'] = model_data.get('params', {})
|
||||
|
||||
updated_model = ModelForm(**{**existing_model.model_dump(), **model_data})
|
||||
Models.update_model_by_id(model_id, updated_model, db=db)
|
||||
await Models.update_model_by_id(model_id, updated_model, db=db)
|
||||
else:
|
||||
# Insert new model
|
||||
model_data['meta'] = model_data.get('meta', {})
|
||||
model_data['params'] = model_data.get('params', {})
|
||||
new_model = ModelForm(**model_data)
|
||||
Models.insert_new_model(user_id=user.id, form_data=new_model, db=db)
|
||||
await Models.insert_new_model(user_id=user.id, form_data=new_model, db=db)
|
||||
return True
|
||||
else:
|
||||
raise HTTPException(status_code=400, detail='Invalid JSON format')
|
||||
@@ -322,9 +322,9 @@ async def sync_models(
|
||||
request: Request,
|
||||
form_data: SyncModelsForm,
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
return Models.sync_models(user.id, form_data.models, db=db)
|
||||
return await Models.sync_models(user.id, form_data.models, db=db)
|
||||
|
||||
|
||||
###########################
|
||||
@@ -338,13 +338,13 @@ class ModelIdForm(BaseModel):
|
||||
|
||||
# Note: We're not using the typical url path param here, but instead using a query parameter to allow '/' in the id
|
||||
@router.get('/model', response_model=Optional[ModelAccessResponse])
|
||||
async def get_model_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
model = Models.get_model_by_id(id, db=db)
|
||||
async def get_model_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
model = await Models.get_model_by_id(id, db=db)
|
||||
if model:
|
||||
if (
|
||||
(user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL)
|
||||
or model.user_id == user.id
|
||||
or AccessGrants.has_access(
|
||||
or await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='model',
|
||||
resource_id=model.id,
|
||||
@@ -357,7 +357,7 @@ async def get_model_by_id(id: str, user=Depends(get_verified_user), db: Session
|
||||
write_access=(
|
||||
(user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL)
|
||||
or user.id == model.user_id
|
||||
or AccessGrants.has_access(
|
||||
or await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='model',
|
||||
resource_id=model.id,
|
||||
@@ -384,8 +384,8 @@ async def get_model_by_id(id: str, user=Depends(get_verified_user), db: Session
|
||||
|
||||
|
||||
@router.get('/model/profile/image')
|
||||
def get_model_profile_image(id: str, user=Depends(get_verified_user)):
|
||||
model = Models.get_model_by_id(id)
|
||||
async def get_model_profile_image(id: str, user=Depends(get_verified_user)):
|
||||
model = await Models.get_model_by_id(id)
|
||||
|
||||
if model:
|
||||
etag = f'"{model.updated_at}"' if model.updated_at else None
|
||||
@@ -426,13 +426,13 @@ def get_model_profile_image(id: str, user=Depends(get_verified_user)):
|
||||
|
||||
|
||||
@router.post('/model/toggle', response_model=Optional[ModelResponse])
|
||||
async def toggle_model_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
model = Models.get_model_by_id(id, db=db)
|
||||
async def toggle_model_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
model = await Models.get_model_by_id(id, db=db)
|
||||
if model:
|
||||
if (
|
||||
user.role == 'admin'
|
||||
or model.user_id == user.id
|
||||
or AccessGrants.has_access(
|
||||
or await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='model',
|
||||
resource_id=model.id,
|
||||
@@ -440,7 +440,7 @@ async def toggle_model_by_id(id: str, user=Depends(get_verified_user), db: Sessi
|
||||
db=db,
|
||||
)
|
||||
):
|
||||
model = Models.toggle_model_by_id(id, db=db)
|
||||
model = await Models.toggle_model_by_id(id, db=db)
|
||||
|
||||
if model:
|
||||
return model
|
||||
@@ -471,9 +471,9 @@ async def update_model_by_id(
|
||||
request: Request,
|
||||
form_data: ModelForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
model = Models.get_model_by_id(form_data.id, db=db)
|
||||
model = await Models.get_model_by_id(form_data.id, db=db)
|
||||
if not model:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
@@ -482,7 +482,7 @@ async def update_model_by_id(
|
||||
|
||||
if (
|
||||
model.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='model',
|
||||
resource_id=model.id,
|
||||
@@ -496,7 +496,7 @@ async def update_model_by_id(
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
form_data.access_grants = filter_allowed_access_grants(
|
||||
form_data.access_grants = await filter_allowed_access_grants(
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
user.id,
|
||||
user.role,
|
||||
@@ -504,7 +504,7 @@ async def update_model_by_id(
|
||||
'sharing.public_models',
|
||||
)
|
||||
|
||||
model = Models.update_model_by_id(form_data.id, ModelForm(**form_data.model_dump()), db=db)
|
||||
model = await Models.update_model_by_id(form_data.id, ModelForm(**form_data.model_dump()), db=db)
|
||||
return model
|
||||
|
||||
|
||||
@@ -524,9 +524,9 @@ async def update_model_access_by_id(
|
||||
request: Request,
|
||||
form_data: ModelAccessGrantsForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
model = Models.get_model_by_id(form_data.id, db=db)
|
||||
model = await Models.get_model_by_id(form_data.id, db=db)
|
||||
|
||||
# Non-preset models (e.g. direct Ollama/OpenAI models) may not have a DB
|
||||
# entry yet. Create a minimal one so access grants can be stored.
|
||||
@@ -536,7 +536,7 @@ async def update_model_access_by_id(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
model = Models.insert_new_model(
|
||||
model = await Models.insert_new_model(
|
||||
ModelForm(
|
||||
id=form_data.id,
|
||||
name=form_data.name or form_data.id,
|
||||
@@ -554,7 +554,7 @@ async def update_model_access_by_id(
|
||||
|
||||
if (
|
||||
model.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='model',
|
||||
resource_id=model.id,
|
||||
@@ -568,7 +568,7 @@ async def update_model_access_by_id(
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
form_data.access_grants = filter_allowed_access_grants(
|
||||
form_data.access_grants = await filter_allowed_access_grants(
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
user.id,
|
||||
user.role,
|
||||
@@ -576,11 +576,11 @@ async def update_model_access_by_id(
|
||||
'sharing.public_models',
|
||||
)
|
||||
|
||||
AccessGrants.set_access_grants('model', form_data.id, form_data.access_grants, db=db)
|
||||
await AccessGrants.set_access_grants('model', form_data.id, form_data.access_grants, db=db)
|
||||
|
||||
Models.update_model_updated_at_by_id(form_data.id, db=db)
|
||||
await Models.update_model_updated_at_by_id(form_data.id, db=db)
|
||||
|
||||
return Models.get_model_by_id(form_data.id, db=db)
|
||||
return await Models.get_model_by_id(form_data.id, db=db)
|
||||
|
||||
|
||||
############################
|
||||
@@ -592,9 +592,9 @@ async def update_model_access_by_id(
|
||||
async def delete_model_by_id(
|
||||
form_data: ModelIdForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
model = Models.get_model_by_id(form_data.id, db=db)
|
||||
model = await Models.get_model_by_id(form_data.id, db=db)
|
||||
if not model:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
@@ -604,7 +604,7 @@ async def delete_model_by_id(
|
||||
if (
|
||||
user.role != 'admin'
|
||||
and model.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='model',
|
||||
resource_id=model.id,
|
||||
@@ -617,11 +617,11 @@ async def delete_model_by_id(
|
||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||
)
|
||||
|
||||
result = Models.delete_model_by_id(form_data.id, db=db)
|
||||
result = await Models.delete_model_by_id(form_data.id, db=db)
|
||||
return result
|
||||
|
||||
|
||||
@router.delete('/delete/all', response_model=bool)
|
||||
async def delete_all_models(user=Depends(get_admin_user), db: Session = Depends(get_session)):
|
||||
result = Models.delete_all_models(db=db)
|
||||
async def delete_all_models(user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
|
||||
result = await Models.delete_all_models(db=db)
|
||||
return result
|
||||
|
||||
@@ -34,8 +34,8 @@ from open_webui.utils.access_control import (
|
||||
filter_allowed_access_grants,
|
||||
)
|
||||
from open_webui.models.access_grants import AccessGrants
|
||||
from open_webui.internal.db import get_session
|
||||
from sqlalchemy.orm import Session
|
||||
from open_webui.internal.db import get_async_session
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -68,9 +68,9 @@ async def get_notes(
|
||||
request: Request,
|
||||
page: Optional[int] = None,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if user.role != 'admin' and not has_permission(
|
||||
if user.role != 'admin' and not await has_permission(
|
||||
user.id, 'features.notes', request.app.state.config.USER_PERMISSIONS, db=db
|
||||
):
|
||||
raise HTTPException(
|
||||
@@ -84,12 +84,12 @@ async def get_notes(
|
||||
limit = 60
|
||||
skip = (page - 1) * limit
|
||||
|
||||
notes = Notes.get_notes_by_user_id(user.id, 'read', skip=skip, limit=limit, db=db)
|
||||
notes = await Notes.get_notes_by_user_id(user.id, 'read', skip=skip, limit=limit, db=db)
|
||||
if not notes:
|
||||
return []
|
||||
|
||||
user_ids = list(set(note.user_id for note in notes))
|
||||
users = {user.id: user for user in Users.get_users_by_user_ids(user_ids, db=db)}
|
||||
users = {user.id: user for user in await Users.get_users_by_user_ids(user_ids, db=db)}
|
||||
|
||||
return [
|
||||
NoteUserResponse(
|
||||
@@ -114,9 +114,9 @@ async def search_notes(
|
||||
direction: Optional[str] = None,
|
||||
page: Optional[int] = 1,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if user.role != 'admin' and not has_permission(
|
||||
if user.role != 'admin' and not await has_permission(
|
||||
user.id, 'features.notes', request.app.state.config.USER_PERMISSIONS, db=db
|
||||
):
|
||||
raise HTTPException(
|
||||
@@ -143,13 +143,13 @@ async def search_notes(
|
||||
filter['direction'] = direction
|
||||
|
||||
if not user.role == 'admin' or not BYPASS_ADMIN_ACCESS_CONTROL:
|
||||
groups = Groups.get_groups_by_member_id(user.id, db=db)
|
||||
groups = await Groups.get_groups_by_member_id(user.id, db=db)
|
||||
if groups:
|
||||
filter['group_ids'] = [group.id for group in groups]
|
||||
|
||||
filter['user_id'] = user.id
|
||||
|
||||
result = Notes.search_notes(user.id, filter, skip=skip, limit=limit, db=db)
|
||||
result = await Notes.search_notes(user.id, filter, skip=skip, limit=limit, db=db)
|
||||
for note in result.items:
|
||||
note.data = _truncate_note_data(note.data)
|
||||
return result
|
||||
@@ -165,9 +165,9 @@ async def create_new_note(
|
||||
request: Request,
|
||||
form_data: NoteForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if user.role != 'admin' and not has_permission(
|
||||
if user.role != 'admin' and not await has_permission(
|
||||
user.id, 'features.notes', request.app.state.config.USER_PERMISSIONS, db=db
|
||||
):
|
||||
raise HTTPException(
|
||||
@@ -175,7 +175,7 @@ async def create_new_note(
|
||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||
)
|
||||
|
||||
form_data.access_grants = filter_allowed_access_grants(
|
||||
form_data.access_grants = await filter_allowed_access_grants(
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
user.id,
|
||||
user.role,
|
||||
@@ -185,7 +185,7 @@ async def create_new_note(
|
||||
)
|
||||
|
||||
try:
|
||||
note = Notes.insert_new_note(user.id, form_data, db=db)
|
||||
note = await Notes.insert_new_note(user.id, form_data, db=db)
|
||||
return note
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
@@ -206,9 +206,9 @@ async def get_note_by_id(
|
||||
request: Request,
|
||||
id: str,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if user.role != 'admin' and not has_permission(
|
||||
if user.role != 'admin' and not await has_permission(
|
||||
user.id, 'features.notes', request.app.state.config.USER_PERMISSIONS, db=db
|
||||
):
|
||||
raise HTTPException(
|
||||
@@ -216,14 +216,14 @@ async def get_note_by_id(
|
||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||
)
|
||||
|
||||
note = Notes.get_note_by_id(id, db=db)
|
||||
note = await Notes.get_note_by_id(id, db=db)
|
||||
if not note:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND)
|
||||
|
||||
if user.role != 'admin' and (
|
||||
user.id != note.user_id
|
||||
and (
|
||||
not AccessGrants.has_access(
|
||||
not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='note',
|
||||
resource_id=note.id,
|
||||
@@ -237,7 +237,7 @@ async def get_note_by_id(
|
||||
write_access = (
|
||||
user.role == 'admin'
|
||||
or (user.id == note.user_id)
|
||||
or AccessGrants.has_access(
|
||||
or await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='note',
|
||||
resource_id=note.id,
|
||||
@@ -261,9 +261,9 @@ async def update_note_by_id(
|
||||
id: str,
|
||||
form_data: NoteForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if user.role != 'admin' and not has_permission(
|
||||
if user.role != 'admin' and not await has_permission(
|
||||
user.id, 'features.notes', request.app.state.config.USER_PERMISSIONS, db=db
|
||||
):
|
||||
raise HTTPException(
|
||||
@@ -271,13 +271,13 @@ async def update_note_by_id(
|
||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||
)
|
||||
|
||||
note = Notes.get_note_by_id(id, db=db)
|
||||
note = await Notes.get_note_by_id(id, db=db)
|
||||
if not note:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND)
|
||||
|
||||
if user.role != 'admin' and (
|
||||
user.id != note.user_id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='note',
|
||||
resource_id=note.id,
|
||||
@@ -287,7 +287,7 @@ async def update_note_by_id(
|
||||
):
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
|
||||
|
||||
form_data.access_grants = filter_allowed_access_grants(
|
||||
form_data.access_grants = await filter_allowed_access_grants(
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
user.id,
|
||||
user.role,
|
||||
@@ -297,7 +297,7 @@ async def update_note_by_id(
|
||||
)
|
||||
|
||||
try:
|
||||
note = Notes.update_note_by_id(id, form_data, db=db)
|
||||
note = await Notes.update_note_by_id(id, form_data, db=db)
|
||||
await sio.emit(
|
||||
'note-events',
|
||||
note.model_dump(),
|
||||
@@ -325,9 +325,9 @@ async def update_note_access_by_id(
|
||||
id: str,
|
||||
form_data: NoteAccessGrantsForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if user.role != 'admin' and not has_permission(
|
||||
if user.role != 'admin' and not await has_permission(
|
||||
user.id, 'features.notes', request.app.state.config.USER_PERMISSIONS, db=db
|
||||
):
|
||||
raise HTTPException(
|
||||
@@ -335,13 +335,13 @@ async def update_note_access_by_id(
|
||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||
)
|
||||
|
||||
note = Notes.get_note_by_id(id, db=db)
|
||||
note = await Notes.get_note_by_id(id, db=db)
|
||||
if not note:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND)
|
||||
|
||||
if user.role != 'admin' and (
|
||||
user.id != note.user_id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='note',
|
||||
resource_id=note.id,
|
||||
@@ -351,7 +351,7 @@ async def update_note_access_by_id(
|
||||
):
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
|
||||
|
||||
form_data.access_grants = filter_allowed_access_grants(
|
||||
form_data.access_grants = await filter_allowed_access_grants(
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
user.id,
|
||||
user.role,
|
||||
@@ -359,9 +359,9 @@ async def update_note_access_by_id(
|
||||
'sharing.public_notes',
|
||||
)
|
||||
|
||||
AccessGrants.set_access_grants('note', id, form_data.access_grants, db=db)
|
||||
await AccessGrants.set_access_grants('note', id, form_data.access_grants, db=db)
|
||||
|
||||
return Notes.get_note_by_id(id, db=db)
|
||||
return await Notes.get_note_by_id(id, db=db)
|
||||
|
||||
|
||||
############################
|
||||
@@ -374,9 +374,9 @@ async def delete_note_by_id(
|
||||
request: Request,
|
||||
id: str,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if user.role != 'admin' and not has_permission(
|
||||
if user.role != 'admin' and not await has_permission(
|
||||
user.id, 'features.notes', request.app.state.config.USER_PERMISSIONS, db=db
|
||||
):
|
||||
raise HTTPException(
|
||||
@@ -384,13 +384,13 @@ async def delete_note_by_id(
|
||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||
)
|
||||
|
||||
note = Notes.get_note_by_id(id, db=db)
|
||||
note = await Notes.get_note_by_id(id, db=db)
|
||||
if not note:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND)
|
||||
|
||||
if user.role != 'admin' and (
|
||||
user.id != note.user_id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='note',
|
||||
resource_id=note.id,
|
||||
@@ -401,7 +401,7 @@ async def delete_note_by_id(
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
|
||||
|
||||
try:
|
||||
note = Notes.delete_note_by_id(id, db=db)
|
||||
note = await Notes.delete_note_by_id(id, db=db)
|
||||
return True
|
||||
except Exception as e:
|
||||
log.exception(e)
|
||||
|
||||
@@ -39,9 +39,9 @@ from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import StreamingResponse
|
||||
from pydantic import BaseModel, ConfigDict, validator
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from open_webui.internal.db import get_session
|
||||
from open_webui.internal.db import get_async_session
|
||||
|
||||
|
||||
from open_webui.models.models import Models
|
||||
@@ -398,11 +398,11 @@ async def get_all_models(request: Request, user: UserModel = None):
|
||||
async def get_filtered_models(models, user, db=None):
|
||||
# Filter models based on user access control
|
||||
model_ids = [model['model'] for model in models.get('models', [])]
|
||||
model_infos = {model_info.id: model_info for model_info in Models.get_models_by_ids(model_ids, db=db)}
|
||||
user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user.id, db=db)}
|
||||
model_infos = {model_info.id: model_info for model_info in await Models.get_models_by_ids(model_ids, db=db)}
|
||||
user_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user.id, db=db)}
|
||||
|
||||
# Batch-fetch accessible resource IDs in a single query instead of N has_access calls
|
||||
accessible_model_ids = AccessGrants.get_accessible_resource_ids(
|
||||
accessible_model_ids = await AccessGrants.get_accessible_resource_ids(
|
||||
user_id=user.id,
|
||||
resource_type='model',
|
||||
resource_ids=list(model_infos.keys()),
|
||||
@@ -800,7 +800,7 @@ async def show_model_info(request: Request, form_data: ModelNameForm, user=Depen
|
||||
model = form_data.get('model')
|
||||
|
||||
# Enforce per-model access control
|
||||
check_model_access(user, Models.get_model_by_id(model), BYPASS_MODEL_ACCESS_CONTROL)
|
||||
await check_model_access(user, await Models.get_model_by_id(model), BYPASS_MODEL_ACCESS_CONTROL)
|
||||
|
||||
await get_all_models(request, user=user)
|
||||
models = request.app.state.OLLAMA_MODELS
|
||||
@@ -850,7 +850,7 @@ async def embed(
|
||||
log.info(f'generate_ollama_batch_embeddings {form_data}')
|
||||
|
||||
# Enforce per-model access control
|
||||
check_model_access(user, Models.get_model_by_id(form_data.model), BYPASS_MODEL_ACCESS_CONTROL)
|
||||
await check_model_access(user, await Models.get_model_by_id(form_data.model), BYPASS_MODEL_ACCESS_CONTROL)
|
||||
|
||||
if url_idx is None:
|
||||
model = form_data.model
|
||||
@@ -909,7 +909,7 @@ async def embeddings(
|
||||
log.info(f'generate_ollama_embeddings {form_data}')
|
||||
|
||||
# Enforce per-model access control
|
||||
check_model_access(user, Models.get_model_by_id(form_data.model), BYPASS_MODEL_ACCESS_CONTROL)
|
||||
await check_model_access(user, await Models.get_model_by_id(form_data.model), BYPASS_MODEL_ACCESS_CONTROL)
|
||||
|
||||
if url_idx is None:
|
||||
model = form_data.model
|
||||
@@ -974,7 +974,7 @@ async def generate_completion(
|
||||
raise HTTPException(status_code=503, detail='Ollama API is disabled')
|
||||
|
||||
# Enforce per-model access control
|
||||
check_model_access(user, Models.get_model_by_id(form_data.model), BYPASS_MODEL_ACCESS_CONTROL)
|
||||
await check_model_access(user, await Models.get_model_by_id(form_data.model), BYPASS_MODEL_ACCESS_CONTROL)
|
||||
|
||||
if url_idx is None:
|
||||
await get_all_models(request, user=user)
|
||||
@@ -1064,7 +1064,7 @@ async def generate_chat_completion(
|
||||
if not request.app.state.config.ENABLE_OLLAMA_API:
|
||||
raise HTTPException(status_code=503, detail='Ollama API is disabled')
|
||||
|
||||
# NOTE: We intentionally do NOT use Depends(get_session) here.
|
||||
# NOTE: We intentionally do NOT use Depends(get_async_session) here.
|
||||
# Database operations (get_model_by_id, AccessGrants.has_access) manage their own short-lived sessions.
|
||||
# This prevents holding a connection during the entire LLM call (30-60+ seconds),
|
||||
# which would exhaust the connection pool under concurrent load.
|
||||
@@ -1093,7 +1093,7 @@ async def generate_chat_completion(
|
||||
del payload['metadata']
|
||||
|
||||
model_id = payload['model']
|
||||
model_info = Models.get_model_by_id(model_id)
|
||||
model_info = await Models.get_model_by_id(model_id)
|
||||
|
||||
if model_info:
|
||||
if model_info.base_model_id:
|
||||
@@ -1111,9 +1111,9 @@ async def generate_chat_completion(
|
||||
if not bypass_system_prompt:
|
||||
payload = apply_system_prompt_to_body(system, payload, metadata, user)
|
||||
|
||||
check_model_access(user, model_info, bypass_filter)
|
||||
await check_model_access(user, model_info, bypass_filter)
|
||||
else:
|
||||
check_model_access(user, None, bypass_filter)
|
||||
await check_model_access(user, None, bypass_filter)
|
||||
|
||||
url, url_idx = await get_ollama_url(request, payload['model'], url_idx)
|
||||
api_config = request.app.state.config.OLLAMA_API_CONFIGS.get(
|
||||
@@ -1171,7 +1171,7 @@ async def generate_openai_completion(
|
||||
url_idx: Optional[int] = None,
|
||||
user=Depends(get_verified_user),
|
||||
):
|
||||
# NOTE: We intentionally do NOT use Depends(get_session) here.
|
||||
# NOTE: We intentionally do NOT use Depends(get_async_session) here.
|
||||
# Database operations (get_model_by_id, AccessGrants.has_access) manage their own short-lived sessions.
|
||||
# This prevents holding a connection during the entire LLM call (30-60+ seconds),
|
||||
# which would exhaust the connection pool under concurrent load.
|
||||
@@ -1191,7 +1191,7 @@ async def generate_openai_completion(
|
||||
del payload['metadata']
|
||||
|
||||
model_id = form_data.model
|
||||
model_info = Models.get_model_by_id(model_id)
|
||||
model_info = await Models.get_model_by_id(model_id)
|
||||
if model_info:
|
||||
if model_info.base_model_id:
|
||||
payload['model'] = model_info.base_model_id
|
||||
@@ -1200,9 +1200,9 @@ async def generate_openai_completion(
|
||||
if params:
|
||||
payload = apply_model_params_to_body_openai(params, payload)
|
||||
|
||||
check_model_access(user, model_info)
|
||||
await check_model_access(user, model_info)
|
||||
else:
|
||||
check_model_access(user, None)
|
||||
await check_model_access(user, None)
|
||||
|
||||
url, url_idx = await get_ollama_url(request, payload['model'], url_idx)
|
||||
api_config = request.app.state.config.OLLAMA_API_CONFIGS.get(
|
||||
@@ -1233,7 +1233,7 @@ async def generate_openai_chat_completion(
|
||||
url_idx: Optional[int] = None,
|
||||
user=Depends(get_verified_user),
|
||||
):
|
||||
# NOTE: We intentionally do NOT use Depends(get_session) here.
|
||||
# NOTE: We intentionally do NOT use Depends(get_async_session) here.
|
||||
# Database operations (get_model_by_id, AccessGrants.has_access) manage their own short-lived sessions.
|
||||
# This prevents holding a connection during the entire LLM call (30-60+ seconds),
|
||||
# which would exhaust the connection pool under concurrent load.
|
||||
@@ -1253,7 +1253,7 @@ async def generate_openai_chat_completion(
|
||||
del payload['metadata']
|
||||
|
||||
model_id = completion_form.model
|
||||
model_info = Models.get_model_by_id(model_id)
|
||||
model_info = await Models.get_model_by_id(model_id)
|
||||
if model_info:
|
||||
if model_info.base_model_id:
|
||||
payload['model'] = model_info.base_model_id
|
||||
@@ -1266,9 +1266,9 @@ async def generate_openai_chat_completion(
|
||||
payload = apply_model_params_to_body_openai(params, payload)
|
||||
payload = apply_system_prompt_to_body(system, payload, metadata, user)
|
||||
|
||||
check_model_access(user, model_info)
|
||||
await check_model_access(user, model_info)
|
||||
else:
|
||||
check_model_access(user, None)
|
||||
await check_model_access(user, None)
|
||||
|
||||
url, url_idx = await get_ollama_url(request, payload['model'], url_idx)
|
||||
api_config = request.app.state.config.OLLAMA_API_CONFIGS.get(
|
||||
@@ -1313,14 +1313,14 @@ async def generate_anthropic_messages(
|
||||
payload = {**form_data}
|
||||
model_id = payload.get('model', '')
|
||||
|
||||
model_info = Models.get_model_by_id(model_id)
|
||||
model_info = await Models.get_model_by_id(model_id)
|
||||
if model_info:
|
||||
if model_info.base_model_id:
|
||||
payload['model'] = model_info.base_model_id
|
||||
|
||||
check_model_access(user, model_info)
|
||||
await check_model_access(user, model_info)
|
||||
else:
|
||||
check_model_access(user, None)
|
||||
await check_model_access(user, None)
|
||||
|
||||
url, url_idx = await get_ollama_url(request, payload['model'], url_idx)
|
||||
api_config = request.app.state.config.OLLAMA_API_CONFIGS.get(
|
||||
@@ -1371,17 +1371,17 @@ async def generate_responses(
|
||||
payload = form_data.model_dump()
|
||||
model_id = form_data.model
|
||||
|
||||
model_info = Models.get_model_by_id(model_id)
|
||||
model_info = await Models.get_model_by_id(model_id)
|
||||
if model_info:
|
||||
if model_info.base_model_id:
|
||||
payload['model'] = model_info.base_model_id
|
||||
|
||||
# Check if user has access to the model
|
||||
if user.role == 'user':
|
||||
user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user.id)}
|
||||
user_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user.id)}
|
||||
if not (
|
||||
user.id == model_info.user_id
|
||||
or AccessGrants.has_access(
|
||||
or await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='model',
|
||||
resource_id=model_info.id,
|
||||
@@ -1426,7 +1426,7 @@ async def get_openai_models(
|
||||
request: Request,
|
||||
url_idx: Optional[int] = None,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
models = []
|
||||
if url_idx is None:
|
||||
@@ -1458,11 +1458,11 @@ async def get_openai_models(
|
||||
if user.role == 'user' and not BYPASS_MODEL_ACCESS_CONTROL:
|
||||
# Filter models based on user access control
|
||||
model_ids = [model['id'] for model in models]
|
||||
model_infos = {model_info.id: model_info for model_info in Models.get_models_by_ids(model_ids, db=db)}
|
||||
user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user.id, db=db)}
|
||||
model_infos = {model_info.id: model_info for model_info in await Models.get_models_by_ids(model_ids, db=db)}
|
||||
user_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user.id, db=db)}
|
||||
|
||||
# Batch-fetch accessible resource IDs in a single query instead of N has_access calls
|
||||
accessible_model_ids = AccessGrants.get_accessible_resource_ids(
|
||||
accessible_model_ids = await AccessGrants.get_accessible_resource_ids(
|
||||
user_id=user.id,
|
||||
resource_type='model',
|
||||
resource_ids=list(model_infos.keys()),
|
||||
|
||||
@@ -21,9 +21,9 @@ from fastapi.responses import (
|
||||
)
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from open_webui.internal.db import get_session
|
||||
from open_webui.internal.db import get_async_session
|
||||
|
||||
from open_webui.models.models import Models
|
||||
from open_webui.models.access_grants import AccessGrants
|
||||
@@ -450,11 +450,11 @@ async def get_all_models_responses(request: Request, user: UserModel) -> list:
|
||||
async def get_filtered_models(models, user, db=None):
|
||||
# Filter models based on user access control
|
||||
model_ids = [model['id'] for model in models.get('data', [])]
|
||||
model_infos = {model_info.id: model_info for model_info in Models.get_models_by_ids(model_ids, db=db)}
|
||||
user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user.id, db=db)}
|
||||
model_infos = {model_info.id: model_info for model_info in await Models.get_models_by_ids(model_ids, db=db)}
|
||||
user_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user.id, db=db)}
|
||||
|
||||
# Batch-fetch accessible resource IDs in a single query instead of N has_access calls
|
||||
accessible_model_ids = AccessGrants.get_accessible_resource_ids(
|
||||
accessible_model_ids = await AccessGrants.get_accessible_resource_ids(
|
||||
user_id=user.id,
|
||||
resource_type='model',
|
||||
resource_ids=list(model_infos.keys()),
|
||||
@@ -1025,7 +1025,7 @@ async def generate_chat_completion(
|
||||
user=Depends(get_verified_user),
|
||||
bypass_system_prompt: bool = False,
|
||||
):
|
||||
# NOTE: We intentionally do NOT use Depends(get_session) here.
|
||||
# NOTE: We intentionally do NOT use Depends(get_async_session) here.
|
||||
# Database operations (get_model_by_id, AccessGrants.has_access) manage their own short-lived sessions.
|
||||
# This prevents holding a connection during the entire LLM call (30-60+ seconds),
|
||||
# which would exhaust the connection pool under concurrent load.
|
||||
@@ -1043,7 +1043,7 @@ async def generate_chat_completion(
|
||||
metadata = payload.pop('metadata', None)
|
||||
|
||||
model_id = form_data.get('model')
|
||||
model_info = Models.get_model_by_id(model_id)
|
||||
model_info = await Models.get_model_by_id(model_id)
|
||||
|
||||
# Check model info and override the payload
|
||||
if model_info:
|
||||
@@ -1063,9 +1063,9 @@ async def generate_chat_completion(
|
||||
if not bypass_system_prompt:
|
||||
payload = apply_system_prompt_to_body(system, payload, metadata, user)
|
||||
|
||||
check_model_access(user, model_info, bypass_filter)
|
||||
await check_model_access(user, model_info, bypass_filter)
|
||||
else:
|
||||
check_model_access(user, None, bypass_filter)
|
||||
await check_model_access(user, None, bypass_filter)
|
||||
|
||||
# Check if model is already in app state cache to avoid expensive get_all_models() call
|
||||
models = request.app.state.OPENAI_MODELS
|
||||
@@ -1344,7 +1344,7 @@ async def responses(
|
||||
model_id = form_data.model
|
||||
|
||||
# Enforce per-model access control
|
||||
check_model_access(user, Models.get_model_by_id(model_id), BYPASS_MODEL_ACCESS_CONTROL)
|
||||
await check_model_access(user, await Models.get_model_by_id(model_id), BYPASS_MODEL_ACCESS_CONTROL)
|
||||
|
||||
body = json.dumps(payload)
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ from open_webui.constants import ERROR_MESSAGES
|
||||
from open_webui.utils.auth import get_admin_user, get_verified_user
|
||||
from open_webui.utils.access_control import has_permission, filter_allowed_access_grants
|
||||
from open_webui.config import BYPASS_ADMIN_ACCESS_CONTROL
|
||||
from open_webui.internal.db import get_session
|
||||
from sqlalchemy.orm import Session
|
||||
from open_webui.internal.db import get_async_session
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
@@ -48,21 +48,21 @@ PAGE_ITEM_COUNT = 30
|
||||
|
||||
|
||||
@router.get('/', response_model=list[PromptModel])
|
||||
async def get_prompts(user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
async def get_prompts(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
if user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL:
|
||||
prompts = Prompts.get_prompts(db=db)
|
||||
prompts = await Prompts.get_prompts(db=db)
|
||||
else:
|
||||
prompts = Prompts.get_prompts_by_user_id(user.id, 'read', db=db)
|
||||
prompts = await Prompts.get_prompts_by_user_id(user.id, 'read', db=db)
|
||||
|
||||
return prompts
|
||||
|
||||
|
||||
@router.get('/tags', response_model=list[str])
|
||||
async def get_prompt_tags(user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
async def get_prompt_tags(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
if user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL:
|
||||
return Prompts.get_tags(db=db)
|
||||
return await Prompts.get_tags(db=db)
|
||||
else:
|
||||
prompts = Prompts.get_prompts_by_user_id(user.id, 'read', db=db)
|
||||
prompts = await Prompts.get_prompts_by_user_id(user.id, 'read', db=db)
|
||||
tags = set()
|
||||
for prompt in prompts:
|
||||
if prompt.tags:
|
||||
@@ -79,7 +79,7 @@ async def get_prompt_list(
|
||||
direction: Optional[str] = None,
|
||||
page: Optional[int] = 1,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
limit = PAGE_ITEM_COUNT
|
||||
|
||||
@@ -99,7 +99,7 @@ async def get_prompt_list(
|
||||
filter['direction'] = direction
|
||||
|
||||
# Pre-fetch user group IDs once - used for both filter and write_access check
|
||||
groups = Groups.get_groups_by_member_id(user.id, db=db)
|
||||
groups = await Groups.get_groups_by_member_id(user.id, db=db)
|
||||
user_group_ids = {group.id for group in groups}
|
||||
|
||||
if not (user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL):
|
||||
@@ -108,11 +108,11 @@ async def get_prompt_list(
|
||||
|
||||
filter['user_id'] = user.id
|
||||
|
||||
result = Prompts.search_prompts(user.id, filter=filter, skip=skip, limit=limit, db=db)
|
||||
result = await Prompts.search_prompts(user.id, filter=filter, skip=skip, limit=limit, db=db)
|
||||
|
||||
# Batch-fetch writable prompt IDs in a single query instead of N has_access calls
|
||||
prompt_ids = [prompt.id for prompt in result.items]
|
||||
writable_prompt_ids = AccessGrants.get_accessible_resource_ids(
|
||||
writable_prompt_ids = await AccessGrants.get_accessible_resource_ids(
|
||||
user_id=user.id,
|
||||
resource_type='prompt',
|
||||
resource_ids=prompt_ids,
|
||||
@@ -147,16 +147,16 @@ async def create_new_prompt(
|
||||
request: Request,
|
||||
form_data: PromptForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if user.role != 'admin' and not (
|
||||
has_permission(
|
||||
await has_permission(
|
||||
user.id,
|
||||
'workspace.prompts',
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
db=db,
|
||||
)
|
||||
or has_permission(
|
||||
or await has_permission(
|
||||
user.id,
|
||||
'workspace.prompts_import',
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
@@ -168,7 +168,7 @@ async def create_new_prompt(
|
||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||
)
|
||||
|
||||
form_data.access_grants = filter_allowed_access_grants(
|
||||
form_data.access_grants = await filter_allowed_access_grants(
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
user.id,
|
||||
user.role,
|
||||
@@ -176,9 +176,9 @@ async def create_new_prompt(
|
||||
'sharing.public_prompts',
|
||||
)
|
||||
|
||||
prompt = Prompts.get_prompt_by_command(form_data.command, db=db)
|
||||
prompt = await Prompts.get_prompt_by_command(form_data.command, db=db)
|
||||
if prompt is None:
|
||||
prompt = Prompts.insert_new_prompt(user.id, form_data, db=db)
|
||||
prompt = await Prompts.insert_new_prompt(user.id, form_data, db=db)
|
||||
|
||||
if prompt:
|
||||
return prompt
|
||||
@@ -198,14 +198,14 @@ async def create_new_prompt(
|
||||
|
||||
|
||||
@router.get('/command/{command}', response_model=Optional[PromptAccessResponse])
|
||||
async def get_prompt_by_command(command: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
prompt = Prompts.get_prompt_by_command(command, db=db)
|
||||
async def get_prompt_by_command(command: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
prompt = await Prompts.get_prompt_by_command(command, db=db)
|
||||
|
||||
if prompt:
|
||||
if (
|
||||
user.role == 'admin'
|
||||
or prompt.user_id == user.id
|
||||
or AccessGrants.has_access(
|
||||
or await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='prompt',
|
||||
resource_id=prompt.id,
|
||||
@@ -218,7 +218,7 @@ async def get_prompt_by_command(command: str, user=Depends(get_verified_user), d
|
||||
write_access=(
|
||||
(user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL)
|
||||
or user.id == prompt.user_id
|
||||
or AccessGrants.has_access(
|
||||
or await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='prompt',
|
||||
resource_id=prompt.id,
|
||||
@@ -240,14 +240,14 @@ async def get_prompt_by_command(command: str, user=Depends(get_verified_user), d
|
||||
|
||||
|
||||
@router.get('/id/{prompt_id}', response_model=Optional[PromptAccessResponse])
|
||||
async def get_prompt_by_id(prompt_id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
prompt = Prompts.get_prompt_by_id(prompt_id, db=db)
|
||||
async def get_prompt_by_id(prompt_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
prompt = await Prompts.get_prompt_by_id(prompt_id, db=db)
|
||||
|
||||
if prompt:
|
||||
if (
|
||||
user.role == 'admin'
|
||||
or prompt.user_id == user.id
|
||||
or AccessGrants.has_access(
|
||||
or await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='prompt',
|
||||
resource_id=prompt.id,
|
||||
@@ -260,7 +260,7 @@ async def get_prompt_by_id(prompt_id: str, user=Depends(get_verified_user), db:
|
||||
write_access=(
|
||||
(user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL)
|
||||
or user.id == prompt.user_id
|
||||
or AccessGrants.has_access(
|
||||
or await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='prompt',
|
||||
resource_id=prompt.id,
|
||||
@@ -287,9 +287,9 @@ async def update_prompt_by_id(
|
||||
prompt_id: str,
|
||||
form_data: PromptForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
prompt = Prompts.get_prompt_by_id(prompt_id, db=db)
|
||||
prompt = await Prompts.get_prompt_by_id(prompt_id, db=db)
|
||||
|
||||
if not prompt:
|
||||
raise HTTPException(
|
||||
@@ -300,7 +300,7 @@ async def update_prompt_by_id(
|
||||
# Is the user the original creator, in a group with write access, or an admin
|
||||
if (
|
||||
prompt.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='prompt',
|
||||
resource_id=prompt.id,
|
||||
@@ -316,14 +316,14 @@ async def update_prompt_by_id(
|
||||
|
||||
# Check for command collision if command is being changed
|
||||
if form_data.command != prompt.command:
|
||||
existing_prompt = Prompts.get_prompt_by_command(form_data.command, db=db)
|
||||
existing_prompt = await Prompts.get_prompt_by_command(form_data.command, db=db)
|
||||
if existing_prompt and existing_prompt.id != prompt.id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"Command '/{form_data.command}' is already in use by another prompt",
|
||||
)
|
||||
|
||||
form_data.access_grants = filter_allowed_access_grants(
|
||||
form_data.access_grants = await filter_allowed_access_grants(
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
user.id,
|
||||
user.role,
|
||||
@@ -332,7 +332,7 @@ async def update_prompt_by_id(
|
||||
)
|
||||
|
||||
# Use the ID from the found prompt
|
||||
updated_prompt = Prompts.update_prompt_by_id(prompt.id, form_data, user.id, db=db)
|
||||
updated_prompt = await Prompts.update_prompt_by_id(prompt.id, form_data, user.id, db=db)
|
||||
if updated_prompt:
|
||||
return updated_prompt
|
||||
else:
|
||||
@@ -352,10 +352,10 @@ async def update_prompt_metadata(
|
||||
prompt_id: str,
|
||||
form_data: PromptMetadataForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""Update prompt name and command only (no history created)."""
|
||||
prompt = Prompts.get_prompt_by_id(prompt_id, db=db)
|
||||
prompt = await Prompts.get_prompt_by_id(prompt_id, db=db)
|
||||
|
||||
if not prompt:
|
||||
raise HTTPException(
|
||||
@@ -365,7 +365,7 @@ async def update_prompt_metadata(
|
||||
|
||||
if (
|
||||
prompt.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='prompt',
|
||||
resource_id=prompt.id,
|
||||
@@ -381,14 +381,14 @@ async def update_prompt_metadata(
|
||||
|
||||
# Check for command collision if command is being changed
|
||||
if form_data.command != prompt.command:
|
||||
existing_prompt = Prompts.get_prompt_by_command(form_data.command, db=db)
|
||||
existing_prompt = await Prompts.get_prompt_by_command(form_data.command, db=db)
|
||||
if existing_prompt and existing_prompt.id != prompt.id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"Command '/{form_data.command}' is already in use",
|
||||
)
|
||||
|
||||
updated_prompt = Prompts.update_prompt_metadata(prompt.id, form_data.name, form_data.command, form_data.tags, db=db)
|
||||
updated_prompt = await Prompts.update_prompt_metadata(prompt.id, form_data.name, form_data.command, form_data.tags, db=db)
|
||||
if updated_prompt:
|
||||
return updated_prompt
|
||||
else:
|
||||
@@ -403,9 +403,9 @@ async def set_prompt_version(
|
||||
prompt_id: str,
|
||||
form_data: PromptVersionUpdateForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
prompt = Prompts.get_prompt_by_id(prompt_id, db=db)
|
||||
prompt = await Prompts.get_prompt_by_id(prompt_id, db=db)
|
||||
if not prompt:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -414,7 +414,7 @@ async def set_prompt_version(
|
||||
|
||||
if (
|
||||
prompt.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='prompt',
|
||||
resource_id=prompt.id,
|
||||
@@ -428,7 +428,7 @@ async def set_prompt_version(
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
updated_prompt = Prompts.update_prompt_version(prompt.id, form_data.version_id, db=db)
|
||||
updated_prompt = await Prompts.update_prompt_version(prompt.id, form_data.version_id, db=db)
|
||||
if updated_prompt:
|
||||
return updated_prompt
|
||||
else:
|
||||
@@ -453,9 +453,9 @@ async def update_prompt_access_by_id(
|
||||
prompt_id: str,
|
||||
form_data: PromptAccessGrantsForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
prompt = Prompts.get_prompt_by_id(prompt_id, db=db)
|
||||
prompt = await Prompts.get_prompt_by_id(prompt_id, db=db)
|
||||
if not prompt:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -464,7 +464,7 @@ async def update_prompt_access_by_id(
|
||||
|
||||
if (
|
||||
prompt.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='prompt',
|
||||
resource_id=prompt.id,
|
||||
@@ -478,7 +478,7 @@ async def update_prompt_access_by_id(
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
form_data.access_grants = filter_allowed_access_grants(
|
||||
form_data.access_grants = await filter_allowed_access_grants(
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
user.id,
|
||||
user.role,
|
||||
@@ -486,9 +486,9 @@ async def update_prompt_access_by_id(
|
||||
'sharing.public_prompts',
|
||||
)
|
||||
|
||||
AccessGrants.set_access_grants('prompt', prompt_id, form_data.access_grants, db=db)
|
||||
await AccessGrants.set_access_grants('prompt', prompt_id, form_data.access_grants, db=db)
|
||||
|
||||
return Prompts.get_prompt_by_id(prompt_id, db=db)
|
||||
return await Prompts.get_prompt_by_id(prompt_id, db=db)
|
||||
|
||||
|
||||
############################
|
||||
@@ -497,8 +497,8 @@ async def update_prompt_access_by_id(
|
||||
|
||||
|
||||
@router.post('/id/{prompt_id}/toggle', response_model=Optional[PromptModel])
|
||||
async def toggle_prompt_active(prompt_id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
prompt = Prompts.get_prompt_by_id(prompt_id, db=db)
|
||||
async def toggle_prompt_active(prompt_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
prompt = await Prompts.get_prompt_by_id(prompt_id, db=db)
|
||||
|
||||
if not prompt:
|
||||
raise HTTPException(
|
||||
@@ -508,7 +508,7 @@ async def toggle_prompt_active(prompt_id: str, user=Depends(get_verified_user),
|
||||
|
||||
if (
|
||||
prompt.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='prompt',
|
||||
resource_id=prompt.id,
|
||||
@@ -522,7 +522,7 @@ async def toggle_prompt_active(prompt_id: str, user=Depends(get_verified_user),
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
result = Prompts.toggle_prompt_active(prompt.id, db=db)
|
||||
result = await Prompts.toggle_prompt_active(prompt.id, db=db)
|
||||
if result:
|
||||
return result
|
||||
raise HTTPException(
|
||||
@@ -537,8 +537,8 @@ async def toggle_prompt_active(prompt_id: str, user=Depends(get_verified_user),
|
||||
|
||||
|
||||
@router.delete('/id/{prompt_id}/delete', response_model=bool)
|
||||
async def delete_prompt_by_id(prompt_id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
prompt = Prompts.get_prompt_by_id(prompt_id, db=db)
|
||||
async def delete_prompt_by_id(prompt_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
prompt = await Prompts.get_prompt_by_id(prompt_id, db=db)
|
||||
|
||||
if not prompt:
|
||||
raise HTTPException(
|
||||
@@ -548,7 +548,7 @@ async def delete_prompt_by_id(prompt_id: str, user=Depends(get_verified_user), d
|
||||
|
||||
if (
|
||||
prompt.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='prompt',
|
||||
resource_id=prompt.id,
|
||||
@@ -562,7 +562,7 @@ async def delete_prompt_by_id(prompt_id: str, user=Depends(get_verified_user), d
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
result = Prompts.delete_prompt_by_id(prompt.id, db=db)
|
||||
result = await Prompts.delete_prompt_by_id(prompt.id, db=db)
|
||||
return result
|
||||
|
||||
|
||||
@@ -576,12 +576,12 @@ async def get_prompt_history(
|
||||
prompt_id: str,
|
||||
page: int = 0,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""Get version history for a prompt."""
|
||||
PAGE_SIZE = 20
|
||||
|
||||
prompt = Prompts.get_prompt_by_id(prompt_id, db=db)
|
||||
prompt = await Prompts.get_prompt_by_id(prompt_id, db=db)
|
||||
|
||||
if not prompt:
|
||||
raise HTTPException(
|
||||
@@ -593,7 +593,7 @@ async def get_prompt_history(
|
||||
if not (
|
||||
user.role == 'admin'
|
||||
or prompt.user_id == user.id
|
||||
or AccessGrants.has_access(
|
||||
or await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='prompt',
|
||||
resource_id=prompt.id,
|
||||
@@ -606,7 +606,7 @@ async def get_prompt_history(
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
history = PromptHistories.get_history_by_prompt_id(prompt.id, limit=PAGE_SIZE, offset=page * PAGE_SIZE, db=db)
|
||||
history = await PromptHistories.get_history_by_prompt_id(prompt.id, limit=PAGE_SIZE, offset=page * PAGE_SIZE, db=db)
|
||||
return history
|
||||
|
||||
|
||||
@@ -615,10 +615,10 @@ async def get_prompt_history_entry(
|
||||
prompt_id: str,
|
||||
history_id: str,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""Get a specific version from history."""
|
||||
prompt = Prompts.get_prompt_by_id(prompt_id, db=db)
|
||||
prompt = await Prompts.get_prompt_by_id(prompt_id, db=db)
|
||||
|
||||
if not prompt:
|
||||
raise HTTPException(
|
||||
@@ -630,7 +630,7 @@ async def get_prompt_history_entry(
|
||||
if not (
|
||||
user.role == 'admin'
|
||||
or prompt.user_id == user.id
|
||||
or AccessGrants.has_access(
|
||||
or await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='prompt',
|
||||
resource_id=prompt.id,
|
||||
@@ -643,7 +643,7 @@ async def get_prompt_history_entry(
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
history_entry = PromptHistories.get_history_entry_by_id(history_id, db=db)
|
||||
history_entry = await PromptHistories.get_history_entry_by_id(history_id, db=db)
|
||||
if not history_entry or history_entry.prompt_id != prompt.id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -658,10 +658,10 @@ async def delete_prompt_history_entry(
|
||||
prompt_id: str,
|
||||
history_id: str,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""Delete a history entry. Cannot delete the active production version."""
|
||||
prompt = Prompts.get_prompt_by_id(prompt_id, db=db)
|
||||
prompt = await Prompts.get_prompt_by_id(prompt_id, db=db)
|
||||
|
||||
if not prompt:
|
||||
raise HTTPException(
|
||||
@@ -673,7 +673,7 @@ async def delete_prompt_history_entry(
|
||||
if not (
|
||||
user.role == 'admin'
|
||||
or prompt.user_id == user.id
|
||||
or AccessGrants.has_access(
|
||||
or await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='prompt',
|
||||
resource_id=prompt.id,
|
||||
@@ -693,7 +693,7 @@ async def delete_prompt_history_entry(
|
||||
detail='Cannot delete the active production version',
|
||||
)
|
||||
|
||||
success = PromptHistories.delete_history_entry(history_id, db=db)
|
||||
success = await PromptHistories.delete_history_entry(history_id, db=db)
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -709,10 +709,10 @@ async def get_prompt_diff(
|
||||
from_id: str,
|
||||
to_id: str,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""Get diff between two versions."""
|
||||
prompt = Prompts.get_prompt_by_id(prompt_id, db=db)
|
||||
prompt = await Prompts.get_prompt_by_id(prompt_id, db=db)
|
||||
|
||||
if not prompt:
|
||||
raise HTTPException(
|
||||
@@ -724,7 +724,7 @@ async def get_prompt_diff(
|
||||
if not (
|
||||
user.role == 'admin'
|
||||
or prompt.user_id == user.id
|
||||
or AccessGrants.has_access(
|
||||
or await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='prompt',
|
||||
resource_id=prompt.id,
|
||||
@@ -737,7 +737,7 @@ async def get_prompt_diff(
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
diff = PromptHistories.compute_diff(from_id, to_id, db=db)
|
||||
diff = await PromptHistories.compute_diff(from_id, to_id, db=db)
|
||||
if not diff:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
|
||||
@@ -40,8 +40,8 @@ from open_webui.models.files import FileModel, FileUpdateForm, Files
|
||||
from open_webui.utils.access_control.files import has_access_to_file
|
||||
from open_webui.models.knowledge import Knowledges
|
||||
from open_webui.storage.provider import Storage
|
||||
from open_webui.internal.db import get_session, get_db
|
||||
from sqlalchemy.orm import Session
|
||||
from open_webui.internal.db import get_async_session, get_db
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
|
||||
from open_webui.retrieval.vector.factory import VECTOR_DB_CLIENT
|
||||
@@ -1526,11 +1526,11 @@ class ProcessFileForm(BaseModel):
|
||||
|
||||
|
||||
@router.post('/process/file')
|
||||
def process_file(
|
||||
async def process_file(
|
||||
request: Request,
|
||||
form_data: ProcessFileForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""
|
||||
Process a file and save its content to the vector database.
|
||||
@@ -1539,9 +1539,9 @@ def process_file(
|
||||
The session is committed before external API calls, and updates use a fresh session.
|
||||
"""
|
||||
if user.role == 'admin':
|
||||
file = Files.get_file_by_id(form_data.file_id, db=db)
|
||||
file = await Files.get_file_by_id(form_data.file_id, db=db)
|
||||
else:
|
||||
file = Files.get_file_by_id_and_user_id(form_data.file_id, user.id, db=db)
|
||||
file = await Files.get_file_by_id_and_user_id(form_data.file_id, user.id, db=db)
|
||||
|
||||
if file:
|
||||
try:
|
||||
@@ -1674,7 +1674,7 @@ def process_file(
|
||||
text_content = ' '.join([doc.page_content for doc in docs])
|
||||
|
||||
log.debug(f'text_content: {text_content}')
|
||||
Files.update_file_data_by_id(
|
||||
await Files.update_file_data_by_id(
|
||||
file.id,
|
||||
{'content': text_content},
|
||||
db=db,
|
||||
@@ -1682,8 +1682,8 @@ def process_file(
|
||||
hash = calculate_sha256_string(text_content)
|
||||
|
||||
if request.app.state.config.BYPASS_EMBEDDING_AND_RETRIEVAL:
|
||||
Files.update_file_data_by_id(file.id, {'status': 'completed'}, db=db)
|
||||
Files.update_file_hash_by_id(file.id, hash, db=db)
|
||||
await Files.update_file_data_by_id(file.id, {'status': 'completed'}, db=db)
|
||||
await Files.update_file_hash_by_id(file.id, hash, db=db)
|
||||
return {
|
||||
'status': True,
|
||||
'collection_name': None,
|
||||
@@ -1715,7 +1715,7 @@ def process_file(
|
||||
if result:
|
||||
# Fresh session for the final update.
|
||||
with get_db() as session:
|
||||
Files.update_file_metadata_by_id(
|
||||
await Files.update_file_metadata_by_id(
|
||||
file.id,
|
||||
{
|
||||
'collection_name': collection_name,
|
||||
@@ -1723,12 +1723,12 @@ def process_file(
|
||||
db=session,
|
||||
)
|
||||
|
||||
Files.update_file_data_by_id(
|
||||
await Files.update_file_data_by_id(
|
||||
file.id,
|
||||
{'status': 'completed'},
|
||||
db=session,
|
||||
)
|
||||
Files.update_file_hash_by_id(file.id, hash, db=session)
|
||||
await Files.update_file_hash_by_id(file.id, hash, db=session)
|
||||
|
||||
return {
|
||||
'status': True,
|
||||
@@ -1745,13 +1745,13 @@ def process_file(
|
||||
log.exception(e)
|
||||
# Fresh session for error status update.
|
||||
with get_db() as session:
|
||||
Files.update_file_data_by_id(
|
||||
await Files.update_file_data_by_id(
|
||||
file.id,
|
||||
{'status': 'failed'},
|
||||
db=session,
|
||||
)
|
||||
# Clear the hash so the file can be re-uploaded after fixing the issue
|
||||
Files.update_file_hash_by_id(file.id, None, db=session)
|
||||
await Files.update_file_hash_by_id(file.id, None, db=session)
|
||||
|
||||
if 'No pandoc was found' in str(e):
|
||||
raise HTTPException(
|
||||
@@ -2175,7 +2175,7 @@ async def process_web_search(request: Request, form_data: SearchForm, user=Depen
|
||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||
)
|
||||
|
||||
if user.role != 'admin' and not has_permission(
|
||||
if user.role != 'admin' and not await has_permission(
|
||||
user.id, 'features.web_search', request.app.state.config.USER_PERMISSIONS
|
||||
):
|
||||
raise HTTPException(
|
||||
@@ -2327,7 +2327,7 @@ async def process_web_search(request: Request, form_data: SearchForm, user=Depen
|
||||
)
|
||||
|
||||
|
||||
def _validate_collection_access(collection_names: list[str], user) -> None:
|
||||
async def _validate_collection_access(collection_names: list[str], user) -> None:
|
||||
"""
|
||||
Prevent users from querying collections they don't own.
|
||||
Enforces ownership on user-memory-* and file-* collections.
|
||||
@@ -2344,7 +2344,7 @@ def _validate_collection_access(collection_names: list[str], user) -> None:
|
||||
)
|
||||
elif name.startswith('file-'):
|
||||
file_id = name[len('file-') :]
|
||||
if not has_access_to_file(
|
||||
if not await has_access_to_file(
|
||||
file_id=file_id,
|
||||
access_type='read',
|
||||
user=user,
|
||||
@@ -2370,7 +2370,7 @@ async def query_doc_handler(
|
||||
form_data: QueryDocForm,
|
||||
user=Depends(get_verified_user),
|
||||
):
|
||||
_validate_collection_access([form_data.collection_name], user)
|
||||
await _validate_collection_access([form_data.collection_name], user)
|
||||
|
||||
try:
|
||||
if request.app.state.config.ENABLE_RAG_HYBRID_SEARCH and (form_data.hybrid is None or form_data.hybrid):
|
||||
@@ -2435,7 +2435,7 @@ async def query_collection_handler(
|
||||
form_data: QueryCollectionsForm,
|
||||
user=Depends(get_verified_user),
|
||||
):
|
||||
_validate_collection_access(form_data.collection_names, user)
|
||||
await _validate_collection_access(form_data.collection_names, user)
|
||||
|
||||
try:
|
||||
if request.app.state.config.ENABLE_RAG_HYBRID_SEARCH and (form_data.hybrid is None or form_data.hybrid):
|
||||
@@ -2496,14 +2496,14 @@ class DeleteForm(BaseModel):
|
||||
|
||||
|
||||
@router.post('/delete')
|
||||
def delete_entries_from_collection(
|
||||
async def delete_entries_from_collection(
|
||||
form_data: DeleteForm,
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
try:
|
||||
if VECTOR_DB_CLIENT.has_collection(collection_name=form_data.collection_name):
|
||||
file = Files.get_file_by_id(form_data.file_id, db=db)
|
||||
file = await Files.get_file_by_id(form_data.file_id, db=db)
|
||||
if not file:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -2524,13 +2524,13 @@ def delete_entries_from_collection(
|
||||
|
||||
|
||||
@router.post('/reset/db')
|
||||
def reset_vector_db(user=Depends(get_admin_user), db: Session = Depends(get_session)):
|
||||
async def reset_vector_db(user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
|
||||
VECTOR_DB_CLIENT.reset()
|
||||
Knowledges.delete_all_knowledge(db=db)
|
||||
await Knowledges.delete_all_knowledge(db=db)
|
||||
|
||||
|
||||
@router.post('/reset/uploads')
|
||||
def reset_upload_dir(user=Depends(get_admin_user)) -> bool:
|
||||
async def reset_upload_dir(user=Depends(get_admin_user)) -> bool:
|
||||
folder = f'{UPLOAD_DIR}'
|
||||
try:
|
||||
# Check if the directory exists
|
||||
@@ -2585,7 +2585,7 @@ async def process_files_batch(
|
||||
"""
|
||||
Process a batch of files and save them to the vector database.
|
||||
|
||||
NOTE: We intentionally do NOT use Depends(get_session) here.
|
||||
NOTE: We intentionally do NOT use Depends(get_async_session) here.
|
||||
The save_docs_to_vector_db() call makes external embedding API calls which
|
||||
can take 5-60+ seconds for batch operations. Database operations after
|
||||
embedding (Files.update_file_by_id) manage their own short-lived sessions.
|
||||
@@ -2603,7 +2603,7 @@ async def process_files_batch(
|
||||
for file in form_data.files:
|
||||
try:
|
||||
# Ownership check: verify the requesting user owns the file or is an admin
|
||||
db_file = Files.get_file_by_id(file.id, db=db)
|
||||
db_file = await Files.get_file_by_id(file.id, db=db)
|
||||
if not db_file:
|
||||
file_errors.append(
|
||||
BatchProcessFilesResult(
|
||||
@@ -2665,7 +2665,7 @@ async def process_files_batch(
|
||||
|
||||
# Update all files with collection name
|
||||
for file_update, file_result in zip(file_updates, file_results):
|
||||
Files.update_file_by_id(id=file_result.file_id, form_data=file_update, db=db)
|
||||
await Files.update_file_by_id(id=file_result.file_id, form_data=file_update, db=db)
|
||||
file_result.status = 'completed'
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -30,8 +30,8 @@ from open_webui.config import OAUTH_PROVIDERS
|
||||
from open_webui.env import SCIM_AUTH_PROVIDER
|
||||
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from open_webui.internal.db import get_session
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from open_webui.internal.db import get_async_session
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -326,18 +326,18 @@ def get_scim_provider() -> str:
|
||||
return SCIM_AUTH_PROVIDER
|
||||
|
||||
|
||||
def find_user_by_external_id(external_id: str, db=None) -> Optional[UserModel]:
|
||||
async def find_user_by_external_id(external_id: str, db=None) -> Optional[UserModel]:
|
||||
"""Find a user by SCIM externalId, falling back to OAuth sub match."""
|
||||
provider = get_scim_provider()
|
||||
user = Users.get_user_by_scim_external_id(provider, external_id, db=db)
|
||||
user = await Users.get_user_by_scim_external_id(provider, external_id, db=db)
|
||||
if user:
|
||||
return user
|
||||
|
||||
# Fallback: check if externalId matches an existing OAuth sub (account linking)
|
||||
return Users.get_user_by_oauth_sub(provider, external_id, db=db)
|
||||
return await Users.get_user_by_oauth_sub(provider, external_id, db=db)
|
||||
|
||||
|
||||
def user_to_scim(user: UserModel, request: Request, db=None) -> SCIMUser:
|
||||
async def user_to_scim(user: UserModel, request: Request, db=None) -> SCIMUser:
|
||||
"""Convert internal User model to SCIM User"""
|
||||
# Parse display name into name components
|
||||
name_parts = user.name.split(' ', 1) if user.name else ['', '']
|
||||
@@ -345,7 +345,7 @@ def user_to_scim(user: UserModel, request: Request, db=None) -> SCIMUser:
|
||||
family_name = name_parts[1] if len(name_parts) > 1 else ''
|
||||
|
||||
# Get user's groups
|
||||
user_groups = Groups.get_groups_by_member_id(user.id, db=db)
|
||||
user_groups = await Groups.get_groups_by_member_id(user.id, db=db)
|
||||
groups = [
|
||||
{
|
||||
'value': group.id,
|
||||
@@ -379,12 +379,12 @@ def user_to_scim(user: UserModel, request: Request, db=None) -> SCIMUser:
|
||||
)
|
||||
|
||||
|
||||
def group_to_scim(group: GroupModel, request: Request, db=None) -> SCIMGroup:
|
||||
async def group_to_scim(group: GroupModel, request: Request, db=None) -> SCIMGroup:
|
||||
"""Convert internal Group model to SCIM Group"""
|
||||
member_ids = Groups.get_group_user_ids_by_id(group.id, db) or []
|
||||
member_ids = await Groups.get_group_user_ids_by_id(group.id, db) or []
|
||||
|
||||
# Batch-fetch all users to avoid N+1 queries
|
||||
users = Users.get_users_by_user_ids(member_ids, db=db) if member_ids else []
|
||||
users = await Users.get_users_by_user_ids(member_ids, db=db) if member_ids else []
|
||||
members = [
|
||||
SCIMGroupMember(
|
||||
value=user.id,
|
||||
@@ -512,7 +512,7 @@ async def get_users(
|
||||
count: int = Query(20),
|
||||
filter: Optional[str] = None,
|
||||
_: bool = Depends(get_scim_auth),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""List SCIM Users"""
|
||||
# Clamp per SCIM 2.0 spec (RFC 7644 §3.4.2.4):
|
||||
@@ -527,25 +527,25 @@ async def get_users(
|
||||
# Simple filter parsing - supports userName eq, externalId eq
|
||||
if 'userName eq' in filter:
|
||||
email = filter.split('"')[1]
|
||||
user = Users.get_user_by_email(email, db=db)
|
||||
user = await Users.get_user_by_email(email, db=db)
|
||||
users_list = [user] if user else []
|
||||
total = 1 if user else 0
|
||||
elif 'externalId eq' in filter:
|
||||
external_id = filter.split('"')[1]
|
||||
user = find_user_by_external_id(external_id, db=db)
|
||||
user = await find_user_by_external_id(external_id, db=db)
|
||||
users_list = [user] if user else []
|
||||
total = 1 if user else 0
|
||||
else:
|
||||
response = Users.get_users(skip=skip, limit=limit, db=db)
|
||||
response = await Users.get_users(skip=skip, limit=limit, db=db)
|
||||
users_list = response['users']
|
||||
total = response['total']
|
||||
else:
|
||||
response = Users.get_users(skip=skip, limit=limit, db=db)
|
||||
response = await Users.get_users(skip=skip, limit=limit, db=db)
|
||||
users_list = response['users']
|
||||
total = response['total']
|
||||
|
||||
# Convert to SCIM format
|
||||
scim_users = [user_to_scim(user, request, db=db) for user in users_list]
|
||||
scim_users = [await user_to_scim(user, request, db=db) for user in users_list]
|
||||
|
||||
return SCIMListResponse(
|
||||
totalResults=total,
|
||||
@@ -560,14 +560,14 @@ async def get_user(
|
||||
user_id: str,
|
||||
request: Request,
|
||||
_: bool = Depends(get_scim_auth),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""Get SCIM User by ID"""
|
||||
user = Users.get_user_by_id(user_id, db=db)
|
||||
user = await Users.get_user_by_id(user_id, db=db)
|
||||
if not user:
|
||||
return scim_error(status_code=status.HTTP_404_NOT_FOUND, detail=f'User {user_id} not found')
|
||||
|
||||
return user_to_scim(user, request, db=db)
|
||||
return await user_to_scim(user, request, db=db)
|
||||
|
||||
|
||||
@router.post('/Users', response_model=SCIMUser, status_code=status.HTTP_201_CREATED)
|
||||
@@ -575,12 +575,12 @@ async def create_user(
|
||||
request: Request,
|
||||
user_data: SCIMUserCreateRequest,
|
||||
_: bool = Depends(get_scim_auth),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""Create SCIM User"""
|
||||
# Check for duplicate by externalId
|
||||
if user_data.externalId:
|
||||
existing_user = find_user_by_external_id(user_data.externalId, db=db)
|
||||
existing_user = await find_user_by_external_id(user_data.externalId, db=db)
|
||||
if existing_user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
@@ -596,7 +596,7 @@ async def create_user(
|
||||
email = email.lower()
|
||||
|
||||
# Check for duplicate by email
|
||||
existing_user = Users.get_user_by_email(email, db=db)
|
||||
existing_user = await Users.get_user_by_email(email, db=db)
|
||||
if existing_user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
@@ -619,7 +619,7 @@ async def create_user(
|
||||
if user_data.photos and len(user_data.photos) > 0:
|
||||
profile_image = user_data.photos[0].value
|
||||
|
||||
new_user = Users.insert_new_user(
|
||||
new_user = await Users.insert_new_user(
|
||||
id=user_id,
|
||||
name=name,
|
||||
email=email,
|
||||
@@ -637,10 +637,10 @@ async def create_user(
|
||||
# Store externalId in the scim field
|
||||
if user_data.externalId:
|
||||
provider = get_scim_provider()
|
||||
Users.update_user_scim_by_id(user_id, provider, user_data.externalId, db=db)
|
||||
new_user = Users.get_user_by_id(user_id, db=db)
|
||||
await Users.update_user_scim_by_id(user_id, provider, user_data.externalId, db=db)
|
||||
new_user = await Users.get_user_by_id(user_id, db=db)
|
||||
|
||||
return user_to_scim(new_user, request, db=db)
|
||||
return await user_to_scim(new_user, request, db=db)
|
||||
|
||||
|
||||
@router.put('/Users/{user_id}', response_model=SCIMUser)
|
||||
@@ -649,10 +649,10 @@ async def update_user(
|
||||
request: Request,
|
||||
user_data: SCIMUserUpdateRequest,
|
||||
_: bool = Depends(get_scim_auth),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""Update SCIM User (full update)"""
|
||||
user = Users.get_user_by_id(user_id, db=db)
|
||||
user = await Users.get_user_by_id(user_id, db=db)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -682,7 +682,7 @@ async def update_user(
|
||||
if user_data.photos and len(user_data.photos) > 0:
|
||||
update_data['profile_image_url'] = user_data.photos[0].value
|
||||
|
||||
updated_user = Users.update_user_by_id(user_id, update_data, db=db)
|
||||
updated_user = await Users.update_user_by_id(user_id, update_data, db=db)
|
||||
if not updated_user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -692,10 +692,10 @@ async def update_user(
|
||||
# Update externalId in the scim field
|
||||
if user_data.externalId:
|
||||
provider = get_scim_provider()
|
||||
Users.update_user_scim_by_id(user_id, provider, user_data.externalId, db=db)
|
||||
updated_user = Users.get_user_by_id(user_id, db=db)
|
||||
await Users.update_user_scim_by_id(user_id, provider, user_data.externalId, db=db)
|
||||
updated_user = await Users.get_user_by_id(user_id, db=db)
|
||||
|
||||
return user_to_scim(updated_user, request, db=db)
|
||||
return await user_to_scim(updated_user, request, db=db)
|
||||
|
||||
|
||||
@router.patch('/Users/{user_id}', response_model=SCIMUser)
|
||||
@@ -704,10 +704,10 @@ async def patch_user(
|
||||
request: Request,
|
||||
patch_data: SCIMPatchRequest,
|
||||
_: bool = Depends(get_scim_auth),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""Update SCIM User (partial update)"""
|
||||
user = Users.get_user_by_id(user_id, db=db)
|
||||
user = await Users.get_user_by_id(user_id, db=db)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -734,11 +734,11 @@ async def patch_user(
|
||||
update_data['name'] = value
|
||||
elif path == 'externalId':
|
||||
provider = get_scim_provider()
|
||||
Users.update_user_scim_by_id(user_id, provider, value, db=db)
|
||||
await Users.update_user_scim_by_id(user_id, provider, value, db=db)
|
||||
|
||||
# Update user
|
||||
if update_data:
|
||||
updated_user = Users.update_user_by_id(user_id, update_data, db=db)
|
||||
updated_user = await Users.update_user_by_id(user_id, update_data, db=db)
|
||||
if not updated_user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -747,7 +747,7 @@ async def patch_user(
|
||||
else:
|
||||
updated_user = user
|
||||
|
||||
return user_to_scim(updated_user, request, db=db)
|
||||
return await user_to_scim(updated_user, request, db=db)
|
||||
|
||||
|
||||
@router.delete('/Users/{user_id}', status_code=status.HTTP_204_NO_CONTENT)
|
||||
@@ -755,17 +755,17 @@ async def delete_user(
|
||||
user_id: str,
|
||||
request: Request,
|
||||
_: bool = Depends(get_scim_auth),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""Delete SCIM User"""
|
||||
user = Users.get_user_by_id(user_id, db=db)
|
||||
user = await Users.get_user_by_id(user_id, db=db)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f'User {user_id} not found',
|
||||
)
|
||||
|
||||
success = Users.delete_user_by_id(user_id, db=db)
|
||||
success = await Users.delete_user_by_id(user_id, db=db)
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -783,7 +783,7 @@ async def get_groups(
|
||||
count: int = Query(20),
|
||||
filter: Optional[str] = None,
|
||||
_: bool = Depends(get_scim_auth),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""List SCIM Groups"""
|
||||
# Clamp per SCIM 2.0 spec (RFC 7644 §3.4.2.4):
|
||||
@@ -795,13 +795,13 @@ async def get_groups(
|
||||
if filter:
|
||||
if 'displayName eq' in filter:
|
||||
display_name = filter.split('"')[1]
|
||||
group = Groups.get_group_by_name(display_name, db=db)
|
||||
group = await Groups.get_group_by_name(display_name, db=db)
|
||||
groups_list = [group] if group else []
|
||||
else:
|
||||
# Unrecognized filter — fall back to all groups
|
||||
groups_list = Groups.get_all_groups(db=db)
|
||||
groups_list = await Groups.get_all_groups(db=db)
|
||||
else:
|
||||
groups_list = Groups.get_all_groups(db=db)
|
||||
groups_list = await Groups.get_all_groups(db=db)
|
||||
|
||||
# Apply pagination
|
||||
total = len(groups_list)
|
||||
@@ -810,7 +810,7 @@ async def get_groups(
|
||||
paginated_groups = groups_list[start:end]
|
||||
|
||||
# Convert to SCIM format
|
||||
scim_groups = [group_to_scim(group, request, db=db) for group in paginated_groups]
|
||||
scim_groups = [await group_to_scim(group, request, db=db) for group in paginated_groups]
|
||||
|
||||
return SCIMListResponse(
|
||||
totalResults=total,
|
||||
@@ -825,17 +825,17 @@ async def get_group(
|
||||
group_id: str,
|
||||
request: Request,
|
||||
_: bool = Depends(get_scim_auth),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""Get SCIM Group by ID"""
|
||||
group = Groups.get_group_by_id(group_id, db=db)
|
||||
group = await Groups.get_group_by_id(group_id, db=db)
|
||||
if not group:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f'Group {group_id} not found',
|
||||
)
|
||||
|
||||
return group_to_scim(group, request, db=db)
|
||||
return await group_to_scim(group, request, db=db)
|
||||
|
||||
|
||||
@router.post('/Groups', response_model=SCIMGroup, status_code=status.HTTP_201_CREATED)
|
||||
@@ -843,7 +843,7 @@ async def create_group(
|
||||
request: Request,
|
||||
group_data: SCIMGroupCreateRequest,
|
||||
_: bool = Depends(get_scim_auth),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""Create SCIM Group"""
|
||||
# Extract member IDs
|
||||
@@ -861,14 +861,14 @@ async def create_group(
|
||||
)
|
||||
|
||||
# Need to get the creating user's ID - we'll use the first admin
|
||||
admin_user = Users.get_super_admin_user(db=db)
|
||||
admin_user = await Users.get_super_admin_user(db=db)
|
||||
if not admin_user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail='No admin user found',
|
||||
)
|
||||
|
||||
new_group = Groups.insert_new_group(admin_user.id, form, db=db)
|
||||
new_group = await Groups.insert_new_group(admin_user.id, form, db=db)
|
||||
if not new_group:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
@@ -884,12 +884,12 @@ async def create_group(
|
||||
description=new_group.description,
|
||||
)
|
||||
|
||||
Groups.update_group_by_id(new_group.id, update_form, db=db)
|
||||
Groups.set_group_user_ids_by_id(new_group.id, member_ids, db=db)
|
||||
await Groups.update_group_by_id(new_group.id, update_form, db=db)
|
||||
await Groups.set_group_user_ids_by_id(new_group.id, member_ids, db=db)
|
||||
|
||||
new_group = Groups.get_group_by_id(new_group.id, db=db)
|
||||
new_group = await Groups.get_group_by_id(new_group.id, db=db)
|
||||
|
||||
return group_to_scim(new_group, request, db=db)
|
||||
return await group_to_scim(new_group, request, db=db)
|
||||
|
||||
|
||||
@router.put('/Groups/{group_id}', response_model=SCIMGroup)
|
||||
@@ -898,10 +898,10 @@ async def update_group(
|
||||
request: Request,
|
||||
group_data: SCIMGroupUpdateRequest,
|
||||
_: bool = Depends(get_scim_auth),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""Update SCIM Group (full update)"""
|
||||
group = Groups.get_group_by_id(group_id, db=db)
|
||||
group = await Groups.get_group_by_id(group_id, db=db)
|
||||
if not group:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -919,17 +919,17 @@ async def update_group(
|
||||
# Handle members if provided
|
||||
if group_data.members is not None:
|
||||
member_ids = [member.value for member in group_data.members]
|
||||
Groups.set_group_user_ids_by_id(group_id, member_ids, db=db)
|
||||
await Groups.set_group_user_ids_by_id(group_id, member_ids, db=db)
|
||||
|
||||
# Update group
|
||||
updated_group = Groups.update_group_by_id(group_id, update_form, db=db)
|
||||
updated_group = await Groups.update_group_by_id(group_id, update_form, db=db)
|
||||
if not updated_group:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail='Failed to update group',
|
||||
)
|
||||
|
||||
return group_to_scim(updated_group, request, db=db)
|
||||
return await group_to_scim(updated_group, request, db=db)
|
||||
|
||||
|
||||
@router.patch('/Groups/{group_id}', response_model=SCIMGroup)
|
||||
@@ -938,10 +938,10 @@ async def patch_group(
|
||||
request: Request,
|
||||
patch_data: SCIMPatchRequest,
|
||||
_: bool = Depends(get_scim_auth),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""Update SCIM Group (partial update)"""
|
||||
group = Groups.get_group_by_id(group_id, db=db)
|
||||
group = await Groups.get_group_by_id(group_id, db=db)
|
||||
if not group:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -965,7 +965,7 @@ async def patch_group(
|
||||
update_form.name = value
|
||||
elif path == 'members':
|
||||
# Replace all members
|
||||
Groups.set_group_user_ids_by_id(group_id, [member['value'] for member in value], db=db)
|
||||
await Groups.set_group_user_ids_by_id(group_id, [member['value'] for member in value], db=db)
|
||||
|
||||
elif op == 'add':
|
||||
if path == 'members':
|
||||
@@ -973,22 +973,22 @@ async def patch_group(
|
||||
if isinstance(value, list):
|
||||
for member in value:
|
||||
if isinstance(member, dict) and 'value' in member:
|
||||
Groups.add_users_to_group(group_id, [member['value']], db=db)
|
||||
await Groups.add_users_to_group(group_id, [member['value']], db=db)
|
||||
elif op == 'remove':
|
||||
if path and path.startswith('members[value eq'):
|
||||
# Remove specific member
|
||||
member_id = path.split('"')[1]
|
||||
Groups.remove_users_from_group(group_id, [member_id], db=db)
|
||||
await Groups.remove_users_from_group(group_id, [member_id], db=db)
|
||||
|
||||
# Update group
|
||||
updated_group = Groups.update_group_by_id(group_id, update_form, db=db)
|
||||
updated_group = await Groups.update_group_by_id(group_id, update_form, db=db)
|
||||
if not updated_group:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail='Failed to update group',
|
||||
)
|
||||
|
||||
return group_to_scim(updated_group, request, db=db)
|
||||
return await group_to_scim(updated_group, request, db=db)
|
||||
|
||||
|
||||
@router.delete('/Groups/{group_id}', status_code=status.HTTP_204_NO_CONTENT)
|
||||
@@ -996,17 +996,17 @@ async def delete_group(
|
||||
group_id: str,
|
||||
request: Request,
|
||||
_: bool = Depends(get_scim_auth),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
"""Delete SCIM Group"""
|
||||
group = Groups.get_group_by_id(group_id, db=db)
|
||||
group = await Groups.get_group_by_id(group_id, db=db)
|
||||
if not group:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f'Group {group_id} not found',
|
||||
)
|
||||
|
||||
success = Groups.delete_group_by_id(group_id, db=db)
|
||||
success = await Groups.delete_group_by_id(group_id, db=db)
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
|
||||
@@ -5,9 +5,9 @@ from open_webui.models.groups import Groups
|
||||
from pydantic import BaseModel
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from open_webui.internal.db import get_session
|
||||
from open_webui.internal.db import get_async_session
|
||||
from open_webui.models.skills import (
|
||||
SkillForm,
|
||||
SkillModel,
|
||||
@@ -40,18 +40,18 @@ router = APIRouter()
|
||||
async def get_skills(
|
||||
request: Request,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL:
|
||||
skills = Skills.get_skills(db=db)
|
||||
skills = await Skills.get_skills(db=db)
|
||||
else:
|
||||
user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user.id, db=db)}
|
||||
all_skills = Skills.get_skills(db=db)
|
||||
user_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user.id, db=db)}
|
||||
all_skills = await Skills.get_skills(db=db)
|
||||
skills = [
|
||||
skill
|
||||
for skill in all_skills
|
||||
if skill.user_id == user.id
|
||||
or AccessGrants.has_access(
|
||||
or await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='skill',
|
||||
resource_id=skill.id,
|
||||
@@ -75,7 +75,7 @@ async def get_skill_list(
|
||||
view_option: Optional[str] = None,
|
||||
page: Optional[int] = 1,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
limit = PAGE_ITEM_COUNT
|
||||
|
||||
@@ -89,13 +89,13 @@ async def get_skill_list(
|
||||
filter['view_option'] = view_option
|
||||
|
||||
if not (user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL):
|
||||
groups = Groups.get_groups_by_member_id(user.id, db=db)
|
||||
groups = await Groups.get_groups_by_member_id(user.id, db=db)
|
||||
if groups:
|
||||
filter['group_ids'] = [group.id for group in groups]
|
||||
|
||||
filter['user_id'] = user.id
|
||||
|
||||
result = Skills.search_skills(user.id, filter=filter, skip=skip, limit=limit, db=db)
|
||||
result = await Skills.search_skills(user.id, filter=filter, skip=skip, limit=limit, db=db)
|
||||
|
||||
return SkillAccessListResponse(
|
||||
items=[
|
||||
@@ -104,7 +104,7 @@ async def get_skill_list(
|
||||
write_access=(
|
||||
(user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL)
|
||||
or user.id == skill.user_id
|
||||
or AccessGrants.has_access(
|
||||
or await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='skill',
|
||||
resource_id=skill.id,
|
||||
@@ -128,9 +128,9 @@ async def get_skill_list(
|
||||
async def export_skills(
|
||||
request: Request,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if user.role != 'admin' and not has_permission(
|
||||
if user.role != 'admin' and not await has_permission(
|
||||
user.id,
|
||||
'workspace.skills',
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
@@ -142,9 +142,9 @@ async def export_skills(
|
||||
)
|
||||
|
||||
if user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL:
|
||||
return Skills.get_skills(db=db)
|
||||
return await Skills.get_skills(db=db)
|
||||
else:
|
||||
return Skills.get_skills_by_user_id(user.id, 'read', db=db)
|
||||
return await Skills.get_skills_by_user_id(user.id, 'read', db=db)
|
||||
|
||||
|
||||
############################
|
||||
@@ -157,9 +157,9 @@ async def create_new_skill(
|
||||
request: Request,
|
||||
form_data: SkillForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if user.role != 'admin' and not has_permission(
|
||||
if user.role != 'admin' and not await has_permission(
|
||||
user.id, 'workspace.skills', request.app.state.config.USER_PERMISSIONS, db=db
|
||||
):
|
||||
raise HTTPException(
|
||||
@@ -169,7 +169,7 @@ async def create_new_skill(
|
||||
|
||||
form_data.id = form_data.id.lower().replace(' ', '-')
|
||||
|
||||
existing = Skills.get_skill_by_id(form_data.id, db=db)
|
||||
existing = await Skills.get_skill_by_id(form_data.id, db=db)
|
||||
if existing is not None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
@@ -177,7 +177,7 @@ async def create_new_skill(
|
||||
)
|
||||
|
||||
try:
|
||||
skill = Skills.insert_new_skill(user.id, form_data, db=db)
|
||||
skill = await Skills.insert_new_skill(user.id, form_data, db=db)
|
||||
if skill:
|
||||
return skill
|
||||
else:
|
||||
@@ -199,14 +199,14 @@ async def create_new_skill(
|
||||
|
||||
|
||||
@router.get('/id/{id}', response_model=Optional[SkillAccessResponse])
|
||||
async def get_skill_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
skill = Skills.get_skill_by_id(id, db=db)
|
||||
async def get_skill_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
skill = await Skills.get_skill_by_id(id, db=db)
|
||||
|
||||
if skill:
|
||||
if (
|
||||
user.role == 'admin'
|
||||
or skill.user_id == user.id
|
||||
or AccessGrants.has_access(
|
||||
or await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='skill',
|
||||
resource_id=skill.id,
|
||||
@@ -219,7 +219,7 @@ async def get_skill_by_id(id: str, user=Depends(get_verified_user), db: Session
|
||||
write_access=(
|
||||
(user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL)
|
||||
or user.id == skill.user_id
|
||||
or AccessGrants.has_access(
|
||||
or await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='skill',
|
||||
resource_id=skill.id,
|
||||
@@ -251,9 +251,9 @@ async def update_skill_by_id(
|
||||
id: str,
|
||||
form_data: SkillForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
skill = Skills.get_skill_by_id(id, db=db)
|
||||
skill = await Skills.get_skill_by_id(id, db=db)
|
||||
if not skill:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -262,7 +262,7 @@ async def update_skill_by_id(
|
||||
|
||||
if (
|
||||
skill.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='skill',
|
||||
resource_id=skill.id,
|
||||
@@ -281,7 +281,7 @@ async def update_skill_by_id(
|
||||
**form_data.model_dump(exclude={'id'}),
|
||||
}
|
||||
|
||||
skill = Skills.update_skill_by_id(id, updated, db=db)
|
||||
skill = await Skills.update_skill_by_id(id, updated, db=db)
|
||||
|
||||
if skill:
|
||||
return skill
|
||||
@@ -312,9 +312,9 @@ async def update_skill_access_by_id(
|
||||
id: str,
|
||||
form_data: SkillAccessGrantsForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
skill = Skills.get_skill_by_id(id, db=db)
|
||||
skill = await Skills.get_skill_by_id(id, db=db)
|
||||
if not skill:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -323,7 +323,7 @@ async def update_skill_access_by_id(
|
||||
|
||||
if (
|
||||
skill.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='skill',
|
||||
resource_id=skill.id,
|
||||
@@ -337,7 +337,7 @@ async def update_skill_access_by_id(
|
||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||
)
|
||||
|
||||
form_data.access_grants = filter_allowed_access_grants(
|
||||
form_data.access_grants = await filter_allowed_access_grants(
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
user.id,
|
||||
user.role,
|
||||
@@ -345,9 +345,9 @@ async def update_skill_access_by_id(
|
||||
'sharing.public_skills',
|
||||
)
|
||||
|
||||
AccessGrants.set_access_grants('skill', id, form_data.access_grants, db=db)
|
||||
await AccessGrants.set_access_grants('skill', id, form_data.access_grants, db=db)
|
||||
|
||||
return Skills.get_skill_by_id(id, db=db)
|
||||
return await Skills.get_skill_by_id(id, db=db)
|
||||
|
||||
|
||||
############################
|
||||
@@ -356,13 +356,13 @@ async def update_skill_access_by_id(
|
||||
|
||||
|
||||
@router.post('/id/{id}/toggle', response_model=Optional[SkillModel])
|
||||
async def toggle_skill_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
skill = Skills.get_skill_by_id(id, db=db)
|
||||
async def toggle_skill_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
skill = await Skills.get_skill_by_id(id, db=db)
|
||||
if skill:
|
||||
if (
|
||||
user.role == 'admin'
|
||||
or skill.user_id == user.id
|
||||
or AccessGrants.has_access(
|
||||
or await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='skill',
|
||||
resource_id=skill.id,
|
||||
@@ -370,7 +370,7 @@ async def toggle_skill_by_id(id: str, user=Depends(get_verified_user), db: Sessi
|
||||
db=db,
|
||||
)
|
||||
):
|
||||
skill = Skills.toggle_skill_by_id(id, db=db)
|
||||
skill = await Skills.toggle_skill_by_id(id, db=db)
|
||||
|
||||
if skill:
|
||||
return skill
|
||||
@@ -401,9 +401,9 @@ async def delete_skill_by_id(
|
||||
request: Request,
|
||||
id: str,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
skill = Skills.get_skill_by_id(id, db=db)
|
||||
skill = await Skills.get_skill_by_id(id, db=db)
|
||||
if not skill:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -412,7 +412,7 @@ async def delete_skill_by_id(
|
||||
|
||||
if (
|
||||
skill.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='skill',
|
||||
resource_id=skill.id,
|
||||
@@ -426,5 +426,5 @@ async def delete_skill_by_id(
|
||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||
)
|
||||
|
||||
result = Skills.delete_skill_by_id(id, db=db)
|
||||
result = await Skills.delete_skill_by_id(id, db=db)
|
||||
return result
|
||||
|
||||
@@ -52,7 +52,7 @@ def _sanitize_proxy_path(path: str) -> str | None:
|
||||
async def list_terminal_servers(request: Request, user=Depends(get_verified_user)):
|
||||
"""Return terminal servers the authenticated user has access to."""
|
||||
connections = request.app.state.config.TERMINAL_SERVER_CONNECTIONS or []
|
||||
user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user.id)}
|
||||
user_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user.id)}
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -61,7 +61,7 @@ async def list_terminal_servers(request: Request, user=Depends(get_verified_user
|
||||
'name': connection.get('name', ''),
|
||||
}
|
||||
for connection in connections
|
||||
if connection.get('enabled', True) and has_connection_access(user, connection, user_group_ids)
|
||||
if connection.get('enabled', True) and await has_connection_access(user, connection, user_group_ids)
|
||||
]
|
||||
|
||||
|
||||
@@ -82,8 +82,8 @@ async def proxy_terminal(
|
||||
if connection is None:
|
||||
return JSONResponse({'error': f"Terminal server '{server_id}' not found"}, status_code=404)
|
||||
|
||||
user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user.id)}
|
||||
if not has_connection_access(user, connection, user_group_ids):
|
||||
user_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user.id)}
|
||||
if not await has_connection_access(user, connection, user_group_ids):
|
||||
return JSONResponse({'error': 'Access denied'}, status_code=403)
|
||||
|
||||
base_url = (connection.get('url') or '').rstrip('/')
|
||||
@@ -208,7 +208,7 @@ async def _resolve_authenticated_connection(ws: WebSocket, server_id: str):
|
||||
if data is None or 'id' not in data:
|
||||
await ws.close(code=4001, reason='Invalid token')
|
||||
return None
|
||||
user = Users.get_user_by_id(data['id'])
|
||||
user = await Users.get_user_by_id(data['id'])
|
||||
if user is None:
|
||||
await ws.close(code=4001, reason='User not found')
|
||||
return None
|
||||
@@ -227,8 +227,8 @@ async def _resolve_authenticated_connection(ws: WebSocket, server_id: str):
|
||||
await ws.close(code=4004, reason='Terminal server not found')
|
||||
return None
|
||||
|
||||
user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user.id)}
|
||||
if not has_connection_access(user, connection, user_group_ids):
|
||||
user_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user.id)}
|
||||
if not await has_connection_access(user, connection, user_group_ids):
|
||||
await ws.close(code=4003, reason='Access denied')
|
||||
return None
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ from open_webui.env import AIOHTTP_CLIENT_TIMEOUT
|
||||
from open_webui.models.groups import Groups
|
||||
from pydantic import BaseModel, HttpUrl
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
from sqlalchemy.orm import Session
|
||||
from open_webui.internal.db import get_session
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from open_webui.internal.db import get_async_session
|
||||
|
||||
|
||||
from open_webui.models.oauth_sessions import OAuthSessions
|
||||
@@ -46,11 +46,11 @@ log = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def get_tool_module(request, tool_id, load_from_db=True):
|
||||
async def get_tool_module(request, tool_id, load_from_db=True):
|
||||
"""
|
||||
Get the tool module by its ID.
|
||||
"""
|
||||
tool_module, _ = get_tool_module_from_cache(request, tool_id, load_from_db)
|
||||
tool_module, _ = await get_tool_module_from_cache(request, tool_id, load_from_db)
|
||||
return tool_module
|
||||
|
||||
|
||||
@@ -65,12 +65,12 @@ def get_tool_module(request, tool_id, load_from_db=True):
|
||||
async def get_tools(
|
||||
request: Request,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
tools = []
|
||||
|
||||
# Local Tools
|
||||
for tool in Tools.get_tools(defer_content=True, db=db):
|
||||
for tool in await Tools.get_tools(defer_content=True, db=db):
|
||||
tool_module = request.app.state.TOOLS.get(tool.id) if hasattr(request.app.state, 'TOOLS') else None
|
||||
tools.append(
|
||||
ToolUserResponse(
|
||||
@@ -159,7 +159,7 @@ async def get_tools(
|
||||
# Admin can see all tools
|
||||
return tools
|
||||
else:
|
||||
user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user.id, db=db)}
|
||||
user_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user.id, db=db)}
|
||||
tools = [
|
||||
tool
|
||||
for tool in tools
|
||||
@@ -173,7 +173,7 @@ async def get_tools(
|
||||
db=db,
|
||||
)
|
||||
if str(tool.id).startswith('server:')
|
||||
else AccessGrants.has_access(
|
||||
else await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='tool',
|
||||
resource_id=tool.id,
|
||||
@@ -192,13 +192,13 @@ async def get_tools(
|
||||
|
||||
|
||||
@router.get('/list', response_model=list[ToolAccessResponse])
|
||||
async def get_tool_list(user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
async def get_tool_list(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
if user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL:
|
||||
tools = Tools.get_tools(defer_content=True, db=db)
|
||||
tools = await Tools.get_tools(defer_content=True, db=db)
|
||||
else:
|
||||
tools = Tools.get_tools_by_user_id(user.id, 'read', defer_content=True, db=db)
|
||||
tools = await Tools.get_tools_by_user_id(user.id, 'read', defer_content=True, db=db)
|
||||
|
||||
user_group_ids = {group.id for group in Groups.get_groups_by_member_id(user.id, db=db)}
|
||||
user_group_ids = {group.id for group in await Groups.get_groups_by_member_id(user.id, db=db)}
|
||||
|
||||
result = []
|
||||
for tool in tools:
|
||||
@@ -298,9 +298,9 @@ async def load_tool_from_url(request: Request, form_data: LoadUrlForm, user=Depe
|
||||
async def export_tools(
|
||||
request: Request,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if user.role != 'admin' and not has_permission(
|
||||
if user.role != 'admin' and not await has_permission(
|
||||
user.id,
|
||||
'workspace.tools_export',
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
@@ -312,9 +312,9 @@ async def export_tools(
|
||||
)
|
||||
|
||||
if user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL:
|
||||
return Tools.get_tools(db=db)
|
||||
return await Tools.get_tools(db=db)
|
||||
else:
|
||||
return Tools.get_tools_by_user_id(user.id, 'read', db=db)
|
||||
return await Tools.get_tools_by_user_id(user.id, 'read', db=db)
|
||||
|
||||
|
||||
############################
|
||||
@@ -327,11 +327,11 @@ async def create_new_tools(
|
||||
request: Request,
|
||||
form_data: ToolForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if user.role != 'admin' and not (
|
||||
has_permission(user.id, 'workspace.tools', request.app.state.config.USER_PERMISSIONS, db=db)
|
||||
or has_permission(
|
||||
await has_permission(user.id, 'workspace.tools', request.app.state.config.USER_PERMISSIONS, db=db)
|
||||
or await has_permission(
|
||||
user.id,
|
||||
'workspace.tools_import',
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
@@ -351,10 +351,10 @@ async def create_new_tools(
|
||||
|
||||
form_data.id = form_data.id.lower()
|
||||
|
||||
tools = Tools.get_tool_by_id(form_data.id, db=db)
|
||||
tools = await Tools.get_tool_by_id(form_data.id, db=db)
|
||||
if tools is None:
|
||||
try:
|
||||
form_data.access_grants = filter_allowed_access_grants(
|
||||
form_data.access_grants = await filter_allowed_access_grants(
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
user.id,
|
||||
user.role,
|
||||
@@ -363,14 +363,14 @@ async def create_new_tools(
|
||||
)
|
||||
|
||||
form_data.content = replace_imports(form_data.content)
|
||||
tool_module, frontmatter = load_tool_module_by_id(form_data.id, content=form_data.content)
|
||||
tool_module, frontmatter = await load_tool_module_by_id(form_data.id, content=form_data.content)
|
||||
form_data.meta.manifest = frontmatter
|
||||
|
||||
TOOLS = request.app.state.TOOLS
|
||||
TOOLS[form_data.id] = tool_module
|
||||
|
||||
specs = get_tool_specs(TOOLS[form_data.id])
|
||||
tools = Tools.insert_new_tool(user.id, form_data, specs, db=db)
|
||||
tools = await Tools.insert_new_tool(user.id, form_data, specs, db=db)
|
||||
|
||||
tool_cache_dir = CACHE_DIR / 'tools' / form_data.id
|
||||
tool_cache_dir.mkdir(parents=True, exist_ok=True)
|
||||
@@ -401,14 +401,14 @@ async def create_new_tools(
|
||||
|
||||
|
||||
@router.get('/id/{id}', response_model=Optional[ToolAccessResponse])
|
||||
async def get_tools_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
tools = Tools.get_tool_by_id(id, db=db)
|
||||
async def get_tools_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
tools = await Tools.get_tool_by_id(id, db=db)
|
||||
|
||||
if tools:
|
||||
if (
|
||||
user.role == 'admin'
|
||||
or tools.user_id == user.id
|
||||
or AccessGrants.has_access(
|
||||
or await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='tool',
|
||||
resource_id=tools.id,
|
||||
@@ -421,7 +421,7 @@ async def get_tools_by_id(id: str, user=Depends(get_verified_user), db: Session
|
||||
write_access=(
|
||||
(user.role == 'admin' and BYPASS_ADMIN_ACCESS_CONTROL)
|
||||
or user.id == tools.user_id
|
||||
or AccessGrants.has_access(
|
||||
or await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='tool',
|
||||
resource_id=tools.id,
|
||||
@@ -453,9 +453,9 @@ async def update_tools_by_id(
|
||||
id: str,
|
||||
form_data: ToolForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
tools = Tools.get_tool_by_id(id, db=db)
|
||||
tools = await Tools.get_tool_by_id(id, db=db)
|
||||
if not tools:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
@@ -465,7 +465,7 @@ async def update_tools_by_id(
|
||||
# Is the user the original creator, in a group with write access, or an admin
|
||||
if (
|
||||
tools.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='tool',
|
||||
resource_id=tools.id,
|
||||
@@ -481,7 +481,7 @@ async def update_tools_by_id(
|
||||
|
||||
try:
|
||||
form_data.content = replace_imports(form_data.content)
|
||||
tool_module, frontmatter = load_tool_module_by_id(id, content=form_data.content)
|
||||
tool_module, frontmatter = await load_tool_module_by_id(id, content=form_data.content)
|
||||
form_data.meta.manifest = frontmatter
|
||||
|
||||
TOOLS = request.app.state.TOOLS
|
||||
@@ -489,7 +489,7 @@ async def update_tools_by_id(
|
||||
|
||||
specs = get_tool_specs(TOOLS[id])
|
||||
|
||||
form_data.access_grants = filter_allowed_access_grants(
|
||||
form_data.access_grants = await filter_allowed_access_grants(
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
user.id,
|
||||
user.role,
|
||||
@@ -503,7 +503,7 @@ async def update_tools_by_id(
|
||||
}
|
||||
|
||||
log.debug(updated)
|
||||
tools = Tools.update_tool_by_id(id, updated, db=db)
|
||||
tools = await Tools.update_tool_by_id(id, updated, db=db)
|
||||
|
||||
if tools:
|
||||
return tools
|
||||
@@ -535,9 +535,9 @@ async def update_tool_access_by_id(
|
||||
id: str,
|
||||
form_data: ToolAccessGrantsForm,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
tools = Tools.get_tool_by_id(id, db=db)
|
||||
tools = await Tools.get_tool_by_id(id, db=db)
|
||||
if not tools:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -546,7 +546,7 @@ async def update_tool_access_by_id(
|
||||
|
||||
if (
|
||||
tools.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='tool',
|
||||
resource_id=tools.id,
|
||||
@@ -560,7 +560,7 @@ async def update_tool_access_by_id(
|
||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||
)
|
||||
|
||||
form_data.access_grants = filter_allowed_access_grants(
|
||||
form_data.access_grants = await filter_allowed_access_grants(
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
user.id,
|
||||
user.role,
|
||||
@@ -568,9 +568,9 @@ async def update_tool_access_by_id(
|
||||
'sharing.public_tools',
|
||||
)
|
||||
|
||||
AccessGrants.set_access_grants('tool', id, form_data.access_grants, db=db)
|
||||
await AccessGrants.set_access_grants('tool', id, form_data.access_grants, db=db)
|
||||
|
||||
return Tools.get_tool_by_id(id, db=db)
|
||||
return await Tools.get_tool_by_id(id, db=db)
|
||||
|
||||
|
||||
############################
|
||||
@@ -583,9 +583,9 @@ async def delete_tools_by_id(
|
||||
request: Request,
|
||||
id: str,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
tools = Tools.get_tool_by_id(id, db=db)
|
||||
tools = await Tools.get_tool_by_id(id, db=db)
|
||||
if not tools:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
@@ -594,7 +594,7 @@ async def delete_tools_by_id(
|
||||
|
||||
if (
|
||||
tools.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='tool',
|
||||
resource_id=tools.id,
|
||||
@@ -608,7 +608,7 @@ async def delete_tools_by_id(
|
||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||
)
|
||||
|
||||
result = Tools.delete_tool_by_id(id, db=db)
|
||||
result = await Tools.delete_tool_by_id(id, db=db)
|
||||
if result:
|
||||
TOOLS = request.app.state.TOOLS
|
||||
if id in TOOLS:
|
||||
@@ -623,8 +623,8 @@ async def delete_tools_by_id(
|
||||
|
||||
|
||||
@router.get('/id/{id}/valves', response_model=Optional[dict])
|
||||
async def get_tools_valves_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
tools = Tools.get_tool_by_id(id, db=db)
|
||||
async def get_tools_valves_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
tools = await Tools.get_tool_by_id(id, db=db)
|
||||
if not tools:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -633,7 +633,7 @@ async def get_tools_valves_by_id(id: str, user=Depends(get_verified_user), db: S
|
||||
|
||||
if (
|
||||
tools.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='tool',
|
||||
resource_id=tools.id,
|
||||
@@ -648,7 +648,7 @@ async def get_tools_valves_by_id(id: str, user=Depends(get_verified_user), db: S
|
||||
)
|
||||
|
||||
try:
|
||||
valves = Tools.get_tool_valves_by_id(id, db=db)
|
||||
valves = await Tools.get_tool_valves_by_id(id, db=db)
|
||||
return valves
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
@@ -667,9 +667,9 @@ async def get_tools_valves_spec_by_id(
|
||||
request: Request,
|
||||
id: str,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
tools = Tools.get_tool_by_id(id, db=db)
|
||||
tools = await Tools.get_tool_by_id(id, db=db)
|
||||
if not tools:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -678,7 +678,7 @@ async def get_tools_valves_spec_by_id(
|
||||
|
||||
if (
|
||||
tools.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='tool',
|
||||
resource_id=tools.id,
|
||||
@@ -695,7 +695,7 @@ async def get_tools_valves_spec_by_id(
|
||||
if id in request.app.state.TOOLS:
|
||||
tools_module = request.app.state.TOOLS[id]
|
||||
else:
|
||||
tools_module, _ = load_tool_module_by_id(id)
|
||||
tools_module, _ = await load_tool_module_by_id(id)
|
||||
request.app.state.TOOLS[id] = tools_module
|
||||
|
||||
if hasattr(tools_module, 'Valves'):
|
||||
@@ -718,9 +718,9 @@ async def update_tools_valves_by_id(
|
||||
id: str,
|
||||
form_data: dict,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
tools = Tools.get_tool_by_id(id, db=db)
|
||||
tools = await Tools.get_tool_by_id(id, db=db)
|
||||
if not tools:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
@@ -729,7 +729,7 @@ async def update_tools_valves_by_id(
|
||||
|
||||
if (
|
||||
tools.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='tool',
|
||||
resource_id=tools.id,
|
||||
@@ -746,7 +746,7 @@ async def update_tools_valves_by_id(
|
||||
if id in request.app.state.TOOLS:
|
||||
tools_module = request.app.state.TOOLS[id]
|
||||
else:
|
||||
tools_module, _ = load_tool_module_by_id(id)
|
||||
tools_module, _ = await load_tool_module_by_id(id)
|
||||
request.app.state.TOOLS[id] = tools_module
|
||||
|
||||
if not hasattr(tools_module, 'Valves'):
|
||||
@@ -760,7 +760,7 @@ async def update_tools_valves_by_id(
|
||||
form_data = {k: v for k, v in form_data.items() if v is not None}
|
||||
valves = Valves(**form_data)
|
||||
valves_dict = valves.model_dump(exclude_unset=True)
|
||||
Tools.update_tool_valves_by_id(id, valves_dict, db=db)
|
||||
await Tools.update_tool_valves_by_id(id, valves_dict, db=db)
|
||||
return valves_dict
|
||||
except Exception as e:
|
||||
log.exception(f'Failed to update tool valves by id {id}: {e}')
|
||||
@@ -776,8 +776,8 @@ async def update_tools_valves_by_id(
|
||||
|
||||
|
||||
@router.get('/id/{id}/valves/user', response_model=Optional[dict])
|
||||
async def get_tools_user_valves_by_id(id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
tools = Tools.get_tool_by_id(id, db=db)
|
||||
async def get_tools_user_valves_by_id(id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
tools = await Tools.get_tool_by_id(id, db=db)
|
||||
if not tools:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -786,7 +786,7 @@ async def get_tools_user_valves_by_id(id: str, user=Depends(get_verified_user),
|
||||
|
||||
if (
|
||||
tools.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='tool',
|
||||
resource_id=tools.id,
|
||||
@@ -801,7 +801,7 @@ async def get_tools_user_valves_by_id(id: str, user=Depends(get_verified_user),
|
||||
)
|
||||
|
||||
try:
|
||||
user_valves = Tools.get_user_valves_by_id_and_user_id(id, user.id, db=db)
|
||||
user_valves = await Tools.get_user_valves_by_id_and_user_id(id, user.id, db=db)
|
||||
return user_valves
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
@@ -815,9 +815,9 @@ async def get_tools_user_valves_spec_by_id(
|
||||
request: Request,
|
||||
id: str,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
tools = Tools.get_tool_by_id(id, db=db)
|
||||
tools = await Tools.get_tool_by_id(id, db=db)
|
||||
if not tools:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -826,7 +826,7 @@ async def get_tools_user_valves_spec_by_id(
|
||||
|
||||
if (
|
||||
tools.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='tool',
|
||||
resource_id=tools.id,
|
||||
@@ -843,7 +843,7 @@ async def get_tools_user_valves_spec_by_id(
|
||||
if id in request.app.state.TOOLS:
|
||||
tools_module = request.app.state.TOOLS[id]
|
||||
else:
|
||||
tools_module, _ = load_tool_module_by_id(id)
|
||||
tools_module, _ = await load_tool_module_by_id(id)
|
||||
request.app.state.TOOLS[id] = tools_module
|
||||
|
||||
if hasattr(tools_module, 'UserValves'):
|
||||
@@ -861,9 +861,9 @@ async def update_tools_user_valves_by_id(
|
||||
id: str,
|
||||
form_data: dict,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
tools = Tools.get_tool_by_id(id, db=db)
|
||||
tools = await Tools.get_tool_by_id(id, db=db)
|
||||
if not tools:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
@@ -872,7 +872,7 @@ async def update_tools_user_valves_by_id(
|
||||
|
||||
if (
|
||||
tools.user_id != user.id
|
||||
and not AccessGrants.has_access(
|
||||
and not await AccessGrants.has_access(
|
||||
user_id=user.id,
|
||||
resource_type='tool',
|
||||
resource_id=tools.id,
|
||||
@@ -889,7 +889,7 @@ async def update_tools_user_valves_by_id(
|
||||
if id in request.app.state.TOOLS:
|
||||
tools_module = request.app.state.TOOLS[id]
|
||||
else:
|
||||
tools_module, _ = load_tool_module_by_id(id)
|
||||
tools_module, _ = await load_tool_module_by_id(id)
|
||||
request.app.state.TOOLS[id] = tools_module
|
||||
|
||||
if hasattr(tools_module, 'UserValves'):
|
||||
@@ -899,7 +899,7 @@ async def update_tools_user_valves_by_id(
|
||||
form_data = {k: v for k, v in form_data.items() if v is not None}
|
||||
user_valves = UserValves(**form_data)
|
||||
user_valves_dict = user_valves.model_dump(exclude_unset=True)
|
||||
Tools.update_user_valves_by_id_and_user_id(id, user.id, user_valves_dict, db=db)
|
||||
await Tools.update_user_valves_by_id_and_user_id(id, user.id, user_valves_dict, db=db)
|
||||
return user_valves_dict
|
||||
except Exception as e:
|
||||
log.exception(f'Failed to update user valves by id {id}: {e}')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import logging
|
||||
from typing import Optional
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
import base64
|
||||
import io
|
||||
|
||||
@@ -30,7 +30,7 @@ from open_webui.models.users import (
|
||||
|
||||
from open_webui.constants import ERROR_MESSAGES
|
||||
from open_webui.env import STATIC_DIR
|
||||
from open_webui.internal.db import get_session
|
||||
from open_webui.internal.db import get_async_session
|
||||
|
||||
|
||||
from open_webui.utils.auth import (
|
||||
@@ -63,7 +63,7 @@ async def get_users(
|
||||
direction: Optional[str] = None,
|
||||
page: Optional[int] = 1,
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
limit = PAGE_ITEM_COUNT
|
||||
|
||||
@@ -80,14 +80,14 @@ async def get_users(
|
||||
|
||||
filter['direction'] = direction
|
||||
|
||||
result = Users.get_users(filter=filter, skip=skip, limit=limit, db=db)
|
||||
result = await Users.get_users(filter=filter, skip=skip, limit=limit, db=db)
|
||||
|
||||
users = result['users']
|
||||
total = result['total']
|
||||
|
||||
# Fetch groups for all users in a single query to avoid N+1
|
||||
user_ids = [user.id for user in users]
|
||||
user_groups = Groups.get_groups_by_member_ids(user_ids, db=db)
|
||||
user_groups = await Groups.get_groups_by_member_ids(user_ids, db=db)
|
||||
|
||||
return {
|
||||
'users': [
|
||||
@@ -106,9 +106,9 @@ async def get_users(
|
||||
@router.get('/all', response_model=UserInfoListResponse)
|
||||
async def get_all_users(
|
||||
user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
return Users.get_users(db=db)
|
||||
return await Users.get_users(db=db)
|
||||
|
||||
|
||||
@router.get('/search', response_model=UserInfoListResponse)
|
||||
@@ -118,7 +118,7 @@ async def search_users(
|
||||
direction: Optional[str] = None,
|
||||
page: Optional[int] = 1,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
limit = PAGE_ITEM_COUNT
|
||||
|
||||
@@ -133,7 +133,7 @@ async def search_users(
|
||||
if direction:
|
||||
filter['direction'] = direction
|
||||
|
||||
return Users.get_users(filter=filter, skip=skip, limit=limit, db=db)
|
||||
return await Users.get_users(filter=filter, skip=skip, limit=limit, db=db)
|
||||
|
||||
|
||||
############################
|
||||
@@ -142,8 +142,8 @@ async def search_users(
|
||||
|
||||
|
||||
@router.get('/groups')
|
||||
async def get_user_groups(user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
return Groups.get_groups_by_member_id(user.id, db=db)
|
||||
async def get_user_groups(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
return await Groups.get_groups_by_member_id(user.id, db=db)
|
||||
|
||||
|
||||
############################
|
||||
@@ -155,9 +155,9 @@ async def get_user_groups(user=Depends(get_verified_user), db: Session = Depends
|
||||
async def get_user_permissisions(
|
||||
request: Request,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
user_permissions = get_permissions(user.id, request.app.state.config.USER_PERMISSIONS, db=db)
|
||||
user_permissions = await get_permissions(user.id, request.app.state.config.USER_PERMISSIONS, db=db)
|
||||
|
||||
return user_permissions
|
||||
|
||||
@@ -272,8 +272,8 @@ async def update_default_user_permissions(request: Request, form_data: UserPermi
|
||||
|
||||
|
||||
@router.get('/user/settings', response_model=Optional[UserSettings])
|
||||
async def get_user_settings_by_session_user(user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
user = Users.get_user_by_id(user.id, db=db)
|
||||
async def get_user_settings_by_session_user(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
user = await Users.get_user_by_id(user.id, db=db)
|
||||
if user:
|
||||
return user.settings
|
||||
else:
|
||||
@@ -293,7 +293,7 @@ async def update_user_settings_by_session_user(
|
||||
request: Request,
|
||||
form_data: UserSettings,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
updated_user_settings = form_data.model_dump()
|
||||
ui_settings = updated_user_settings.get('ui')
|
||||
@@ -301,7 +301,7 @@ async def update_user_settings_by_session_user(
|
||||
user.role != 'admin'
|
||||
and ui_settings is not None
|
||||
and 'toolServers' in ui_settings.keys()
|
||||
and not has_permission(
|
||||
and not await has_permission(
|
||||
user.id,
|
||||
'features.direct_tool_servers',
|
||||
request.app.state.config.USER_PERMISSIONS,
|
||||
@@ -310,7 +310,7 @@ async def update_user_settings_by_session_user(
|
||||
# If the user is not an admin and does not have permission to use tool servers, remove the key
|
||||
updated_user_settings['ui'].pop('toolServers', None)
|
||||
|
||||
user = Users.update_user_settings_by_id(user.id, updated_user_settings, db=db)
|
||||
user = await Users.update_user_settings_by_id(user.id, updated_user_settings, db=db)
|
||||
if user:
|
||||
return user.settings
|
||||
else:
|
||||
@@ -329,14 +329,14 @@ async def update_user_settings_by_session_user(
|
||||
async def get_user_status_by_session_user(
|
||||
request: Request,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if not request.app.state.config.ENABLE_USER_STATUS:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=ERROR_MESSAGES.ACTION_PROHIBITED,
|
||||
)
|
||||
user = Users.get_user_by_id(user.id, db=db)
|
||||
user = await Users.get_user_by_id(user.id, db=db)
|
||||
if user:
|
||||
return user
|
||||
else:
|
||||
@@ -356,16 +356,16 @@ async def update_user_status_by_session_user(
|
||||
request: Request,
|
||||
form_data: UserStatus,
|
||||
user=Depends(get_verified_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
if not request.app.state.config.ENABLE_USER_STATUS:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=ERROR_MESSAGES.ACTION_PROHIBITED,
|
||||
)
|
||||
user = Users.get_user_by_id(user.id, db=db)
|
||||
user = await Users.get_user_by_id(user.id, db=db)
|
||||
if user:
|
||||
user = Users.update_user_status_by_id(user.id, form_data, db=db)
|
||||
user = await Users.update_user_status_by_id(user.id, form_data, db=db)
|
||||
return user
|
||||
else:
|
||||
raise HTTPException(
|
||||
@@ -380,8 +380,8 @@ async def update_user_status_by_session_user(
|
||||
|
||||
|
||||
@router.get('/user/info', response_model=Optional[dict])
|
||||
async def get_user_info_by_session_user(user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
user = Users.get_user_by_id(user.id, db=db)
|
||||
async def get_user_info_by_session_user(user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
user = await Users.get_user_by_id(user.id, db=db)
|
||||
if user:
|
||||
return user.info
|
||||
else:
|
||||
@@ -398,14 +398,14 @@ async def get_user_info_by_session_user(user=Depends(get_verified_user), db: Ses
|
||||
|
||||
@router.post('/user/info/update', response_model=Optional[dict])
|
||||
async def update_user_info_by_session_user(
|
||||
form_data: dict, user=Depends(get_verified_user), db: Session = Depends(get_session)
|
||||
form_data: dict, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
|
||||
):
|
||||
user = Users.get_user_by_id(user.id, db=db)
|
||||
user = await Users.get_user_by_id(user.id, db=db)
|
||||
if user:
|
||||
if user.info is None:
|
||||
user.info = {}
|
||||
|
||||
user = Users.update_user_by_id(user.id, {'info': {**user.info, **form_data}}, db=db)
|
||||
user = await Users.update_user_by_id(user.id, {'info': {**user.info, **form_data}}, db=db)
|
||||
if user:
|
||||
return user.info
|
||||
else:
|
||||
@@ -435,12 +435,12 @@ class UserActiveResponse(UserStatus):
|
||||
|
||||
|
||||
@router.get('/{user_id}', response_model=UserActiveResponse)
|
||||
async def get_user_by_id(user_id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
|
||||
async def get_user_by_id(user_id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
|
||||
# Check if user_id is a shared chat
|
||||
# If it is, get the user_id from the chat
|
||||
if user_id.startswith('shared-'):
|
||||
chat_id = user_id.replace('shared-', '')
|
||||
chat = Chats.get_chat_by_id(chat_id)
|
||||
chat = await Chats.get_chat_by_id(chat_id)
|
||||
if chat:
|
||||
user_id = chat.user_id
|
||||
else:
|
||||
@@ -449,14 +449,14 @@ async def get_user_by_id(user_id: str, user=Depends(get_admin_user), db: Session
|
||||
detail=ERROR_MESSAGES.USER_NOT_FOUND,
|
||||
)
|
||||
|
||||
user = Users.get_user_by_id(user_id, db=db)
|
||||
user = await Users.get_user_by_id(user_id, db=db)
|
||||
if user:
|
||||
groups = Groups.get_groups_by_member_id(user_id, db=db)
|
||||
groups = await Groups.get_groups_by_member_id(user_id, db=db)
|
||||
return UserActiveResponse(
|
||||
**{
|
||||
**user.model_dump(),
|
||||
'groups': [{'id': group.id, 'name': group.name} for group in groups],
|
||||
'is_active': Users.is_user_active(user_id, db=db),
|
||||
'is_active': await Users.is_user_active(user_id, db=db),
|
||||
}
|
||||
)
|
||||
else:
|
||||
@@ -467,15 +467,15 @@ async def get_user_by_id(user_id: str, user=Depends(get_admin_user), db: Session
|
||||
|
||||
|
||||
@router.get('/{user_id}/info', response_model=UserInfoResponse)
|
||||
async def get_user_info_by_id(user_id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)):
|
||||
user = Users.get_user_by_id(user_id, db=db)
|
||||
async def get_user_info_by_id(user_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)):
|
||||
user = await Users.get_user_by_id(user_id, db=db)
|
||||
if user:
|
||||
groups = Groups.get_groups_by_member_id(user_id, db=db)
|
||||
groups = await Groups.get_groups_by_member_id(user_id, db=db)
|
||||
return UserInfoResponse(
|
||||
**{
|
||||
**user.model_dump(),
|
||||
'groups': [{'id': group.id, 'name': group.name} for group in groups],
|
||||
'is_active': Users.is_user_active(user_id, db=db),
|
||||
'is_active': await Users.is_user_active(user_id, db=db),
|
||||
}
|
||||
)
|
||||
else:
|
||||
@@ -486,8 +486,8 @@ async def get_user_info_by_id(user_id: str, user=Depends(get_verified_user), db:
|
||||
|
||||
|
||||
@router.get('/{user_id}/oauth/sessions')
|
||||
async def get_user_oauth_sessions_by_id(user_id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
|
||||
sessions = OAuthSessions.get_sessions_by_user_id(user_id, db=db)
|
||||
async def get_user_oauth_sessions_by_id(user_id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
|
||||
sessions = await OAuthSessions.get_sessions_by_user_id(user_id, db=db)
|
||||
if sessions and len(sessions) > 0:
|
||||
return sessions
|
||||
else:
|
||||
@@ -503,8 +503,8 @@ async def get_user_oauth_sessions_by_id(user_id: str, user=Depends(get_admin_use
|
||||
|
||||
|
||||
@router.get('/{user_id}/profile/image')
|
||||
def get_user_profile_image_by_id(user_id: str, user=Depends(get_verified_user)):
|
||||
user = Users.get_user_by_id(user_id)
|
||||
async def get_user_profile_image_by_id(user_id: str, user=Depends(get_verified_user)):
|
||||
user = await Users.get_user_by_id(user_id)
|
||||
if user:
|
||||
if user.profile_image_url:
|
||||
# check if it's url or base64
|
||||
@@ -542,10 +542,10 @@ def get_user_profile_image_by_id(user_id: str, user=Depends(get_verified_user)):
|
||||
|
||||
@router.get('/{user_id}/active', response_model=dict)
|
||||
async def get_user_active_status_by_id(
|
||||
user_id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)
|
||||
user_id: str, user=Depends(get_verified_user), db: AsyncSession = Depends(get_async_session)
|
||||
):
|
||||
return {
|
||||
'active': Users.is_user_active(user_id, db=db),
|
||||
'active': await Users.is_user_active(user_id, db=db),
|
||||
}
|
||||
|
||||
|
||||
@@ -559,11 +559,11 @@ async def update_user_by_id(
|
||||
user_id: str,
|
||||
form_data: UserUpdateForm,
|
||||
session_user=Depends(get_admin_user),
|
||||
db: Session = Depends(get_session),
|
||||
db: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
# Prevent modification of the primary admin user by other admins
|
||||
try:
|
||||
first_user = Users.get_first_user(db=db)
|
||||
first_user = await Users.get_first_user(db=db)
|
||||
if first_user:
|
||||
if user_id == first_user.id:
|
||||
if session_user.id != user_id:
|
||||
@@ -587,11 +587,11 @@ async def update_user_by_id(
|
||||
detail='Could not verify primary admin status.',
|
||||
)
|
||||
|
||||
user = Users.get_user_by_id(user_id, db=db)
|
||||
user = await Users.get_user_by_id(user_id, db=db)
|
||||
|
||||
if user:
|
||||
if form_data.email.lower() != user.email:
|
||||
email_user = Users.get_user_by_email(form_data.email.lower(), db=db)
|
||||
email_user = await Users.get_user_by_email(form_data.email.lower(), db=db)
|
||||
if email_user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
@@ -605,10 +605,10 @@ async def update_user_by_id(
|
||||
raise HTTPException(400, detail=str(e))
|
||||
|
||||
hashed = get_password_hash(form_data.password)
|
||||
Auths.update_user_password_by_id(user_id, hashed, db=db)
|
||||
await Auths.update_user_password_by_id(user_id, hashed, db=db)
|
||||
|
||||
Auths.update_email_by_id(user_id, form_data.email.lower(), db=db)
|
||||
updated_user = Users.update_user_by_id(
|
||||
await Auths.update_email_by_id(user_id, form_data.email.lower(), db=db)
|
||||
updated_user = await Users.update_user_by_id(
|
||||
user_id,
|
||||
{
|
||||
'role': form_data.role,
|
||||
@@ -639,10 +639,10 @@ async def update_user_by_id(
|
||||
|
||||
|
||||
@router.delete('/{user_id}', response_model=bool)
|
||||
async def delete_user_by_id(user_id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
|
||||
async def delete_user_by_id(user_id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
|
||||
# Prevent deletion of the primary admin user
|
||||
try:
|
||||
first_user = Users.get_first_user(db=db)
|
||||
first_user = await Users.get_first_user(db=db)
|
||||
if first_user and user_id == first_user.id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
@@ -656,7 +656,7 @@ async def delete_user_by_id(user_id: str, user=Depends(get_admin_user), db: Sess
|
||||
)
|
||||
|
||||
if user.id != user_id:
|
||||
result = Auths.delete_auth_by_id(user_id, db=db)
|
||||
result = await Auths.delete_auth_by_id(user_id, db=db)
|
||||
|
||||
if result:
|
||||
return True
|
||||
@@ -679,5 +679,5 @@ async def delete_user_by_id(user_id: str, user=Depends(get_admin_user), db: Sess
|
||||
|
||||
|
||||
@router.get('/{user_id}/groups')
|
||||
async def get_user_groups_by_id(user_id: str, user=Depends(get_admin_user), db: Session = Depends(get_session)):
|
||||
return Groups.get_groups_by_member_id(user_id, db=db)
|
||||
async def get_user_groups_by_id(user_id: str, user=Depends(get_admin_user), db: AsyncSession = Depends(get_async_session)):
|
||||
return await Groups.get_groups_by_member_id(user_id, db=db)
|
||||
|
||||
Reference in New Issue
Block a user