feat: implement chat persistence with MongoDB

- Add full chat persistence to database (conversations and messages)
- Implement MongoChatRepository with cursor-based pagination
- Add getConversations/getConversation endpoints in ChatController
- Save user and assistant messages in ChatService.processMessage()
- Track respondedAction (confirm/reject) on proposed event messages
- Load existing messages on chat screen mount
- Add addMessages() bulk action and chatMessageToMessageData() helper to ChatStore
- Add RespondedAction type and UpdateMessageDTO to shared types
This commit is contained in:
2026-01-09 16:21:01 +01:00
parent d86b18173f
commit c897b6d680
11 changed files with 245 additions and 45 deletions

View File

@@ -9,6 +9,7 @@ import {
CreateEventDTO,
UpdateEventDTO,
EventAction,
CreateMessageDTO,
} from "@caldav/shared";
import { ChatRepository, EventRepository, AIProvider } from "./interfaces";
import { getWeeksOverview, getMonthOverview } from "../utils/eventFormatters";
@@ -252,6 +253,18 @@ export class ChatService {
userId: string,
data: SendMessageDTO,
): Promise<ChatResponse> {
let conversationId = data.conversationId;
if (!conversationId) {
const conversation = await this.chatRepo.createConversation(userId);
conversationId = conversation.id;
}
// Save user message
await this.chatRepo.createMessage(conversationId, {
sender: "user",
content: data.content,
});
const response = await getTestResponse(
responseIndex,
this.eventRepo,
@@ -259,15 +272,14 @@ export class ChatService {
);
responseIndex++;
const message: ChatMessage = {
id: Date.now().toString(),
conversationId: data.conversationId || "temp-conv-id",
// Save and then return assistant response
const answerMessage = await this.chatRepo.createMessage(conversationId, {
sender: "assistant",
content: response.content,
proposedChange: response.proposedChange,
};
});
return { message, conversationId: message.conversationId };
return { message: answerMessage, conversationId: conversationId };
}
async confirmEvent(
@@ -279,6 +291,10 @@ export class ChatService {
eventId?: string,
updates?: UpdateEventDTO,
): Promise<ChatResponse> {
// Update original message with respondedAction
await this.chatRepo.updateMessage(messageId, { respondedAction: "confirm" });
// Perform the actual event operation
let content: string;
if (action === "create" && event) {
@@ -298,12 +314,12 @@ export class ChatService {
content = "Ungültige Aktion.";
}
const message: ChatMessage = {
id: Date.now().toString(),
conversationId,
// Save response message to DB
const message = await this.chatRepo.createMessage(conversationId, {
sender: "assistant",
content,
};
});
return { message, conversationId };
}
@@ -312,17 +328,34 @@ export class ChatService {
conversationId: string,
messageId: string,
): Promise<ChatResponse> {
const message: ChatMessage = {
id: Date.now().toString(),
conversationId,
// Update original message with respondedAction
await this.chatRepo.updateMessage(messageId, { respondedAction: "reject" });
// Save response message to DB
const message = await this.chatRepo.createMessage(conversationId, {
sender: "assistant",
content: "Der Vorschlag wurde abgelehnt.",
};
});
return { message, conversationId };
}
async getConversations(userId: string): Promise<ConversationSummary[]> {
throw new Error("Not implemented");
const conversations = await this.chatRepo.getConversationsByUser(userId);
// For each conversation, get the last message
const summaries: ConversationSummary[] = await Promise.all(
conversations.map(async (conv) => {
const messages = await this.chatRepo.getMessages(conv.id, { limit: 1 });
return {
id: conv.id,
lastMessage: messages[0],
createdAt: conv.createdAt,
};
}),
);
return summaries;
}
async getConversation(
@@ -330,6 +363,14 @@ export class ChatService {
conversationId: string,
options?: GetMessagesOptions,
): Promise<ChatMessage[]> {
throw new Error("Not implemented");
// Verify conversation belongs to user
const conversations = await this.chatRepo.getConversationsByUser(userId);
const conversation = conversations.find((c) => c.id === conversationId);
if (!conversation) {
throw new Error("Conversation not found");
}
return this.chatRepo.getMessages(conversationId, options);
}
}