Files
calchat/apps/server/src/controllers/ChatController.ts
Linus Waldowsky 5a9485acfc fix: use pino err key for proper Error serialization in controllers
Error objects logged as { error } were serialized as {} because pino
only applies its error serializer to the err key.
2026-02-09 22:41:46 +01:00

197 lines
5.3 KiB
TypeScript

import { Response } from "express";
import {
SendMessageDTO,
CreateEventDTO,
UpdateEventDTO,
EventAction,
GetMessagesOptions,
RecurringDeleteMode,
} from "@calchat/shared";
import { ChatService } from "../services";
import { CaldavService } from "../services/CaldavService";
import { createLogger } from "../logging";
import { AuthenticatedRequest } from "./AuthMiddleware";
const log = createLogger("ChatController");
export class ChatController {
constructor(
private chatService: ChatService,
private caldavService: CaldavService,
) {}
async sendMessage(req: AuthenticatedRequest, res: Response): Promise<void> {
try {
const userId = req.user!.userId;
const data: SendMessageDTO = req.body;
const response = await this.chatService.processMessage(userId, data);
res.json(response);
} catch (error) {
log.error(
{ err: error, userId: req.user?.userId },
"Error processing message",
);
res.status(500).json({ error: "Failed to process message" });
}
}
async confirmEvent(req: AuthenticatedRequest, res: Response): Promise<void> {
try {
const userId = req.user!.userId;
const { conversationId, messageId } = req.params;
// DEBUG: Log incoming request body to trace deleteMode issue
log.debug({ body: req.body }, "confirmEvent request body");
const {
proposalId,
action,
event,
eventId,
updates,
deleteMode,
occurrenceDate,
} = req.body as {
proposalId: string;
action: EventAction;
event?: CreateEventDTO;
eventId?: string;
updates?: UpdateEventDTO;
deleteMode?: RecurringDeleteMode;
occurrenceDate?: string;
};
const response = await this.chatService.confirmEvent(
userId,
conversationId,
messageId,
proposalId,
action,
event,
eventId,
updates,
deleteMode,
occurrenceDate,
);
// Sync confirmed event to CalDAV
try {
if (await this.caldavService.getConfig(userId)) {
await this.caldavService.pushAll(userId);
}
} catch (error) {
log.error({ err: error, userId }, "CalDAV push after confirm failed");
}
res.json(response);
} catch (error) {
log.error(
{ err: error, conversationId: req.params.conversationId },
"Error confirming event",
);
res.status(500).json({ error: "Failed to confirm event" });
}
}
async rejectEvent(req: AuthenticatedRequest, res: Response): Promise<void> {
try {
const userId = req.user!.userId;
const { conversationId, messageId } = req.params;
const { proposalId } = req.body as { proposalId: string };
const response = await this.chatService.rejectEvent(
userId,
conversationId,
messageId,
proposalId,
);
res.json(response);
} catch (error) {
log.error(
{ err: error, conversationId: req.params.conversationId },
"Error rejecting event",
);
res.status(500).json({ error: "Failed to reject event" });
}
}
async getConversations(
req: AuthenticatedRequest,
res: Response,
): Promise<void> {
try {
const userId = req.user!.userId;
const conversations = await this.chatService.getConversations(userId);
res.json(conversations);
} catch (error) {
log.error(
{ err: error, userId: req.user?.userId },
"Error getting conversations",
);
res.status(500).json({ error: "Failed to get conversations" });
}
}
async getConversation(
req: AuthenticatedRequest,
res: Response,
): Promise<void> {
try {
const userId = req.user!.userId;
const { id } = req.params;
const { before, limit } = req.query as {
before?: string;
limit?: string;
};
const options: GetMessagesOptions = {};
if (before) options.before = before;
if (limit) options.limit = parseInt(limit, 10);
const messages = await this.chatService.getConversation(
userId,
id,
options,
);
res.json(messages);
} catch (error) {
if ((error as Error).message === "Conversation not found") {
res.status(404).json({ error: "Conversation not found" });
} else {
log.error(
{ err: error, conversationId: req.params.id },
"Error getting conversation",
);
res.status(500).json({ error: "Failed to get conversation" });
}
}
}
async updateProposalEvent(
req: AuthenticatedRequest,
res: Response,
): Promise<void> {
try {
const { messageId } = req.params;
const { proposalId, event } = req.body as {
proposalId: string;
event: CreateEventDTO;
};
const message = await this.chatService.updateProposalEvent(
messageId,
proposalId,
event,
);
if (message) {
res.json(message);
} else {
res.status(404).json({ error: "Message or proposal not found" });
}
} catch (error) {
log.error(
{ err: error, messageId: req.params.messageId },
"Error updating proposal event",
);
res.status(500).json({ error: "Failed to update proposal event" });
}
}
}