Some checks failed
continuous-integration/drone/push Build encountered an error
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.
185 lines
5.4 KiB
TypeScript
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" });
|
|
}
|
|
}
|
|
}
|