fix(conversations): implement soft-delete for conversation deletion

The delete conversation endpoint was a no-op — it verified ownership but
never actually modified the record. Users saw conversations disappear
(frontend optimistic removal) but they reappeared on refresh.

Changes:
- conversation.entity.ts: Add DELETED status, softDelete() and isDeleted()
- conversation.service.ts: Call softDelete() + update instead of no-op
- conversation-postgres.repository.ts: Exclude DELETED conversations
  from findByUserId() queries so they don't appear in user's list

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-02-06 20:49:03 -08:00
parent b75d607e2b
commit d083008001
3 changed files with 15 additions and 6 deletions

View File

@ -38,7 +38,8 @@ export class ConversationPostgresRepository
options?: { status?: ConversationStatusType; limit?: number },
): Promise<ConversationEntity[]> {
const queryBuilder = this.createTenantQueryBuilder('conversation')
.andWhere('conversation.user_id = :userId', { userId });
.andWhere('conversation.user_id = :userId', { userId })
.andWhere('conversation.status != :deletedStatus', { deletedStatus: 'DELETED' });
if (options?.status) {
queryBuilder.andWhere('conversation.status = :status', { status: options.status });

View File

@ -308,11 +308,9 @@ export class ConversationService {
* Delete a conversation and its messages
*/
async deleteConversation(conversationId: string, userId: string): Promise<void> {
// Verify user owns the conversation
await this.getConversation(conversationId, userId);
// Note: In a real application, you'd want to delete messages in the repository
// For now, we rely on database cascade or separate cleanup
const conversation = await this.getConversation(conversationId, userId);
conversation.softDelete();
await this.conversationRepo.update(conversation);
}
/**

View File

@ -5,6 +5,7 @@ export const ConversationStatus = {
ACTIVE: 'ACTIVE',
ENDED: 'ENDED',
ARCHIVED: 'ARCHIVED',
DELETED: 'DELETED',
} as const;
export type ConversationStatusType =
@ -236,6 +237,11 @@ export class ConversationEntity {
this.updatedAt = new Date();
}
softDelete(): void {
this.status = ConversationStatus.DELETED;
this.updatedAt = new Date();
}
isActive(): boolean {
return this.status === ConversationStatus.ACTIVE;
}
@ -243,4 +249,8 @@ export class ConversationEntity {
isEnded(): boolean {
return this.status === ConversationStatus.ENDED;
}
isDeleted(): boolean {
return this.status === ConversationStatus.DELETED;
}
}