mcp-memory-libsql
by spences10
Verified
import { google } from 'googleapis';
import { BaseGoogleService, GoogleServiceError } from '../base/BaseGoogleService.js';
import {
GetEventsParams,
CreateEventParams,
EventResponse,
CreateEventResponse,
CalendarModuleConfig
} from '../../modules/calendar/types.js';
/**
* Google Calendar Service Implementation extending BaseGoogleService.
* Provides calendar functionality while leveraging common Google API patterns.
*/
export class CalendarService extends BaseGoogleService<ReturnType<typeof google.calendar>> {
constructor(config?: CalendarModuleConfig) {
super({
serviceName: 'calendar',
version: 'v3'
});
}
/**
* Get an authenticated Calendar client
*/
private async getCalendarClient(email: string) {
return this.getAuthenticatedClient(
email,
(auth) => google.calendar({ version: 'v3', auth })
);
}
/**
* Retrieve calendar events with optional filtering
*/
async getEvents({ email, query, maxResults = 10, timeMin, timeMax }: GetEventsParams): Promise<EventResponse[]> {
try {
const calendar = await this.getCalendarClient(email);
// Prepare search parameters
const params: any = {
calendarId: 'primary',
maxResults,
singleEvents: true,
orderBy: 'startTime'
};
if (query) {
params.q = query;
}
if (timeMin) {
params.timeMin = new Date(timeMin).toISOString();
}
if (timeMax) {
params.timeMax = new Date(timeMax).toISOString();
}
// List events matching criteria
const { data } = await calendar.events.list(params);
if (!data.items || data.items.length === 0) {
return [];
}
// Map response to our EventResponse type
return data.items.map(event => ({
id: event.id!,
summary: event.summary || '',
description: event.description || undefined,
start: {
dateTime: event.start?.dateTime || event.start?.date || '',
timeZone: event.start?.timeZone || 'UTC'
},
end: {
dateTime: event.end?.dateTime || event.end?.date || '',
timeZone: event.end?.timeZone || 'UTC'
},
attendees: event.attendees?.map(attendee => ({
email: attendee.email!,
responseStatus: attendee.responseStatus || undefined
})),
organizer: event.organizer ? {
email: event.organizer.email!,
self: event.organizer.self || false
} : undefined
}));
} catch (error) {
throw this.handleError(error, 'Failed to get events');
}
}
/**
* Retrieve a single calendar event by ID
*/
async getEvent(email: string, eventId: string): Promise<EventResponse> {
try {
const calendar = await this.getCalendarClient(email);
const { data: event } = await calendar.events.get({
calendarId: 'primary',
eventId
});
if (!event) {
throw new GoogleServiceError(
'Event not found',
'NOT_FOUND',
`No event found with ID: ${eventId}`
);
}
return {
id: event.id!,
summary: event.summary || '',
description: event.description || undefined,
start: {
dateTime: event.start?.dateTime || event.start?.date || '',
timeZone: event.start?.timeZone || 'UTC'
},
end: {
dateTime: event.end?.dateTime || event.end?.date || '',
timeZone: event.end?.timeZone || 'UTC'
},
attendees: event.attendees?.map(attendee => ({
email: attendee.email!,
responseStatus: attendee.responseStatus || undefined
})),
organizer: event.organizer ? {
email: event.organizer.email!,
self: event.organizer.self || false
} : undefined
};
} catch (error) {
throw this.handleError(error, 'Failed to get event');
}
}
/**
* Create a new calendar event
*/
async createEvent({ email, summary, description, start, end, attendees }: CreateEventParams): Promise<CreateEventResponse> {
try {
const calendar = await this.getCalendarClient(email);
const eventData = {
summary,
description,
start,
end,
attendees: attendees?.map(attendee => ({ email: attendee.email }))
};
const { data: event } = await calendar.events.insert({
calendarId: 'primary',
requestBody: eventData,
sendUpdates: 'all' // Send emails to attendees
});
if (!event.id || !event.summary) {
throw new GoogleServiceError(
'Failed to create event',
'CREATE_ERROR',
'Event creation response was incomplete'
);
}
return {
id: event.id,
summary: event.summary,
htmlLink: event.htmlLink || ''
};
} catch (error) {
throw this.handleError(error, 'Failed to create event');
}
}
}