Skip to content

Commit e08fe14

Browse files
committed
Update event RSVPs
1 parent 1b97c7a commit e08fe14

3 files changed

Lines changed: 100 additions & 2 deletions

File tree

sources/google-calendar/src/google-calendar.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ import GoogleContacts from "@plotday/source-google-contacts";
22
import {
33
ActionType,
44
type Action,
5+
type ActorId,
6+
type Actor,
57
ConferencingProvider,
68
type NewLinkWithNotes,
79
type NewContact,
8-
type NewNote,
910
Source,
11+
type Thread,
1012
type ToolBuilder,
1113
} from "@plotday/twister";
14+
import type { ScheduleContactStatus } from "@plotday/twister/schedule";
1215
import type {
1316
NewScheduleContact,
1417
NewScheduleOccurrence,
@@ -1096,6 +1099,44 @@ export class GoogleCalendar extends Source<GoogleCalendar> {
10961099
}
10971100
}
10981101

1102+
/**
1103+
* Called when a user changes their RSVP status in Plot.
1104+
* Syncs the change back to Google Calendar via actAs().
1105+
*/
1106+
async onScheduleContactUpdated(
1107+
thread: Thread,
1108+
_scheduleId: string,
1109+
_contactId: ActorId,
1110+
status: ScheduleContactStatus | null,
1111+
actor: Actor
1112+
): Promise<void> {
1113+
const meta = thread.meta as Record<string, unknown> | null;
1114+
const linkSource = meta?.linkSource as string | null;
1115+
const calendarId = meta?.syncableId as string | null;
1116+
1117+
if (!linkSource || !calendarId) return;
1118+
1119+
// Extract event ID from source format "google-calendar:<eventId>"
1120+
const eventId = linkSource.replace(/^google-calendar:/, "");
1121+
if (!eventId || eventId === linkSource) return;
1122+
1123+
// Map Plot status to Google Calendar status
1124+
const googleStatus = status === "attend" ? "accepted" as const
1125+
: status === "skip" ? "declined" as const
1126+
: "needsAction" as const;
1127+
1128+
await this.tools.integrations.actAs(
1129+
AuthProvider.Google,
1130+
actor.id,
1131+
thread.id,
1132+
this.syncActorRSVP,
1133+
calendarId,
1134+
eventId,
1135+
googleStatus,
1136+
actor.id as string
1137+
);
1138+
}
1139+
10991140
/**
11001141
* Update RSVP status for the authenticated user on a Google Calendar event.
11011142
* Looks up the user's email from the calendar API to find the correct attendee.

sources/outlook-calendar/src/outlook-calendar.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import {
22
type Action,
33
ActionType,
4+
type Actor,
45
type ActorId,
56
ConferencingProvider,
67
type ContentType,
78
type NewLinkWithNotes,
89
type NewContact,
910
Source,
11+
type Thread,
1012
type ToolBuilder,
1113
} from "@plotday/twister";
14+
import type { ScheduleContactStatus } from "@plotday/twister/schedule";
1215
import type {
1316
NewScheduleContact,
1417
NewScheduleOccurrence,
@@ -726,6 +729,44 @@ export class OutlookCalendar extends Source<OutlookCalendar> {
726729
await this.runTask(callback);
727730
}
728731

732+
/**
733+
* Called when a user changes their RSVP status in Plot.
734+
* Syncs the change back to Outlook Calendar via actAs().
735+
*/
736+
async onScheduleContactUpdated(
737+
thread: Thread,
738+
_scheduleId: string,
739+
_contactId: ActorId,
740+
status: ScheduleContactStatus | null,
741+
actor: Actor
742+
): Promise<void> {
743+
const meta = thread.meta as Record<string, unknown> | null;
744+
const linkSource = meta?.linkSource as string | null;
745+
const calendarId = meta?.syncableId as string | null;
746+
747+
if (!linkSource || !calendarId) return;
748+
749+
// Extract event ID from source format "outlook-calendar:<eventId>"
750+
const eventId = linkSource.replace(/^outlook-calendar:/, "");
751+
if (!eventId || eventId === linkSource) return;
752+
753+
// Map Plot status to Outlook Calendar status
754+
const outlookStatus = status === "attend" ? "accepted" as const
755+
: status === "skip" ? "declined" as const
756+
: "tentativelyAccepted" as const;
757+
758+
await this.tools.integrations.actAs(
759+
AuthProvider.Microsoft,
760+
actor.id,
761+
thread.id,
762+
this.syncActorRSVP,
763+
calendarId,
764+
eventId,
765+
outlookStatus,
766+
actor.id as string
767+
);
768+
}
769+
729770
/**
730771
* Sync a schedule contact RSVP change back to Outlook Calendar.
731772
* Called via actAs() which provides the actor's auth token.

twister/src/source.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { type Actor, type Link, type Note, type Thread } from "./plot";
1+
import { type Actor, type ActorId, type Link, type Note, type Thread } from "./plot";
2+
import type { ScheduleContactStatus } from "./schedule";
23
import {
34
type AuthProvider,
45
type AuthToken,
@@ -164,6 +165,21 @@ export abstract class Source<TSelf> extends Twist<TSelf> {
164165
return Promise.resolve();
165166
}
166167

168+
/**
169+
* Called when a schedule contact's RSVP status changes on a thread owned by this source.
170+
* Override to sync RSVP changes back to the external calendar.
171+
*
172+
* @param thread - The thread (includes thread.meta with source-specific data)
173+
* @param scheduleId - The schedule ID
174+
* @param contactId - The contact whose status changed
175+
* @param status - The new RSVP status ('attend', 'skip', or null)
176+
* @param actor - The user who changed the status
177+
*/
178+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
179+
onScheduleContactUpdated(thread: Thread, scheduleId: string, contactId: ActorId, status: ScheduleContactStatus | null, actor: Actor): Promise<void> {
180+
return Promise.resolve();
181+
}
182+
167183
// ---- Activation ----
168184

169185
/**

0 commit comments

Comments
 (0)