Merge pull request #87 from emrysal/main
Adds Office 365 / Outlook.com Calendar Integration
This commit is contained in:
12 files changed
+365
-110
No files matched your search
@@ -1,8 +1,6 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import prisma from '../../../lib/prisma';
|
||||
const {google} = require('googleapis');
|
||||
|
||||
const credentials = process.env.GOOGLE_API_CREDENTIALS;
|
||||
import { getBusyTimes } from '../../../lib/calendarClient';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { user } = req.query
|
||||
@@ -17,32 +15,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
}
|
||||
});
|
||||
|
||||
let availability = [];
|
||||
|
||||
authorise(getAvailability)
|
||||
|
||||
// Set up Google API credentials
|
||||
function authorise(callback) {
|
||||
const {client_secret, client_id, redirect_uris} = JSON.parse(credentials).web;
|
||||
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);
|
||||
oAuth2Client.setCredentials(currentUser.credentials[0].key);
|
||||
callback(oAuth2Client)
|
||||
}
|
||||
|
||||
function getAvailability(auth) {
|
||||
const calendar = google.calendar({version: 'v3', auth});
|
||||
calendar.freebusy.query({
|
||||
requestBody: {
|
||||
timeMin: req.query.dateFrom,
|
||||
timeMax: req.query.dateTo,
|
||||
items: [{
|
||||
"id": "primary"
|
||||
}]
|
||||
}
|
||||
}, (err, apires) => {
|
||||
if (err) return console.log('The API returned an error: ' + err);
|
||||
availability = apires.data.calendars;
|
||||
res.status(200).json(availability);
|
||||
});
|
||||
}
|
||||
const availability = await getBusyTimes(currentUser.credentials, req.query.dateFrom, req.query.dateTo);
|
||||
res.status(200).json(availability);
|
||||
}
|
||||
+14
-48
@@ -1,8 +1,6 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import prisma from '../../../lib/prisma';
|
||||
const {google} = require('googleapis');
|
||||
|
||||
const credentials = process.env.GOOGLE_API_CREDENTIALS;
|
||||
import { createEvent, CalendarEvent } from '../../../lib/calendarClient';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { user } = req.query;
|
||||
@@ -17,50 +15,18 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
}
|
||||
});
|
||||
|
||||
authorise(bookEvent);
|
||||
const evt: CalendarEvent = {
|
||||
title: 'Meeting with ' + req.body.name,
|
||||
description: req.body.notes,
|
||||
startTime: req.body.start,
|
||||
endTime: req.body.end,
|
||||
timeZone: currentUser.timeZone,
|
||||
attendees: [
|
||||
{ email: req.body.email, name: req.body.name }
|
||||
]
|
||||
};
|
||||
|
||||
// Set up Google API credentials
|
||||
function authorise(callback) {
|
||||
const {client_secret, client_id, redirect_uris} = JSON.parse(credentials).web;
|
||||
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]);
|
||||
oAuth2Client.setCredentials(currentUser.credentials[0].key);
|
||||
callback(oAuth2Client);
|
||||
}
|
||||
|
||||
function bookEvent(auth) {
|
||||
var event = {
|
||||
'summary': 'Meeting with ' + req.body.name,
|
||||
'description': req.body.notes,
|
||||
'start': {
|
||||
'dateTime': req.body.start,
|
||||
'timeZone': currentUser.timeZone,
|
||||
},
|
||||
'end': {
|
||||
'dateTime': req.body.end,
|
||||
'timeZone': currentUser.timeZone,
|
||||
},
|
||||
'attendees': [
|
||||
{'email': req.body.email},
|
||||
],
|
||||
'reminders': {
|
||||
'useDefault': false,
|
||||
'overrides': [
|
||||
{'method': 'email', 'minutes': 60}
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const calendar = google.calendar({version: 'v3', auth});
|
||||
calendar.events.insert({
|
||||
auth: auth,
|
||||
calendarId: 'primary',
|
||||
resource: event,
|
||||
}, function(err, event) {
|
||||
if (err) {
|
||||
console.log('There was an error contacting the Calendar service: ' + err);
|
||||
return;
|
||||
}
|
||||
res.status(200).json({message: 'Event created'});
|
||||
});
|
||||
}
|
||||
// TODO: for now, first integration created; primary = obvious todo; ability to change primary.
|
||||
const result = await createEvent(currentUser.credentials[0], evt);
|
||||
res.status(200).json(result);
|
||||
}
|
||||
@@ -30,8 +30,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
const authUrl = oAuth2Client.generateAuthUrl({
|
||||
access_type: 'offline',
|
||||
scope: scopes,
|
||||
// A refresh token is only returned the first time the user
|
||||
// consents to providing access. For illustration purposes,
|
||||
// setting the prompt to 'consent' will force this consent
|
||||
// every time, forcing a refresh_token to be returned.
|
||||
prompt: 'consent',
|
||||
});
|
||||
|
||||
res.status(200).json({url: authUrl});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { getSession } from 'next-auth/client';
|
||||
import prisma from '../../../../lib/prisma';
|
||||
|
||||
const scopes = ['User.Read', 'Calendars.Read', 'Calendars.ReadWrite'];
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (req.method === 'GET') {
|
||||
// Check that user is authenticated
|
||||
const session = await getSession({req: req});
|
||||
|
||||
if (!session) { res.status(401).json({message: 'You must be logged in to do this'}); return; }
|
||||
|
||||
// TODO: Add user ID to user session object
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: session.user.email,
|
||||
},
|
||||
select: {
|
||||
id: true
|
||||
}
|
||||
});
|
||||
|
||||
const hostname = 'x-forwarded-host' in req.headers ? 'https://' + req.headers['x-forwarded-host'] : 'host' in req.headers ? (req.secure ? 'https://' : 'http://') + req.headers['host'] : '';
|
||||
if ( ! hostname || ! req.headers.referer.startsWith(hostname)) {
|
||||
throw new Error('Unable to determine external url, check server settings');
|
||||
}
|
||||
|
||||
function generateAuthUrl() {
|
||||
return 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize?response_type=code&scope=' + scopes.join(' ') + '&client_id=' + process.env.MS_GRAPH_CLIENT_ID + '&redirect_uri=' + hostname + '/api/integrations/office365calendar/callback';
|
||||
}
|
||||
|
||||
res.status(200).json({url: generateAuthUrl() });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { getSession } from 'next-auth/client';
|
||||
import prisma from '../../../../lib/prisma';
|
||||
const scopes = ['offline_access', 'Calendars.Read', 'Calendars.ReadWrite'];
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { code } = req.query;
|
||||
|
||||
// Check that user is authenticated
|
||||
const session = await getSession({req: req});
|
||||
if (!session) { res.status(401).json({message: 'You must be logged in to do this'}); return; }
|
||||
|
||||
// TODO: Add user ID to user session object
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: session.user.email,
|
||||
},
|
||||
select: {
|
||||
id: true
|
||||
}
|
||||
});
|
||||
|
||||
const toUrlEncoded = payload => Object.keys(payload).map( (key) => key + '=' + encodeURIComponent(payload[ key ]) ).join('&');
|
||||
const hostname = 'x-forwarded-host' in req.headers ? 'https://' + req.headers['x-forwarded-host'] : 'host' in req.headers ? (req.secure ? 'https://' : 'http://') + req.headers['host'] : '';
|
||||
|
||||
const body = toUrlEncoded({ client_id: process.env.MS_GRAPH_CLIENT_ID, grant_type: 'authorization_code', code, scope: scopes.join(' '), redirect_uri: hostname + '/api/integrations/office365calendar/callback', client_secret: process.env.MS_GRAPH_CLIENT_SECRET });
|
||||
|
||||
const response = await fetch('https://login.microsoftonline.com/common/oauth2/v2.0/token', { method: 'POST', headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
|
||||
}, body });
|
||||
|
||||
const responseBody = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
return res.redirect('/integrations?error=' + JSON.stringify(responseBody));
|
||||
}
|
||||
|
||||
const whoami = await fetch('https://graph.microsoft.com/v1.0/me', { headers: { 'Authorization': 'Bearer ' + responseBody.access_token } });
|
||||
const graphUser = await whoami.json();
|
||||
|
||||
responseBody.email = graphUser.mail;
|
||||
responseBody.expiry_date = Math.round((+(new Date()) / 1000) + responseBody.expires_in); // set expiry date in seconds
|
||||
delete responseBody.expires_in;
|
||||
|
||||
const credential = await prisma.credential.create({
|
||||
data: {
|
||||
type: 'office365_calendar',
|
||||
key: responseBody,
|
||||
userId: user.id
|
||||
}
|
||||
});
|
||||
|
||||
return res.redirect('/integrations');
|
||||
}
|
||||
Reference in new issue
Block a user