// Firebase imports const functions = require("firebase-functions"); const admin = require('firebase-admin'); const cors = require('cors')({ origin: true }); admin.initializeApp(); // external imports const axios = require('axios'); const parseString = require('xml2js').parseString; /* --------------------- station functions --------------------- */ // function to fetch station data from the Firestore database exports.getStationData = functions.https.onRequest((request, response) => { response.set('Access-Control-Allow-Origin', '*'); response.set('Access-Control-Allow-Credentials', 'true'); let jsonData = []; cors(request, response, () => { // fetch the "stations" collection admin.firestore().collection('stations').get().then((snapshot) => { if (snapshot.empty) { functions.logger.log("Empty snapshot received when fetching station data from Firestore") response.status(404).send({data: "Empty snapshot received when fetching station data from Firestore"}) return; } // iterate through each of the collection's documents snapshot.forEach(doc => { jsonData.push(doc.data()); }); functions.logger.log("Successfully fetched station data from Firestore") response.json({data: jsonData}); }) }); }) // function to populate the Firestore database with station data from Irish Rail exports.postStationData = functions.https.onRequest((request, response) => { // helper functon to parse station JSON objects function parseJSON(result) { let jsonStr = JSON.stringify(result); let jsonObj = JSON.parse(jsonStr); let jsonData = jsonObj.ArrayOfObjStation.objStation; return jsonData; } // helper function to write to the database function batchWriteDB(request, response, db, jsonData, dartCodes, stationTypeCode) { if (!jsonData) return response.set('Access-Control-Allow-Origin', '*'); response.set('Access-Control-Allow-Credentials', 'true'); cors(request, response, () => { var batchWrite = db.batch(); jsonData.forEach((doc) => { // append if the dartCodes hashset is empty or the current station is not present, and ignoring positions of zero if ((dartCodes.size == 0 || !dartCodes.has(doc["StationCode"][0])) && !(doc["StationLongitude"] == 0 || doc["StationLatitude"] == 0)) { doc["StationType"] = [stationTypeCode] var docID = db.collection('stations').doc(doc["StationCode"][0]) batchWrite.set(docID, doc); } }) batchWrite.commit() }) } response.set('Access-Control-Allow-Origin', '*'); response.set('Access-Control-Allow-Credentials', 'true'); cors(request, response, () => { // fetch dart stations axios.get('http://api.irishrail.ie/realtime/realtime.asmx/getAllStationsXML_WithStationType?StationType=D').then(res => { // XML to JSON parseString(res.data, function(err, result) { let jsonData = parseJSON(result) // batch delete all of the station collection's documents var db = admin.firestore() admin.firestore().collection('stations').get().then((snapshot) => { var batchDelete = db.batch() snapshot.forEach(doc => { batchDelete.delete(doc.ref) }) batchDelete.commit().then(function() { // store all dart codes into a hashset // compare these with the station call with code "all" to avoid duplicates let dartCodes = new Set() batchWriteDB(request, response, db, jsonData, dartCodes, "DART") // populate the dartCodes hashset jsonData.forEach((doc) => { dartCodes.add(doc["StationCode"][0]) }) // fetch all stations axios.get('http://api.irishrail.ie/realtime/realtime.asmx/getAllStationsXML_WithStationType?StationType=A').then(res => { parseString(res.data, function(err, result) { let jsonData = parseJSON(result) batchWriteDB(request, response, db, jsonData, dartCodes, "Train") functions.logger.log("Successfully fetched and upload station data from Irish Rail") response.send({data: "Successfully fetched and upload station data from Irish Rail"}) }) }) }) }) }) }) }) }) // scheduled version exports.scheduledPostStationData = functions.pubsub.schedule("every day 00:00").onRun(async (context) => { // helper functon to parse station JSON objects function parseJSON(result) { let jsonStr = JSON.stringify(result); let jsonObj = JSON.parse(jsonStr); let jsonData = jsonObj.ArrayOfObjStation.objStation; return jsonData; } // helper function to write to the database function batchWriteDB(db, jsonData, dartCodes, stationTypeCode) { if (!jsonData) return var batchWrite = db.batch(); jsonData.forEach((doc) => { // append if the dartCodes hashset is empty or the current station is not present, and ignoring positions of zero if ((dartCodes.size == 0 || !dartCodes.has(doc["StationCode"][0])) && !(doc["StationLongitude"] == 0 || doc["StationLatitude"] == 0)) { doc["StationType"] = [stationTypeCode] var docID = db.collection('stations').doc(doc["StationCode"][0]) batchWrite.set(docID, doc); } }); batchWrite.commit() } // fetch dart stations return axios.get('http://api.irishrail.ie/realtime/realtime.asmx/getAllStationsXML_WithStationType?StationType=D').then(res => { // XML to JSON parseString(res.data, function(err, result) { let jsonData = parseJSON(result) // batch delete all of the station collection's documents var db = admin.firestore(); admin.firestore().collection('stations').get().then((snapshot) => { var batchDelete = db.batch(); snapshot.forEach(doc => { batchDelete.delete(doc.ref); }); batchDelete.commit().then(function() { // store all dart codes into a hashset // compare these with the station call with code "all" to avoid duplicates let dartCodes = new Set() batchWriteDB(db, jsonData, dartCodes, "DART"); // populate the dartCodes hashset jsonData.forEach((doc) => { dartCodes.add(doc["StationCode"][0]) }) // fetch all train stations axios.get('http://api.irishrail.ie/realtime/realtime.asmx/getAllStationsXML_WithStationType?StationType=A').then(res => { parseString(res.data, function(err, result) { let jsonData = parseJSON(result) batchWriteDB(db, jsonData, dartCodes, "Train") functions.logger.log("Successfully fetched and upload station data from Irish Rail on a schedule") return "Successfully fetched and upload station data from Irish Rail on a schedule" }) }) }) }) }) }) }) /* --------------------- train functions --------------------- */ // function to fetch live train data from the Firestore database exports.getLiveTrainData = functions.https.onRequest((request, response) => { response.set('Access-Control-Allow-Origin', '*'); response.set('Access-Control-Allow-Credentials', 'true'); let jsonData = []; cors(request, response, () => { // fetch the "liveTrainData" collection admin.firestore().collection('liveTrainData').get().then((snapshot) => { if (snapshot.empty) { functions.logger.log("Empty snapshot received when fetching live train data from Firestore") response.status(404).send({data: "Empty snapshot received when fetching live train data from Firestore"}); return; } // iterate through each of the collection's documents snapshot.forEach(doc => { jsonData.push(doc.data()); }); functions.logger.log("Successfully fetched live train data from Firestore") response.json({data: jsonData}); }) }); }) // function to populate the Firestore database with live train data from Irish Rail exports.postLiveTrainData = functions.https.onRequest((request, response) => { // helper function to parse train JSON objects function parseJSON(result) { let jsonStr = JSON.stringify(result); let jsonObj = JSON.parse(jsonStr); let jsonData = jsonObj.ArrayOfObjTrainPositions.objTrainPositions; return jsonData; } function getRunningOriginDestination(publicMessage) { let startOrigin = publicMessage.indexOf("-") + 1 let endOrigin = publicMessage.indexOf("to ") - 1; let origin = publicMessage.substring(startOrigin, endOrigin); let startDestination = endOrigin + 4; let endDestination = publicMessage.indexOf("(") - 1; let destination = publicMessage.substring(startDestination, endDestination); return {origin: origin, destination: destination} } function getTerminatedOriginDestination(publicMessage) { let startOrigin = publicMessage.indexOf("-") + 1 let endOrigin = publicMessage.indexOf("to ") - 1; let origin = publicMessage.substring(startOrigin, endOrigin); let startDestination = endOrigin + 4; let endDestination = publicMessage.indexOf("("); let destination = publicMessage.substring(startDestination, endDestination); return {origin: origin, destination: destination} } function getNotYetRunningOriginDestination(publicMessage) { let startOrigin = publicMessage.indexOf(".") + 1 let endOrigin = publicMessage.indexOf("to ") - 1; let origin = publicMessage.substring(startOrigin, endOrigin); let startDestination = endOrigin + 3; let endDestination = publicMessage.indexOf(". E"); let destination = publicMessage.substring(startDestination, endDestination); return {origin: origin, destination: destination} } function getOriginDestination(publicMessage, trainType) { if (trainType == "R") return getRunningOriginDestination(publicMessage) else if (trainType == "T") return getTerminatedOriginDestination(publicMessage) else if (trainType == "N") return getNotYetRunningOriginDestination(publicMessage) } function getPunctuality(publicMessage, trainType) { if (trainType == "N") return let start = publicMessage.indexOf("(") + 1 let end = publicMessage.indexOf(")") // check if the train is early if (publicMessage[start] != "-") { return publicMessage.substring(start, end) } let endOfNum = publicMessage.indexOf(" mins") return publicMessage.substring(start+1, endOfNum) + " mins early" } // helper function to write to the database function batchWriteDB(request, response, db, jsonData, trainTypeCode) { if (!jsonData) return response.set('Access-Control-Allow-Origin', '*'); response.set('Access-Control-Allow-Credentials', 'true'); cors(request, response, () => { var batchWrite = db.batch(); jsonData.forEach((doc) => { // ignore trains with longitudes or latitudes equal zero if (!(doc["TrainLongitude"] == 0 || doc["TrainLatitude"] == 0)) { doc["TrainType"] = [trainTypeCode] // get the origin, destination and filter out \n from the public message doc["PublicMessage"][0] = doc["PublicMessage"][0].replace(/\\n/g, ". "); var originDestination = getOriginDestination(doc["PublicMessage"][0], doc["TrainStatus"][0]) doc["Origin"] = [originDestination.origin] doc["Destination"] = [originDestination.destination] // get the lateness if the train is running or terminated if (doc["TrainStatus"][0] == "R" || doc["TrainStatus"][0] == "T") { doc["Punctuality"] = [getPunctuality(doc["PublicMessage"][0], doc["TrainStatus"][0])] } var docID = db.collection('liveTrainData').doc(doc["TrainCode"][0]); batchWrite.set(docID, doc); } }); batchWrite.commit() }) } response.set('Access-Control-Allow-Origin', '*'); response.set('Access-Control-Allow-Credentials', 'true'); cors(request, response, () => { // fetch mainland trains axios.get('https://api.irishrail.ie/realtime/realtime.asmx/getCurrentTrainsXML_WithTrainType?TrainType=M').then(res => { // XML to JSON parseString(res.data, function(err, result) { let jsonData = parseJSON(result) // batch delete all of the liveTrainData collections's documents var db = admin.firestore(); admin.firestore().collection('liveTrainData').get().then((snapshot) => { var batchDelete = db.batch(); snapshot.forEach(doc => { batchDelete.delete(doc.ref); }); batchDelete.commit().then(function() { // batch write all train JSON objects to the liveTrainData collection batchWriteDB(request, response, db, jsonData, "Train"); // fetch suburban trains axios.get('https://api.irishrail.ie/realtime/realtime.asmx/getCurrentTrainsXML_WithTrainType?TrainType=S').then(res => { parseString(res.data, function(err, result) { let jsonData = parseJSON(result) batchWriteDB(request, response, db, jsonData, "Train"); // fetch dart trains axios.get('https://api.irishrail.ie/realtime/realtime.asmx/getCurrentTrainsXML_WithTrainType?TrainType=D').then(res => { parseString(res.data, function(err, result) { let jsonData = parseJSON(result) batchWriteDB(request, response, db, jsonData, "DART"); functions.logger.log("Successfully fetched and uploaded live train data from Irish Rail") response.send({data: "Successfully fetched and uploaded live train data from Irish Rail"}); }) }) }) }) }) }) }) }) }) }) // scheduled version exports.scheduledPostLiveTrainData = functions.pubsub.schedule('every 1 minutes').onRun(async (context) => { // helper function to parse train JSON objects function parseJSON(result) { let jsonStr = JSON.stringify(result); let jsonObj = JSON.parse(jsonStr); let jsonData = jsonObj.ArrayOfObjTrainPositions.objTrainPositions; return jsonData; } function getRunningOriginDestination(publicMessage) { let startOrigin = publicMessage.indexOf("-") + 1 let endOrigin = publicMessage.indexOf("to ") - 1; let origin = publicMessage.substring(startOrigin, endOrigin); let startDestination = endOrigin + 4; let endDestination = publicMessage.indexOf("(") - 1; let destination = publicMessage.substring(startDestination, endDestination); return {origin: origin, destination: destination} } function getTerminatedOriginDestination(publicMessage) { let startOrigin = publicMessage.indexOf("-") + 1 let endOrigin = publicMessage.indexOf("to ") - 1; let origin = publicMessage.substring(startOrigin, endOrigin); let startDestination = endOrigin + 4; let endDestination = publicMessage.indexOf("("); let destination = publicMessage.substring(startDestination, endDestination); return {origin: origin, destination: destination} } function getNotYetRunningOriginDestination(publicMessage) { let startOrigin = publicMessage.indexOf(".") + 1 let endOrigin = publicMessage.indexOf("to ") - 1; let origin = publicMessage.substring(startOrigin, endOrigin); let startDestination = endOrigin + 3; let endDestination = publicMessage.indexOf(". E"); let destination = publicMessage.substring(startDestination, endDestination); return {origin: origin, destination: destination} } function getOriginDestination(publicMessage, trainType) { if (trainType == "R") return getRunningOriginDestination(publicMessage) else if (trainType == "T") return getTerminatedOriginDestination(publicMessage) else if (trainType == "N") return getNotYetRunningOriginDestination(publicMessage) } function getPunctuality(publicMessage, trainType) { if (trainType == "N") return let start = publicMessage.indexOf("(") + 1 let end = publicMessage.indexOf(")") // check if the train is early if (publicMessage[start] != "-") { return publicMessage.substring(start, end) } let endOfNum = publicMessage.indexOf(" mins") return publicMessage.substring(start+1, endOfNum) + " mins early" } // helper function to write to the database function batchWriteDB(db, jsonData, trainTypeCode) { if (!jsonData) return var batchWrite = db.batch(); jsonData.forEach((doc) => { // ignore trains with longitudes or latitudes equal zero if (!(doc["TrainLongitude"] == 0 || doc["TrainLatitude"] == 0)) { doc["TrainType"] = [trainTypeCode] // get the origin, destination and filter out \n from the public message doc["PublicMessage"][0] = doc["PublicMessage"][0].replace(/\\n/g, ". "); var originDestination = getOriginDestination(doc["PublicMessage"][0], doc["TrainStatus"][0]) doc["Origin"] = [originDestination.origin] doc["Destination"] = [originDestination.destination] // get the lateness if the train is running or terminated if (doc["TrainStatus"][0] == "R" || doc["TrainStatus"][0] == "T") { doc["Punctuality"] = [getPunctuality(doc["PublicMessage"][0], doc["TrainStatus"][0])] } var docID = db.collection('liveTrainData').doc(doc["TrainCode"][0]); batchWrite.set(docID, doc); } }); batchWrite.commit() } // fetch mainland stations return axios.get('https://api.irishrail.ie/realtime/realtime.asmx/getCurrentTrainsXML_WithTrainType?TrainType=M').then(res => { // XML to JSON parseString(res.data, function(err, result) { let jsonData = parseJSON(result) // batch delete all of the liveTrainData collections's documents var db = admin.firestore(); admin.firestore().collection('liveTrainData').get().then((snapshot) => { var batchDelete = db.batch(); snapshot.forEach(doc => { batchDelete.delete(doc.ref); }); batchDelete.commit().then(function() { // batch write all train JSON objects to the liveTrainData collection batchWriteDB(db, jsonData, "Train"); // fetch suburban trains axios.get('https://api.irishrail.ie/realtime/realtime.asmx/getCurrentTrainsXML_WithTrainType?TrainType=S').then(res => { parseString(res.data, function(err, result) { let jsonData = parseJSON(result) batchWriteDB(db, jsonData, "Train"); // fetch DARTs axios.get('https://api.irishrail.ie/realtime/realtime.asmx/getCurrentTrainsXML_WithTrainType?TrainType=D').then(res => { parseString(res.data, function(err, result) { let jsonData = parseJSON(result) batchWriteDB(db, jsonData, "DART"); functions.logger.log("Successfully fetched and uploaded live train data from Irish Rail on a schedule") return "Successfully fetched and uploaded live train data from Irish Rail on a schedule" }) }) }) }) }) }) }) }) }) /* --------------------- preferences functions --------------------- */ // secure function to fetch a user's filter preferences from the database exports.getPreferences = functions.https.onCall((data, context) => { if (!context.auth) return "Error request is not verified" return admin.firestore().collection('preferences').doc(context.auth.uid).get().then((preferences) => { functions.logger.log("Successfully fetched user preferences from Firestore") return preferences.data() }) }) // secure function to set a user's filter preferences in the database exports.postPreferences = functions.https.onCall((data, context) => { if (!context.auth) return "Error request is not verified" return admin.firestore().collection('preferences').doc(context.auth.uid).set({data}).then(() => { functions.logger.log("Successfully saved user preferences into Firestore") return "Successfully saved user preferences into Firestore" }) }) // secure function to delete a user's filter preferences from the database exports.deletePreferences = functions.https.onCall((data, context) => { if (!context.auth) return "Error request is not verified" return admin.firestore().collection('preferences').doc(context.auth.uid).delete().then(() => { functions.logger.log("Successfully deleted user preferences from Firestore") return "Successfully deleted user preferences from Firestore" }) })