UNPKG

agenda-paper

Version:

Show your daily agenda on an ePaper display

205 lines (180 loc) 5.82 kB
import { Auth, google, calendar_v3 } from 'googleapis'; import { getEnvVariableOrDie } from '../helpers/env.helper'; interface Response<T> { status: number; statusText: string; data: T; } export interface CalendarInfo { id: string; isPrimary: boolean; } export interface CalendarEvent { id: string; title: string; start: string; end: string; location?: string; isPrivate: boolean; isFree: boolean; } const SITE_URL = 'http://localhost:3000'; const API_URL = `${SITE_URL}/api`; const LOGIN_CALLBACK_URL = `${API_URL}/auth/login/callback`; function buildGoogleOptions(state?: Record<string, unknown>) { const scopes = [ 'https://www.googleapis.com/auth/calendar.readonly', 'https://www.googleapis.com/auth/calendar.events.readonly', ]; const prompts = ['select_account', 'consent']; const options = { access_type: 'offline', scope: scopes, prompt: prompts.join(' '), state: JSON.stringify(state), }; return options; } function createClient(googleRefreshToken?: string) { const GOOGLE_CLIENT_ID = getEnvVariableOrDie('GOOGLE_CLIENT_ID'); const GOOGLE_CLIENT_SECRET = getEnvVariableOrDie('GOOGLE_CLIENT_SECRET'); const client = new google.auth.OAuth2(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, LOGIN_CALLBACK_URL); if(googleRefreshToken) { const credentials = { refresh_token: googleRefreshToken, }; client.setCredentials(credentials); } return client; } function handleResponse<T>(response: Response<T>, allowedCodes?: Array<number>): T | undefined { if (response.status !== 200 && (!allowedCodes || !allowedCodes.includes(response.status))) { console.error(`google api unsuccessfull: ${response.status} - ${response.statusText}`); return undefined; } return response.data; } function logError(e: unknown, operation: string) { console.error(`error while making ${operation} google api call:\n`, e); } export const createAuthUrl = () => { const options = buildGoogleOptions(); const client = createClient(); const url = client.generateAuthUrl(options); return url; }; export const convertCodeToRefreshToken = async (code: string) => { let refreshToken; const client = createClient(); try { const { tokens } = await client.getToken(code); refreshToken = tokens.refresh_token; } catch (e) { const { response: { status, data } = {} } = <{ response?:{ status: number, data?: { error: string, error_description: string }, }, }>e; const props = [ `status: ${status || ''}`, ]; if (data) { props.push(`error: ${data.error || ''}`); props.push(`description: ${data.error_description || ''}`); } const msg = `failed to convert code to token - ${props.join(',')}`; console.error(`${msg}\n`, e); throw e; } return refreshToken; }; function getCalendarService(client: Auth.OAuth2Client) { return google.calendar({ version: 'v3', auth: client }); } function rawCalendarsToCalendars( rawCalendars: Array<calendar_v3.Schema$CalendarListEntry>, ): Array<CalendarInfo> { return rawCalendars.map((rawCalendar) => ({ id: <string>rawCalendar.id, isPrimary: !!rawCalendar.primary, })); } export const getCalendars = async ( googleRefreshToken: string, ): Promise<Array<CalendarInfo> | undefined> => { const client = createClient(googleRefreshToken); const service = getCalendarService(client); try { const response = await service.calendarList.list(); const result = handleResponse(response); return result && result.items ? rawCalendarsToCalendars(result.items) : undefined; } catch (e) { logError(e, 'get calendars'); throw e; } }; async function getEventsByQuery( client: Auth.OAuth2Client, query: calendar_v3.Params$Resource$Events$List, ): Promise<calendar_v3.Schema$Events | undefined> { const service = getCalendarService(client); const response = await service.events.list(query); return handleResponse(response); } function rawEventsToCalendarEvents(rawEvents: Array<calendar_v3.Schema$Event>): Array<CalendarEvent> { return rawEvents.map((rawEvent) => { const startDateTime = <string>(rawEvent.start || {}).dateTime; const endDateTime = <string>(rawEvent.end || {}).dateTime; return { id: <string>rawEvent.id, title: <string>rawEvent.summary, start: startDateTime, end: endDateTime, location: <string | undefined>rawEvent.location, isPrivate: rawEvent.visibility === 'private', isFree: rawEvent.transparency === 'transparent', }; }); } function didCurrentRecipientDeclined( attendees: Array<calendar_v3.Schema$EventAttendee> | undefined, ) { if (!attendees) { return false; } const currentAttendee = attendees.find((attendee) => attendee.self); if (currentAttendee && currentAttendee.responseStatus === 'declined') { return true; } return false; } function getOnlyApprovedEvents(events: calendar_v3.Schema$Event[]) { return events.filter((event) => !didCurrentRecipientDeclined(event.attendees)); } export const getEventsBetweenDateTimes = async ( googleRefreshToken: string, calendarId: string, startDateTime: string, endDateTime: string, ): Promise<Array<CalendarEvent> | undefined> => { const client = createClient(googleRefreshToken); const query: calendar_v3.Params$Resource$Events$List = { calendarId, timeMin: startDateTime, timeMax: endDateTime, singleEvents: true, orderBy: 'startTime', }; try { const result = await getEventsByQuery(client, query); if (!result || !result.items) { return undefined; } const approvedEvents = getOnlyApprovedEvents(result.items); return rawEventsToCalendarEvents(approvedEvents); } catch (e) { logError(e, 'events query'); throw e; } };