diff --git a/interfaces/admin/chat_api.py b/interfaces/admin/chat_api.py index 60b9a2ca..8b50dff0 100644 --- a/interfaces/admin/chat_api.py +++ b/interfaces/admin/chat_api.py @@ -984,8 +984,10 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor if not sess: raise HTTPException(status_code=404, detail="session_not_found") if _is_administrator_chat_viewer(ctx): - deleted = store.delete_session_for_administrator_username( - session_id=session_id, username=_chat_username(ctx) + deleted = store.delete_session_for_administrator_chat_view( + session_id=session_id, + username=_chat_username(ctx), + tenant_id=tenant_id, ) else: deleted = store.delete_session_for_user( @@ -994,8 +996,11 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor if not deleted: raise HTTPException(status_code=404, detail="session_not_found") if _is_administrator_chat_viewer(ctx): - remaining = store.list_sessions_for_administrator_username( - username=_chat_username(ctx), limit=1, offset=0 + remaining = store.list_sessions_for_administrator_chat_view( + username=_chat_username(ctx), + tenant_id=tenant_id, + limit=1, + offset=0, ) else: remaining = store.list_sessions_for_user( diff --git a/svc/persistence/assistant_store_protocol.py b/svc/persistence/assistant_store_protocol.py index 51190201..6573a8cf 100644 --- a/svc/persistence/assistant_store_protocol.py +++ b/svc/persistence/assistant_store_protocol.py @@ -60,6 +60,7 @@ class AssistantStoreProtocol(Protocol): def delete_memory_item(self, *, memory_id: 'str') -> 'int': ... def delete_message(self, *, session_id: 'str', message_id: 'int') -> 'bool': ... def delete_session(self, session_id: 'str') -> 'None': ... + def delete_session_for_administrator_chat_view(self, *, session_id: 'str', username: 'str', tenant_id: 'str') -> 'bool': ... def delete_session_for_user(self, *, session_id: 'str', tenant_id: 'str', user_id: 'str') -> 'bool': ... def delete_session_in_tenant(self, *, session_id: 'str', tenant_id: 'str') -> 'bool': ... def delete_setting(self, key: 'str') -> 'None': ... diff --git a/svc/persistence/sa_repos/chat_sessions.py b/svc/persistence/sa_repos/chat_sessions.py index 31d4fbf2..1585853d 100644 --- a/svc/persistence/sa_repos/chat_sessions.py +++ b/svc/persistence/sa_repos/chat_sessions.py @@ -614,6 +614,31 @@ class ChatSessionsSaRepository: conn.execute(delete(chat_session).where(chat_session.c.id == sid)) return True + def try_delete_chat_session_for_administrator_chat_view( + self, *, session_id: str, username: str, tenant_id: str + ) -> bool: + sid = str(session_id or "").strip() + if not sid: + return False + ids = self._administrator_chat_session_ids_subquery( + username=username, + tenant_id=tenant_id, + ) + with self._engine.begin() as conn: + chk = conn.execute( + select(1) + .select_from(chat_session) + .where( + chat_session.c.id == sid, + chat_session.c.id.in_(select(ids.c.sid)), + ) + .limit(1) + ).first() + if not chk: + return False + conn.execute(delete(chat_session).where(chat_session.c.id == sid)) + return True + def try_delete_chat_session_for_tenant(self, *, session_id: str, tenant_id: str) -> bool: sid, tid = str(session_id or "").strip(), str(tenant_id) if not sid: diff --git a/svc/persistence/sqlite_store.py b/svc/persistence/sqlite_store.py index 7f2172bb..39ba84f2 100644 --- a/svc/persistence/sqlite_store.py +++ b/svc/persistence/sqlite_store.py @@ -1666,6 +1666,15 @@ class SqliteStore(ScheduledJobStoreMixin): session_id=session_id, username=username, tenant_id=tenant_id ) + def delete_session_for_administrator_chat_view( + self, *, session_id: str, username: str, tenant_id: str + ) -> bool: + return self._chat_sessions_repo().try_delete_chat_session_for_administrator_chat_view( + session_id=session_id, + username=username, + tenant_id=tenant_id, + ) + def get_session_for_administrator_username( self, *, session_id: str, username: str ) -> Optional[ChatSession]: diff --git a/tests/test_chat_administrator_cross_tenant_sessions.py b/tests/test_chat_administrator_cross_tenant_sessions.py index 7a514fde..91896406 100644 --- a/tests/test_chat_administrator_cross_tenant_sessions.py +++ b/tests/test_chat_administrator_cross_tenant_sessions.py @@ -76,3 +76,52 @@ def test_delete_session_for_administrator_username_cross_tenant( assert s.get_session_for_administrator_username(session_id=str(other.id), username="administrator") is None meta = s.get_sessions_list_meta_for_administrator_username(username="administrator") assert int(meta.session_count or 0) == 1 + + +def test_delete_channel_only_session_for_administrator_chat_view( + store_two_tenants: SqliteStore, +) -> None: + s = store_two_tenants + team = next( + x for x in s.list_sessions_for_administrator_username(username="administrator", limit=50) + if x.title == "team-sess" + ) + team_owner = s.get_ui_session_owner(session_id=str(team.id)) + login_tid = str((team_owner or {}).get("tenant_id") or "") + assert login_tid + + channel_sid = s.get_or_create_channel_session_v2( + tenant_id=login_tid, + channel="scheduled_job", + account_id="job", + external_chat_id="job-123", + external_user_id="job-123", + session_title="Scheduled ยท job-123", + ) + assert s.get_ui_session_owner(session_id=channel_sid) is None + assert ( + s.get_session_for_administrator_chat_view( + session_id=channel_sid, + username="administrator", + tenant_id=login_tid, + ) + is not None + ) + + assert ( + s.delete_session_for_administrator_chat_view( + session_id=channel_sid, + username="administrator", + tenant_id=login_tid, + ) + is True + ) + assert s.get_session(channel_sid) is None + assert ( + s.get_session_for_administrator_chat_view( + session_id=channel_sid, + username="administrator", + tenant_id=login_tid, + ) + is None + )