Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Appointment calendar #121

Open
wants to merge 30 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
44c5355
feat(Feature): Import the Calendar Package
BenCheng2 Feb 10, 2024
a508f6e
feat(Feature): Prepare the frontend routes
BenCheng2 Feb 10, 2024
a098e9b
feat(Feature): Prepare the backend routes
BenCheng2 Feb 10, 2024
0fd65e6
feat(Feature): Package Installation
BenCheng2 Feb 10, 2024
652d888
feat(Feature): Calendar Frontend
BenCheng2 Feb 13, 2024
61659fa
feat(Feature): Change API to POST
BenCheng2 Feb 13, 2024
daff036
feat(Feature): Change params
BenCheng2 Feb 13, 2024
9868c36
feat(Feature): Frontned Get Current User Id
BenCheng2 Feb 13, 2024
8bd1822
feat(Feature): Big Calendar Package
BenCheng2 Mar 3, 2024
3a215bc
feat(Feature): Big Calendar Package
BenCheng2 Mar 3, 2024
b8a9910
feat(Feature): Big Calendar Frontend
BenCheng2 Mar 3, 2024
9d14a72
feat(Feature): Update appointment model
BenCheng2 Mar 3, 2024
b3b8d62
feat(Feature): Update calendar query
BenCheng2 Mar 3, 2024
58a9b78
feat(Feature): Update calendar fronend component to new api
BenCheng2 Mar 3, 2024
b2bdd6f
feat(Feature): Small and Large Calendar Together
BenCheng2 Mar 3, 2024
edb66ce
feat(Feature): Small and Large Calendar Together
BenCheng2 Mar 4, 2024
93e3658
feat(Feature): Google API Connect ion
BenCheng2 Mar 10, 2024
4f6318b
feat(Feature): Fix an API
BenCheng2 Mar 13, 2024
1520247
Googeapis failure importing
BenCheng2 Mar 15, 2024
f6c4d77
Googleapis Backend Attempt
BenCheng2 Mar 19, 2024
73e0ebf
Google Calendar Progress: Authenticate, and retrieve recent events on…
BenCheng2 Mar 19, 2024
93d4026
Google Calendar Progress: Fetch google calendar's events and display …
BenCheng2 Mar 19, 2024
7616b08
Google Calendar Progress: Fix Bugs
BenCheng2 Mar 19, 2024
6a38c2c
Google Calendar Progress: Backend Global Variables for Google Verific…
BenCheng2 Mar 25, 2024
257e765
feat: Backend Storing and Obtaining Google Refresh Token
BenCheng2 Mar 27, 2024
d1504dc
feat: Double Calendar
BenCheng2 Mar 28, 2024
338c5c8
feat: Double Calendar
BenCheng2 Mar 28, 2024
b543d93
feat: Split the role of functions over Calendar
BenCheng2 Mar 28, 2024
081b379
feat: Add Google Calendar Events
BenCheng2 Mar 28, 2024
b3a8ede
feat: Google Login when refresh token outdated
BenCheng2 Mar 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion backend/loaders/express.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ const {
baseRoute, registerRoute, userRoute, forgotPasswordRoute, usersRoute, clientsRoute,
characteristicRoute, questionRoute, dynamicFormRoute, genericRoute, advancedSearchRoute, serviceProviderRoute,needRoute,
needSatisfierRoute, outcomeRoute, internalTypeRoute, serviceProvisionRoute, programProvisionRoute,
matchingRoute, partnerNetworkApiRoute, partnerNetworkPublicRoute, partnerOrganizationRoute
matchingRoute, partnerNetworkApiRoute, partnerNetworkPublicRoute, partnerOrganizationRoute,
calendarRoute
} = require('../routes');
const {authMiddleware, errorHandler} = require('../services/middleware');

Expand Down Expand Up @@ -62,6 +63,7 @@ app.use('/api', programProvisionRoute);
app.use('/api', matchingRoute);
app.use('/api', partnerNetworkApiRoute);
app.use('/api', partnerOrganizationRoute);
app.use('/api', calendarRoute)

// Authentication not required
app.use('/public', partnerNetworkPublicRoute);
Expand Down
6 changes: 5 additions & 1 deletion backend/models/appointment.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ const GDBAppointmentModel = createGraphDBModel({
person: {type: GDBPersonModel, internalKey: ':hasPerson'},
user: {type: GDBUserAccountModel, internalKey: ':withUser'},
characteristicOccurrences : {type: [GDBCOModel], internalKey: ':hasCharacteristicOccurrence'},
address: {type: GDBAddressModel, internalKey: 'ic:hasAddress'}
address: {type: GDBAddressModel, internalKey: 'ic:hasAddress'},

startDate: {type: Date, internalKey: ':hasStartDate'},
endDate: {type: Date, internalKey: ':hasEndDate'},
appointmentName: {type: String, internalKey: ':hasName'},
}, {
rdfTypes: [':Appointment'], name: 'appointment'
});
Expand Down
4 changes: 3 additions & 1 deletion backend/models/userAccount.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ const GDBUserAccountModel = createGraphDBModel({
status: {type: String, internalKey: ':hasAccountStatus'},

// Exact 3 questions, the answer should be case-insensitive.
securityQuestions: {type: [GDBSecurityQuestion], internalKey: ':hasSecurityQuestion', onDelete: DeleteType.CASCADE}
securityQuestions: {type: [GDBSecurityQuestion], internalKey: ':hasSecurityQuestion', onDelete: DeleteType.CASCADE},

googleRefreshToken: {type: String, internalKey: ':hasGoogleRefreshToken'},

}, {
rdfTypes: [':UserAccount'], name: 'userAccount'
Expand Down
3 changes: 3 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@
},
"author": "",
"dependencies": {
"@google-cloud/local-auth": "^3.0.1",
"chevrotain": "^11.0.3",
"cookie-parser": "^1.4.6",
"cookie-session": "^2.0.0",
"cors": "^2.8.5",
"dotenv": "^16.0.1",
"express": "^4.18.1",
"form-data": "^4.0.0",
"google-auth-library": "^9.7.0",
"googleapis": "^134.0.0",
"graphdb": "^2.0.0",
"graphdb-utils": "git+https://github.com/csse-uoft/graphdb-utils.git",
"jsonwebtoken": "^8.5.1",
Expand Down
14 changes: 14 additions & 0 deletions backend/routes/calendar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const express = require('express');
const {fetchAppointment} = require("../services/calendar");
const {fetchGoogleAppointments, storeGoogleAppointments, GoogleLogin, updateGoogleLogin} = require("../services/calendar/googleCalendar");
const router = express.Router();

router.post('/calendar', fetchAppointment);

router.post('/calendar_google_login', updateGoogleLogin)

router.post('/calendar_google', fetchGoogleAppointments);

router.put('/calendar_google', storeGoogleAppointments);

module.exports = router;
2 changes: 2 additions & 0 deletions backend/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ module.exports = {
partnerNetworkApiRoute: require('./partnerNetworkApi'),
partnerNetworkPublicRoute: require('./partnerNetworkPublic'),
partnerOrganizationRoute: require('./partnerOrganization'),
calendarRoute: require('./calendar'),

}
109 changes: 109 additions & 0 deletions backend/services/calendar/googleCalendar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
const {google} = require('googleapis');
const {updateUserAccount} = require("../userAccount/user");
const {findUserAccountById} = require("../userAccount/user");

require('dotenv').config();
GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID;
GOOGLE_CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET;

const oAuth2Client = new google.auth.OAuth2(
GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET,
"http://localhost:3000"
);

async function updateGoogleLogin(req, res, next){
const { codeResponse, userId } = req.body;
const currentUser = await findUserAccountById(userId); // This is a function from userAccount/user.js that finds a user by ID

const code = req.body.code;
const {tokens} = await oAuth2Client.getToken(code);
oAuth2Client.setCredentials({refresh_token: tokens.refresh_token});
const email = currentUser.primaryEmail;
await updateUserAccount(email, {googleRefreshToken: tokens.refresh_token});

}

async function fetchGoogleAppointments(req, res, next) {
const { userId } = req.body;
try {
const currentUser = await findUserAccountById(userId); // Finds a user by ID
// Check if currentUser has a googleRefreshToken
if (currentUser.googleRefreshToken) {
console.log("Found refresh token");
oAuth2Client.setCredentials({refresh_token: currentUser.googleRefreshToken});
}

const calendar = google.calendar({version: 'v3', auth: oAuth2Client});
const response = await calendar.events.list({
calendarId: 'primary',
maxResults: 2500,
singleEvents: true,
orderBy: 'startTime',
});

const events = response.data.items;
const data = events.map(event => {
const start = Date.parse(event.start.dateTime || event.start.date);
const end = Date.parse(event.end.dateTime || event.end.date);
return {startDate: start, endDate: end, appointmentName: event.summary};
});

return res.status(200).json({success: true, data});
} catch (error) {
if (error.response && error.response.data && error.response.data.error === 'invalid_grant') {
console.error("Refresh token is invalid or expired.");
return res.status(401).json({success: false, message: "Authentication failed. Please log in again."});
}
console.error("An error occurred:", error.message);
return res.status(500).json({success: false, message: "An internal error occurred."});
}
}

async function storeGoogleAppointments(req, res, next) {
const { userId } = req.body;
try {
const currentUser = await findUserAccountById(userId); // Finds a user by ID
// Check if currentUser has a googleRefreshToken
if (currentUser.googleRefreshToken) {
oAuth2Client.setCredentials({refresh_token: currentUser.googleRefreshToken});
}

const calendar = google.calendar({version: 'v3', auth: oAuth2Client});

let startDate = new Date(req.body.start);
let endDate = new Date(req.body.end);

// Add an event to the calendar
const event = {
summary: req.body.name,
description: 'This is a test of the Google Calendar API',
start: {
dateTime: startDate,
timeZone: 'America/Los_Angeles',
},
end: {
dateTime: endDate,
timeZone: 'America/Los_Angeles',
},
};

await calendar.events.insert({
calendarId: 'primary',
requestBody: event,
});

return res.status(200).json({success: true});
} catch (error) {
if (error.response && error.response.data && error.response.data.error === 'invalid_grant') {
console.error("Refresh token is invalid or expired.");
return res.status(401).json({success: false, message: "Authentication failed. Please log in again."});
}
console.error("An error occurred:", error.message);
return res.status(500).json({success: false, message: "An internal error occurred."});
}
}



module.exports = {fetchGoogleAppointments, storeGoogleAppointments, updateGoogleLogin};
96 changes: 96 additions & 0 deletions backend/services/calendar/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
const {GDBAppointmentModel} = require("../../models/appointment");
const {GDBServiceProviderModel} = require("../../models");
const {extractAllIndexes} = require("../../helpers/stringProcess");
const {graphdb} = require("../../config");
const {GDBCharacteristicModel} = require("../../models/ClientFunctionalities/characteristic");
const {GDBCOModel} = require("../../models/ClientFunctionalities/characteristicOccurrence");

async function fetchAppointment(req, res, next) {

const fts_appointments = await fts_appointment_for_calendar(req.body);
if (fts_appointments.length === 0) {
return res.status(200).json({data: [], success: true});
}

let array = [...new Set([...fts_appointments])];

if (array.length !== 0) {
let data_array = [];
for (let i = 0; i < array.length; i++) {
data_array.push(await GDBAppointmentModel.find({_uri: array[i]},
{populates: ['characteristicOccurrences.occurrenceOf']}));

// Obtain the values from the characteristicOccurrences
// Iterate through the characteristicOccurrences
// for (let j = 0; j < data_array[i][0].characteristicOccurrences.length; j++) {
// let occurrence = data_array[i][0].characteristicOccurrences[j];
// if (occurrence.occurrenceOf) {
// // Check if the occurrence is the start date of the appointment
// if (occurrence.occurrenceOf.description === "Start date of a service Occurrence") {
// // Get the date
// data_array[i][0].set("start", occurrence.dataDateValue);
// // data_array[i][0].set("end", new Date());
// }
// if (occurrence.occurrenceOf.description === "End date of a service Occurrence") {
// // Get the date
// data_array[i][0].set("end", occurrence.dataDateValue);
// }
// // Obtain the title of the appointment
// if (occurrence.occurrenceOf.description === "Appointment Name") {
// data_array[i][0].set("title", occurrence.dataStringValue);
// }
// }
// }
}
data = data_array.flat();
}

return res.status(200).json({data, success: true});
}

async function fts_appointment_for_calendar(params) {
// Assume all passed in date are in the format of "2024-02-22T00:00:00Z"
const {user_id, startDate, endDate} = params;

// let date_query;
// if (!startDate || !endDate) {
// date_query = ``;
// } else {
// date_query = `FILTER (?date >= xsd:dateTime("${startDate}") && ?date < xsd:dateTime("${endDate}")).`;
// }

// The id of the user should be an integer like 1, 2, 3, etc.
let person_query;
if (!user_id) {
person_query = ``;
} else {
person_query = `?object1 snmi:hasPerson snmi:person_${user_id}.`;
}

const sparqlQuery =
`
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX snmi: <http://snmi#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>

SELECT DISTINCT ?object1
WHERE
{ ?object1 rdf:type snmi:Appointment
{
${person_query}
}
}
`;

console.log("Query: ", sparqlQuery);

let query = graphdb.addr + "/repositories/snmi?query=" + encodeURIComponent(sparqlQuery);

const response = await fetch(query);
const text = await response.text();
return extractAllIndexes(text);

}


module.exports = {fetchAppointment};
6 changes: 5 additions & 1 deletion backend/services/userAccount/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ async function updateUserAccount(email, updatedData) {

const {
securityQuestions, status, givenName, familyName, countryCode,
areaCode, phoneNumber, altEmail
areaCode, phoneNumber, altEmail, googleRefreshToken
} = updatedData
if (securityQuestions) {
const answer1 = await Hashing.hashPassword(securityQuestions[3]);
Expand Down Expand Up @@ -94,6 +94,10 @@ async function updateUserAccount(email, updatedData) {
userAccount.secondaryEmail = altEmail;
}

if (googleRefreshToken) {
userAccount.googleRefreshToken = googleRefreshToken;
}

await userAccount.save();
console.log(userAccount)
return userAccount;
Expand Down
Loading