mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-17 10:15:27 -04:00
fix(sessions): scope enrichment queries by owner, add LIMIT to auto_sort (#3350)
GET /api/sessions fired full-table scans against sessions, documents, and gallery_images on every call. Added DbSession.owner == user (line 265), Document.owner == user (line 283), GalleryImage.owner == user (line 289), and .limit(2000) to auto_sort_sessions (line 1013). All follow the existing owner-scoping pattern at lines 700 and 1230. No behaviour change — the response was already correct; this eliminates the over-fetch.
This commit is contained in:
@@ -262,7 +262,7 @@ def setup_session_routes(session_manager: SessionManager, config: dict, webhook_
|
|||||||
last_msg_map = {}
|
last_msg_map = {}
|
||||||
mode_map = {}
|
mode_map = {}
|
||||||
msg_count_map = {}
|
msg_count_map = {}
|
||||||
rows = db.query(DbSession.id, DbSession.folder, DbSession.total_input_tokens, DbSession.total_output_tokens, DbSession.is_important, DbSession.created_at, DbSession.updated_at, DbSession.last_message_at, DbSession.mode, DbSession.message_count).filter(DbSession.archived == False).all()
|
rows = db.query(DbSession.id, DbSession.folder, DbSession.total_input_tokens, DbSession.total_output_tokens, DbSession.is_important, DbSession.created_at, DbSession.updated_at, DbSession.last_message_at, DbSession.mode, DbSession.message_count).filter(DbSession.archived == False, DbSession.owner == user).all()
|
||||||
for row in rows:
|
for row in rows:
|
||||||
folder_map[row.id] = row.folder
|
folder_map[row.id] = row.folder
|
||||||
token_map[row.id] = (row.total_input_tokens or 0) + (row.total_output_tokens or 0)
|
token_map[row.id] = (row.total_input_tokens or 0) + (row.total_output_tokens or 0)
|
||||||
@@ -284,12 +284,14 @@ def setup_session_routes(session_manager: SessionManager, config: dict, webhook_
|
|||||||
r[0] for r in db.query(Document.session_id)
|
r[0] for r in db.query(Document.session_id)
|
||||||
.filter(Document.is_active == True,
|
.filter(Document.is_active == True,
|
||||||
Document.current_content != None,
|
Document.current_content != None,
|
||||||
func.trim(Document.current_content) != "")
|
func.trim(Document.current_content) != "",
|
||||||
|
Document.owner == user)
|
||||||
.distinct().all()
|
.distinct().all()
|
||||||
)
|
)
|
||||||
img_session_ids = set(
|
img_session_ids = set(
|
||||||
r[0] for r in db.query(GalleryImage.session_id)
|
r[0] for r in db.query(GalleryImage.session_id)
|
||||||
.filter(GalleryImage.session_id != None)
|
.filter(GalleryImage.session_id != None,
|
||||||
|
GalleryImage.owner == user)
|
||||||
.distinct().all()
|
.distinct().all()
|
||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
@@ -1010,7 +1012,7 @@ def setup_session_routes(session_manager: SessionManager, config: dict, webhook_
|
|||||||
}
|
}
|
||||||
_THROWAWAY_MAX_MESSAGES = 4 # only delete if <= this many messages
|
_THROWAWAY_MAX_MESSAGES = 4 # only delete if <= this many messages
|
||||||
try:
|
try:
|
||||||
rows = db.query(DbSession).filter(DbSession.archived == False, DbSession.owner == user).all()
|
rows = db.query(DbSession).filter(DbSession.archived == False, DbSession.owner == user).limit(2000).all()
|
||||||
folder_map = {r.id: r.folder for r in rows}
|
folder_map = {r.id: r.folder for r in rows}
|
||||||
# Precompute per-session message counts in TWO aggregate queries
|
# Precompute per-session message counts in TWO aggregate queries
|
||||||
# instead of 1–3 queries PER session — with many chats the per-row
|
# instead of 1–3 queries PER session — with many chats the per-row
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
"""list_sessions must return only the authenticated user's sessions.
|
||||||
|
|
||||||
|
Regression for the enrichment query at routes/session_routes.py:265 which
|
||||||
|
previously fetched rows for all owners on every GET /api/sessions call.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import types
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
from sqlalchemy.pool import NullPool
|
||||||
|
|
||||||
|
import core.database as cdb
|
||||||
|
from core.database import Session as DbSession
|
||||||
|
|
||||||
|
_TMPDB = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
|
||||||
|
_ENGINE = create_engine(
|
||||||
|
f"sqlite:///{_TMPDB.name}",
|
||||||
|
connect_args={"check_same_thread": False},
|
||||||
|
poolclass=NullPool,
|
||||||
|
)
|
||||||
|
cdb.Base.metadata.create_all(_ENGINE)
|
||||||
|
_TS = sessionmaker(bind=_ENGINE, autoflush=False, autocommit=False)
|
||||||
|
|
||||||
|
|
||||||
|
def _stub_multipart_if_missing(monkeypatch):
|
||||||
|
try:
|
||||||
|
import python_multipart # noqa: F401
|
||||||
|
return
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
stub = types.ModuleType("python_multipart")
|
||||||
|
stub.__version__ = "0.0.20"
|
||||||
|
monkeypatch.setitem(sys.modules, "python_multipart", stub)
|
||||||
|
|
||||||
|
|
||||||
|
def test_list_sessions_excludes_other_users_sessions(monkeypatch):
|
||||||
|
import routes.session_routes as sr
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
_stub_multipart_if_missing(monkeypatch)
|
||||||
|
monkeypatch.setattr(sr, "SessionLocal", _TS)
|
||||||
|
monkeypatch.setattr(sr, "effective_user", lambda request: "alice")
|
||||||
|
|
||||||
|
alice_id = str(uuid.uuid4())
|
||||||
|
bob_id = str(uuid.uuid4())
|
||||||
|
db = _TS()
|
||||||
|
try:
|
||||||
|
db.query(DbSession).delete()
|
||||||
|
db.add(DbSession(id=alice_id, owner="alice", name="alice session",
|
||||||
|
endpoint_url="http://localhost", model="gpt-4", archived=False))
|
||||||
|
db.add(DbSession(id=bob_id, owner="bob", name="bob session",
|
||||||
|
endpoint_url="http://localhost", model="gpt-4", archived=False))
|
||||||
|
db.commit()
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
alice_session = MagicMock(id=alice_id, name="alice session",
|
||||||
|
model="gpt-4", endpoint_url="http://localhost",
|
||||||
|
rag=False, archived=False)
|
||||||
|
sm = MagicMock()
|
||||||
|
sm.get_sessions_for_user.return_value = {alice_id: alice_session}
|
||||||
|
router = sr.setup_session_routes(sm, {})
|
||||||
|
endpoint = next(r.endpoint for r in router.routes
|
||||||
|
if getattr(r, "path", "") == "/api/sessions"
|
||||||
|
and "GET" in getattr(r, "methods", set()))
|
||||||
|
|
||||||
|
result = endpoint(request=MagicMock())
|
||||||
|
returned_ids = {s["id"] for s in result}
|
||||||
|
assert alice_id in returned_ids
|
||||||
|
assert bob_id not in returned_ids
|
||||||
Reference in New Issue
Block a user