import Event from "../../models/event"; import Member from "../../models/member"; export default defineEventHandler(async (event) => { const query = getQuery(event); const { memberId } = query; if (!memberId) { throw createError({ statusCode: 400, statusMessage: "Member ID is required", }); } try { // Verify member exists const member = await Member.findById(memberId); if (!member) { throw createError({ statusCode: 404, statusMessage: "Member not found", }); } // Find all events where the user is registered const events = await Event.find({ "registrations.memberId": memberId, isCancelled: { $ne: true }, }) .select("title slug description startDate endDate location") .sort({ startDate: 1 }); // Generate iCal format const ical = generateICalendar(events, member); // Set headers for calendar subscription (not download) setHeader(event, "Content-Type", "text/calendar; charset=utf-8"); setHeader(event, "Cache-Control", "no-cache, no-store, must-revalidate"); setHeader(event, "Pragma", "no-cache"); setHeader(event, "Expires", "0"); return ical; } catch (error) { console.error("Error generating calendar:", error); if (error.statusCode) { throw error; } throw createError({ statusCode: 500, statusMessage: "Failed to generate calendar", }); } }); function generateICalendar(events, member) { const now = new Date(); const timestamp = now .toISOString() .replace(/[-:]/g, "") .replace(/\.\d{3}/, ""); let ical = [ "BEGIN:VCALENDAR", "VERSION:2.0", "PRODID:-//Ghost Guild//Events Calendar//EN", "CALSCALE:GREGORIAN", "METHOD:PUBLISH", "X-WR-CALNAME:Ghost Guild - My Events", "X-WR-TIMEZONE:UTC", "X-WR-CALDESC:Your registered Ghost Guild events", "REFRESH-INTERVAL;VALUE=DURATION:PT1H", "X-PUBLISHED-TTL:PT1H", ]; events.forEach((evt) => { const eventStart = new Date(evt.startDate); const eventEnd = new Date(evt.endDate); const dtstart = eventStart .toISOString() .replace(/[-:]/g, "") .replace(/\.\d{3}/, ""); const dtend = eventEnd .toISOString() .replace(/[-:]/g, "") .replace(/\.\d{3}/, ""); const dtstamp = timestamp; // Clean description for iCal format const description = (evt.description || "") .replace(/\n/g, "\\n") .replace(/,/g, "\\,"); const eventUrl = `https://ghostguild.org/events/${evt.slug || evt._id}`; ical.push("BEGIN:VEVENT"); ical.push(`UID:${evt._id}@ghostguild.org`); ical.push(`DTSTAMP:${dtstamp}`); ical.push(`DTSTART:${dtstart}`); ical.push(`DTEND:${dtend}`); ical.push(`SUMMARY:${evt.title}`); ical.push(`DESCRIPTION:${description}\\n\\nView event: ${eventUrl}`); ical.push(`LOCATION:${evt.location || "Online"}`); ical.push(`URL:${eventUrl}`); ical.push(`STATUS:CONFIRMED`); ical.push("END:VEVENT"); }); ical.push("END:VCALENDAR"); return ical.join("\r\n"); }