Generating calendar events with iCalendar on Remix

Main points about ICalendar

An ICalendar file might look like:

BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//kiv.software/docurenew v1.0//EN
BEGIN:VEVENT
UID:docurenew.kiv.software-main-5
CREATED:20220509T133807Z
DTSTAMP:20220509T133809Z
SEQUENCE:1652103489017
DTSTART:20240509T080000Z
DURATION:PT1H
SUMMARY:fake passport expires today! Hope you renewed it when I reminded you 1 year ago...
END:VEVENT
BEGIN:VEVENT
UID:docurenew.kiv.software-reminder-5
CREATED:20220509T133807Z
DTSTAMP:20220509T133809Z
SEQUENCE:1652103489017
DTSTART:20230509T080000Z
DURATION:PT1H
SUMMARY:fake passport expires in 1 year time! Get to it!
END:VEVENT
END:VCALENDAR

Server-side generation with Remix

We can, for example, generate the calendar events dynamically by using a resource route(eg: calendar[.]ics.ts) like so:

import { Response  } from "@remix-run/node";
import type { LoaderFunction } from '@remix-run/node';
import * as data_model from "~/data_model";    
import { getCurrentSession } from "~/session.server";
import * as utils from "~/utils";

export const loader: LoaderFunction = async ({ params, request }) =>{

    const { id } = params;
    if(!id) throw new Response("id is required", {status: 400});

    const session = await getCurrentSession(request);

    const user = data_model.getUser(session.get('userId'))
    if(!user) throw new Response("user not found", {status: 404});

    const document = await data_model.getDocumentById(id, user.id);
    if(!document) throw new Response("document not found", {status: 404});

    const filename = `${document.name.replace(/\s+/g, '-')}-calendar-event.ics`;

    console.log({ document })

    let update_sequence = Date.now();

    let ics = `
BEGIN:VCALENDAR
VERSION:2.0
CALSCALE:GREGORIAN
PRODID:-//kiv.software/docurenew v1.0//EN
BEGIN:VEVENT
UID:docurenew.kiv.software-main-${document.id}
CREATED:${utils.dateToUtcTimestamp(document.created_at)}
DTSTAMP:${utils.dateToUtcTimestamp('now')}
SEQUENCE:${update_sequence}
DTSTART:${document.expiration_date_tz}
DURATION:PT1H
SUMMARY:${document.name} expires today! Hope you renewed it when I reminded you ${document.remind_before} ago...
END:VEVENT
BEGIN:VEVENT
UID:docurenew.kiv.software-reminder-${document.id}
CREATED:${utils.dateToUtcTimestamp(document.created_at)}
DTSTAMP:${utils.dateToUtcTimestamp('now')}
SEQUENCE:${update_sequence}
DTSTART:${document.remind_date_tz}
DURATION:PT1H
SUMMARY:${document.name} expires in ${document.remind_before} time! Get to it!
END:VEVENT
END:VCALENDAR
    `.trim();

    return new Response(ics, {
        headers: {
            'Content-Type': 'text/calendar',
            'Content-Disposition': `attachment; filename="${filename}"` 
        }
    })
}

Here we add the dynamic data to the calendar event, add the proper Content-Type header and Content-Disposition header to be able to set the name of the generated file. Upon navigating to the URL(eg /documents/{id}/calendar.ics) the file will be downloaded and then we can open it with the default calendar app to confirm and save the events.

Topics: TS REMIX