diff --git a/mnemosyne/library/api/workspaces.py b/mnemosyne/library/api/workspaces.py index 1d91d2b..c7ee4c5 100644 --- a/mnemosyne/library/api/workspaces.py +++ b/mnemosyne/library/api/workspaces.py @@ -17,6 +17,7 @@ across users. import logging from neomodel import db +from neomodel.exceptions import UniqueProperty from rest_framework import status from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated @@ -85,7 +86,10 @@ def workspace_create(request): data["workspace_id"], request.user.username, ) return Response( - {"detail": "Workspace id is already in use."}, + { + "detail": "Workspace id is already in use.", + "code": "owner_conflict", + }, status=status.HTTP_409_CONFLICT, ) if existing.library_type != data["library_type"]: @@ -95,7 +99,8 @@ def workspace_create(request): "library_type is immutable for an existing workspace " f"(have '{existing.library_type}', " f"got '{data['library_type']}')." - ) + ), + "code": "library_type_immutable", }, status=status.HTTP_409_CONFLICT, ) @@ -120,7 +125,29 @@ def workspace_create(request): reranker_instruction=defaults["reranker_instruction"], llm_context_prompt=defaults["llm_context_prompt"], ) - lib.save() + try: + lib.save() + except UniqueProperty: + # Library.name is globally unique. A name collision here almost always + # means an orphaned Library survived a failed Daedalus workspace delete + # (the old node kept the name), and the recreate under a new + # workspace_id now clashes. Surface a clean 409 instead of a 500 so + # Daedalus can record + report it; the operator clears the orphan + # (admin delete) or renames the workspace. + logger.warning( + "workspace_create name_conflict workspace_id=%s name=%s", + data["workspace_id"], data["name"], + ) + return Response( + { + "detail": ( + f"A library named '{data['name']}' already exists in " + "Mnemosyne." + ), + "code": "name_conflict", + }, + status=status.HTTP_409_CONFLICT, + ) logger.info( "Workspace created workspace_id=%s library_uid=%s library_type=%s", data["workspace_id"], lib.uid, lib.library_type,