Compare commits

...

2 Commits

Author SHA1 Message Date
e6e7cdb7b7 Merge pull request 'fix(api): delete orphaned sessions when character is deleted' (#7) from bug/orphaned-sessions-on-char-delete into dev
Reviewed-on: #7
2025-11-26 16:47:06 +00:00
98bb6ab589 fix(api): delete orphaned sessions when character is deleted
- Add delete_sessions_by_character() method to SessionService that
  cleans up all game sessions associated with a character
- Update delete_character() to hard delete instead of soft delete
- Call session cleanup before deleting character to prevent orphaned data
- Delete associated chat messages when cleaning up sessions
- Update API documentation to reflect new behavior

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-26 10:46:35 -06:00
4 changed files with 92 additions and 7 deletions

View File

@@ -334,7 +334,10 @@ class CharacterService:
def delete_character(self, character_id: str, user_id: str) -> bool:
"""
Delete a character (soft delete by marking inactive).
Permanently delete a character from the database.
Also cleans up any game sessions associated with the character
to prevent orphaned sessions.
Args:
character_id: Character ID
@@ -354,11 +357,20 @@ class CharacterService:
if not character:
raise CharacterNotFound(f"Character not found: {character_id}")
# Soft delete by marking inactive
self.db.update_document(
# Clean up associated sessions before deleting the character
# Local import to avoid circular dependency (session_service imports character_service)
from app.services.session_service import get_session_service
session_service = get_session_service()
deleted_sessions = session_service.delete_sessions_by_character(character_id)
if deleted_sessions > 0:
logger.info("Cleaned up sessions for deleted character",
character_id=character_id,
sessions_deleted=deleted_sessions)
# Hard delete - permanently remove from database
self.db.delete_document(
collection_id=self.collection_id,
document_id=character_id,
data={'is_active': False}
document_id=character_id
)
logger.info("Character deleted successfully", character_id=character_id)

View File

@@ -465,6 +465,79 @@ class SessionService:
error=str(e))
raise
def delete_sessions_by_character(self, character_id: str) -> int:
"""
Delete all sessions associated with a character.
Used during character deletion to clean up orphaned sessions.
This method finds all sessions where the character is either:
- The solo character (solo_character_id)
- A party member in multiplayer (party_member_ids)
For each session found, this method:
1. Deletes all associated chat messages
2. Hard deletes the session document from the database
Args:
character_id: Character ID to delete sessions for
Returns:
Number of sessions deleted
"""
try:
logger.info("Deleting sessions for character",
character_id=character_id)
# Query all sessions where characterId matches
# The characterId field is indexed at the document level
documents = self.db.list_rows(
table_id=self.collection_id,
queries=[Query.equal('characterId', character_id)]
)
if not documents:
logger.debug("No sessions found for character",
character_id=character_id)
return 0
deleted_count = 0
chat_service = get_chat_message_service()
for document in documents:
session_id = document.id
try:
# Delete associated chat messages first
deleted_messages = chat_service.delete_messages_by_session(session_id)
logger.debug("Deleted chat messages for session",
session_id=session_id,
message_count=deleted_messages)
# Delete session document
self.db.delete_document(
collection_id=self.collection_id,
document_id=session_id
)
deleted_count += 1
except Exception as e:
# Log but continue with other sessions
logger.error("Failed to delete session during character cleanup",
session_id=session_id,
character_id=character_id,
error=str(e))
continue
logger.info("Sessions deleted for character",
character_id=character_id,
deleted_count=deleted_count)
return deleted_count
except Exception as e:
logger.error("Failed to delete sessions for character",
character_id=character_id,
error=str(e))
raise
def add_conversation_entry(
self,
session_id: str,

View File

@@ -463,7 +463,7 @@ Set-Cookie: coc_session=<session_token>; HttpOnly; Secure; SameSite=Lax; Max-Age
| | |
|---|---|
| **Endpoint** | `DELETE /api/v1/characters/<id>` |
| **Description** | Delete character (soft delete - marks as inactive) |
| **Description** | Permanently delete character and all associated game sessions |
| **Auth Required** | Yes |
**Response (200 OK):**

View File

@@ -634,7 +634,7 @@ curl -X POST http://localhost:5000/api/v1/characters \
**Endpoint:** `DELETE /api/v1/characters/<character_id>`
**Description:** Soft-delete a character (marks as inactive rather than removing).
**Description:** Permanently delete a character from the database. Also cleans up all associated game sessions to prevent orphaned data.
**Request:**