Files
calchat/apps/server/src/controllers/EventController.ts
Linus Waldowsky bf8bb3cfb8
Some checks failed
continuous-integration/drone/push Build encountered an error
feat: add Drone CI pipelines, Jest unit tests, and Prettier check
Add Drone CI with server build/test and format check pipelines.
Add unit tests for password utils and recurrenceExpander.
Add check_format script, fix Jest config to ignore dist/,
remove dead CaldavService.test.ts, apply Prettier formatting.
2026-02-24 12:43:31 +01:00

185 lines
5.4 KiB
TypeScript

import { Response } from "express";
import { CalendarEvent, RecurringDeleteMode } from "@calchat/shared";
import { EventService } from "../services";
import { createLogger } from "../logging";
import { AuthenticatedRequest } from "./AuthMiddleware";
import { CaldavService } from "../services/CaldavService";
const log = createLogger("EventController");
export class EventController {
constructor(
private eventService: EventService,
private caldavService: CaldavService,
) {}
private async pushToCaldav(userId: string, event: CalendarEvent) {
if (await this.caldavService.getConfig(userId)) {
try {
await this.caldavService.pushEvent(userId, event);
} catch (error) {
log.error({ err: error, userId }, "Error pushing event to CalDAV");
}
}
}
private async deleteFromCaldav(userId: string, event: CalendarEvent) {
if (event.caldavUUID && (await this.caldavService.getConfig(userId))) {
try {
await this.caldavService.deleteEvent(userId, event.caldavUUID);
} catch (error) {
log.error({ err: error, userId }, "Error deleting event from CalDAV");
}
}
}
async create(req: AuthenticatedRequest, res: Response): Promise<void> {
try {
const userId = req.user!.userId;
const event = await this.eventService.create(userId, req.body);
await this.pushToCaldav(userId, event);
res.status(201).json(event);
} catch (error) {
log.error(
{ err: error, userId: req.user?.userId },
"Error creating event",
);
res.status(500).json({ error: "Failed to create event" });
}
}
async getById(req: AuthenticatedRequest, res: Response): Promise<void> {
try {
const event = await this.eventService.getById(
req.params.id,
req.user!.userId,
);
if (!event) {
res.status(404).json({ error: "Event not found" });
return;
}
res.json(event);
} catch (error) {
log.error({ err: error, eventId: req.params.id }, "Error getting event");
res.status(500).json({ error: "Failed to get event" });
}
}
async getAll(req: AuthenticatedRequest, res: Response): Promise<void> {
try {
const events = await this.eventService.getAll(req.user!.userId);
res.json(events);
} catch (error) {
log.error(
{ err: error, userId: req.user?.userId },
"Error getting events",
);
res.status(500).json({ error: "Failed to get events" });
}
}
async getByDateRange(
req: AuthenticatedRequest,
res: Response,
): Promise<void> {
try {
const { start, end } = req.query;
if (!start || !end) {
res.status(400).json({ error: "start and end query params required" });
return;
}
const startDate = new Date(start as string);
const endDate = new Date(end as string);
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
res.status(400).json({ error: "Invalid date format" });
return;
}
const events = await this.eventService.getByDateRange(
req.user!.userId,
startDate,
endDate,
);
res.json(events);
} catch (error) {
log.error(
{ err: error, start: req.query.start, end: req.query.end },
"Error getting events by range",
);
res.status(500).json({ error: "Failed to get events" });
}
}
async update(req: AuthenticatedRequest, res: Response): Promise<void> {
try {
const userId = req.user!.userId;
const event = await this.eventService.update(
req.params.id,
userId,
req.body,
);
if (!event) {
res.status(404).json({ error: "Event not found" });
return;
}
await this.pushToCaldav(userId, event);
res.json(event);
} catch (error) {
log.error({ err: error, eventId: req.params.id }, "Error updating event");
res.status(500).json({ error: "Failed to update event" });
}
}
async delete(req: AuthenticatedRequest, res: Response): Promise<void> {
try {
const userId = req.user!.userId;
const { mode, occurrenceDate } = req.query as {
mode?: RecurringDeleteMode;
occurrenceDate?: string;
};
// Fetch event before deletion to get caldavUUID for sync
const event = await this.eventService.getById(req.params.id, userId);
if (!event) {
res.status(404).json({ error: "Event not found" });
return;
}
// If mode is specified, use deleteRecurring
if (mode) {
const result = await this.eventService.deleteRecurring(
req.params.id,
userId,
mode,
occurrenceDate,
);
// Event was updated (single/future mode) - push update to CalDAV
if (result) {
await this.pushToCaldav(userId, result);
res.json(result);
return;
}
// Event was fully deleted (all mode, or future from first occurrence)
await this.deleteFromCaldav(userId, event);
res.status(204).send();
return;
}
// Default behavior: delete completely
await this.eventService.delete(req.params.id, userId);
await this.deleteFromCaldav(userId, event);
res.status(204).send();
} catch (error) {
log.error({ err: error, eventId: req.params.id }, "Error deleting event");
res.status(500).json({ error: "Failed to delete event" });
}
}
}