Add public/private calendars, full iCal support, and iCal URL import
- Public/private: toggle is_public via PUT /calendars/{id}; generate/clear
public_token and return ical_url when public
- Public feed: GET /cal/{token}/feed.ics (no auth) for subscription in
Google/Apple/Outlook calendars
- Full iCal export: use golang-ical; VALARM, ATTENDEE, all-day (VALUE=DATE),
RRULE, DTSTAMP, CREATED, LAST-MODIFIED
- Full iCal import: parse TZID, VALUE=DATE, VALARM, ATTENDEE, RRULE
- Import from URL: POST /calendars/import-url with calendar_id + url
- Migration: unique index on public_token, calendar_subscriptions table
- Config: BASE_URL for ical_url; Calendar model + API: ical_url field
- Docs: OpenAPI, llms.txt, README, SKILL.md, about/overview
Made-with: Cursor
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
"get": {
|
||||
"tags": ["ICS"],
|
||||
"summary": "Export calendar as ICS",
|
||||
"description": "Exports all events from a calendar in ICS (iCalendar) format. Requires `calendars:read` scope.",
|
||||
"description": "Exports all events from a calendar in ICS (iCalendar) format with full RFC 5545 support including reminders (VALARM), attendees, all-day events, and recurrence rules. Requires `calendars:read` scope.",
|
||||
"operationId": "exportCalendarICS",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -34,7 +34,7 @@
|
||||
"post": {
|
||||
"tags": ["ICS"],
|
||||
"summary": "Import an ICS file",
|
||||
"description": "Imports events from an ICS file into a specified calendar. The file is sent as multipart form data. Requires `calendars:write` scope.",
|
||||
"description": "Imports events from an ICS file into a specified calendar. Supports VALARM (reminders), ATTENDEE, TZID, VALUE=DATE (all-day events), and RRULE. The file is sent as multipart form data. Requires `calendars:write` scope.",
|
||||
"operationId": "importCalendarICS",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
@@ -77,6 +77,84 @@
|
||||
"403": { "description": "Insufficient scope or permission", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
|
||||
}
|
||||
}
|
||||
},
|
||||
"/calendars/import-url": {
|
||||
"post": {
|
||||
"tags": ["ICS"],
|
||||
"summary": "Import from iCal URL",
|
||||
"description": "Fetches an iCal feed from the given URL and imports all events into the specified calendar. Supports http, https, and webcal protocols. Requires `calendars:write` scope.",
|
||||
"operationId": "importCalendarFromURL",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["calendar_id", "url"],
|
||||
"properties": {
|
||||
"calendar_id": { "type": "string", "format": "uuid", "description": "Target calendar ID" },
|
||||
"url": { "type": "string", "format": "uri", "description": "iCal feed URL (http, https, or webcal)", "example": "https://example.com/calendar.ics" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Import successful",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["ok", "imported", "source"],
|
||||
"properties": {
|
||||
"ok": { "type": "boolean", "example": true },
|
||||
"imported": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"events": { "type": "integer", "example": 12 }
|
||||
}
|
||||
},
|
||||
"source": { "type": "string", "format": "uri", "description": "The URL that was imported" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": { "description": "Validation error, unreachable URL, or invalid ICS", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
|
||||
"401": { "description": "Not authenticated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
|
||||
"403": { "description": "Insufficient scope or permission", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
|
||||
}
|
||||
}
|
||||
},
|
||||
"/cal/{token}/feed.ics": {
|
||||
"get": {
|
||||
"tags": ["ICS"],
|
||||
"summary": "Public iCal feed",
|
||||
"description": "Returns a public iCal feed for a calendar that has been marked as public. No authentication required. This URL can be used to subscribe to the calendar in Google Calendar, Apple Calendar, Outlook, etc. The `ical_url` is returned in the Calendar object when `is_public` is true.",
|
||||
"operationId": "publicCalendarFeed",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "token",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": { "type": "string" },
|
||||
"description": "Public calendar token (from the calendar's ical_url)"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "ICS calendar feed",
|
||||
"content": {
|
||||
"text/calendar": {
|
||||
"schema": { "type": "string" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": { "description": "Calendar not found or not public", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
|
||||
},
|
||||
"security": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
"name": { "type": "string", "minLength": 1, "maxLength": 80 },
|
||||
"color": { "type": "string", "pattern": "^#[0-9A-Fa-f]{6}$", "example": "#22C55E" },
|
||||
"is_public": { "type": "boolean" },
|
||||
"ical_url": { "type": "string", "format": "uri", "description": "Public iCal feed URL (only present when is_public is true)" },
|
||||
"role": { "type": "string", "enum": ["owner", "editor", "viewer"], "description": "Current user's role on this calendar" },
|
||||
"created_at": { "type": "string", "format": "date-time" },
|
||||
"updated_at": { "type": "string", "format": "date-time" }
|
||||
|
||||
Reference in New Issue
Block a user