Error objects logged as { error } were serialized as {} because pino
only applies its error serializer to the err key.
197 lines
5.3 KiB
TypeScript
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" });
|
|
}
|
|
}
|
|
}
|