De FireStore db is toegankelijk op de "server side" door definities in Google Cloud Projects en console en op de "client side" via apps, waardoor er een toegangscontrole dmv regels moet gemaakt worden in FireBase/FireStore.
De toepassing via Apps Script gedraagt zich als een "Server Side" connectie en de toegang dient dan ook volledig geregeld te worden via GCP. (zie QuizToFireStore app) dmv een Service Account.
Vermits een "server side" toegang een potentieel risico inhoud, is het ten stelligste af te raden om de credentials in je code te verwerken, zoals dit meestal in alle demo software wordt gedaan.
Wij hebben gekozen voor een aanpak, waarbij de credentials als file bewaard worden op je G-drive, en deze enkel gedeelt wordt met een specifieke gebruiker (admin van de tools).
Deze gebruiker krijgt de URL naar de file, die hij in de configuratie van de add-on kan invullen (zie setKeyFile in Code.gs).
Hierna worden de credentials ingelezen en toegenkend aan document properties, waardoor ze enkel bruikbaar zijn in dit document.
Wanneer het document gekopieerd wordt, gaan ze verloren. Ook wanneer een user zou trachten een ander Apps Script te koppelen aan het document, gaat de binding verloren.
En doordat de waarde van de sleutels enkel in de server bekend is en in server side code gebruikt wordt, zijn deze niet eenvoudig te achterhalen via een client connectie.
Deze functie maakt gebruik van een REST wrapper API, die te gebruiken is door de library FirestoreApp toe te voegen aan je project, met ID: 1VUSl4b1r1eoNcRWotZM3e87ygkxvXltOgyDZhixqncz9lQ3MjfT1iKFw
De credentials worden gehaald uit de document properties en zijn hier vooraf aan toegekend dmv de setKeyFile() config optie in code.gs.
/**
* Access to Firestore db hardcoded with Serviceaccount *
* use of FirestoreApp lb key: 1VUSl4b1r1eoNcRWotZM3e87ygkxvXltOgyDZhixqncz9lQ3MjfT1iKFw
* Wiki: https://github.com/grahamearley/FirestoreGoogleAppsScript/wiki/Firestore-Method-Documentation
* key values come from scriptProperties
* @return {object} object to firestore db
*/
function getFireStore() {
try {
const docProperties = PropertiesService.getDocumentProperties();
const config = {
'project_id': docProperties.getProperty('project_id'),
'private_key': docProperties.getProperty('private_key'),
'client_email': docProperties.getProperty('client_email')
}
var firestore = FirestoreApp.getFirestore(config.client_email, config.private_key, config.project_id);
return firestore
} catch (e) {
var ui = SpreadsheetApp.getUi();
ui.alert(e)
Logger.log(e);
}
}
Het afvragen van de Firestore db, kan "beperkt" met deze API.
De REST api heeft meer mogelijkheden, maar voor de meeste toepassingen is deze API meer dan voldoende.
Het gebruik van wildcards is niet mogelijk, maar genereerd geen fout.
Voor sommige afvragingen (orderby) dient FireStore een extra index te genereren en dien je dus toegang te hebben tot de FireStore console.
Standaard kan je meerde == files in een EN verknoping samen brengen, zonder extra index.
/**
* Voorbeelden
*/
let sheetval= SheetToFS("NewUsers","myuba")
const QueryGroup = firestore.query("Test").Where("field12", "null").Execute();
const QueryGroup = firestore.query("Collection1/*/score").Where("field15", ">=",10).Execute();-> werkt niet
const QueryGroup = firestore.query("Collection1/doc1/score").Where("field15", ">=", 10).Execute();
const QueryField1a = firestore.query("TestA").Where("field5", ">",0).OrderBy("field5","DESCENDING").Execute();
const QueryField1 = firestore.query("TestA").Where("field1", "==", "veld1").OrderBy("field5","DESCENDING").Execute();
const QueryField5 = firestore.query("TestA").Where("field5", "in", [123, 10, 1002]).Execute()
const QueryF1F5 = firestore.query("TestA").Where("field1", "==", "veld1").Where("field5", "in", [123, 10, 1002]).Execute()
const QueryContains = firestore.query("TestA").Where("field7", "contains", 10).Where("field1", "==", "veld1").Execute()
const QueryContainsany = firestore.query("TestA").Where("field7", "containsany",[10,true,"str"]).Execute()
const Querymap = firestore.query("TestA").Where("field6.map2", "==","map2").Where("field6.submap.smap1", "==","smap1v").Execute()
Deze eenvoudige functie laat je zien hoe je een bestaande sheet kan vertalen naar documents in je FS db. Per row wordt er een document met een unieke naam gemaakt, waarbij de headers van de sheet gebruikt worden voor de keys en de waardes voor de values.
Het is "good practice" om in het document, zeker wanneer je een beduidende naam gebruikt, de naam en datum van het document eveneens te vernoemen.
Hierdoor kan dit nadien gebruikt worden voor de generatie van een index, waardoor de zoekopdrachten meer mogelijkheden krijgen (zie EN functies binnen queries)
/**
* Convert sheetrows to FireStore documents
* @param {string} "sheetname" sheet in active SS
* @param {string} "path" collection path
* @return {array} values of sheet.
* header row becomes properties
* docname propery + document name = sheetname+recordnr
*
*/
function SheetToFS(sheetname, collectionpath) {
try {
// Get the table values
var values = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetname).getDataRange().getValues();
// Extract headers
var headers = values[0];
// Extract the body
var body = values.slice(1);
// You need an index for looping through the body rows
var date= new Date().toISOString()
for (let r = 0; r < body.length; r++) {
// Initialize document obj, put sheet name as field in record (extra action)
let document = {}
document.docname = `${sheetname}_${r + 1}` //docname = sheetname+date+recordnr
document.changed = date
for (c = 0; c < headers.length; c++) {
document[headers[c]] = ``
document[headers[c]] = body[r][c] //Assign cell value to property
}
let firestore = getFireStore()
let updatedoc = firestore.updateDocument(`${collectionpath}/${document.docname}`, document, mask = true);
}
return values;
} catch (e) {
Logger.log(e)
}
}
Standaard output van een Form Quiz naar een sheet bevat in de eerste sheet en de eerste kolommen de gegevens die je nodig hebt:
Timestamp, Email address, Score
Deze gegevens worden gebruikt voor het berekenen van een score op 100%, door het toevoegen van een sheet "calc", waarin de "weergave" van de score wordt omgevormd naar een getal (in %).
Deze omweg is nodig zodat de maken van de quiz een ander gewicht (puntenwaarde) per vraag kan toekennen.
De gevens worden nadien bewaard in de FireStore database als een document met de naam van de quiz en de volgordenummer van het record onder de collection die is opgegevend bij het configureren van de setKeyFile().
In dit voorbeeld werd de collection "HAREC" gebruikt.
De FireStore zal voor alle velden een index genereren die nadien kan gebruikt worden in de queries.
Het "herhalen" van de docname in het document zelf, maakt dat deze sleutel ook geĂŻndexeerd wordt en kan opgezocht worden dmv een query.
Dit principe van "herhalen" van data in bepaalde documenten is noodzakelijk in FS om te vermijden dat er composite queries dienen gemaakt te worden, met specifieke indexen.
Vermits in deze functie gebruik wordt gemaakt van Drive en Sheets dienen beide services actief te staan in GCP voor dit project/gebruiker/domain.
Doordat er binnen een korte tijdsspanne meerdere antwoorden op een formulier kunnen binnen komen, ontstaan er "concurrency" problemen.
Deze zijn door gebruikt te maken van een RecCount, die bewaard wordt in een docproperty ondervangen.
/**
* Convert sheetrows to FireStore documents
* @param {string} "collectionpath" collection path
* @param {number} count, number of last records to update if 0=all
* @return {array} values of sheet.
* header row becomes properties
* if score in 3the column, than 2de is email
* docname propery + document name = sheetname+recordnr
*
*/
function QuizsheetToFS_score(collectionpath, count)
Functie scrijft de sheet waarin de aliasses van studenten worden vermeld, weg in de FS db.
/**
* AliasEmailsWrite
*
* @param {string} collectionpath, pathname collection in FS
* @param {string} sheet, sheet with alias email adresses: FullName, Email, AliasEmail1...
* @return {bool} true is ok, false if something went wrong
*
* Writes to Firestore a document per primary email address of aliasses entry on sheet, overwriting existing alias record.
* Use read-modify-write principle to update aliasses records
* name: primary email address student
* fields:
* docname: document name as field, primary email address student
* fullname: fullName student
* aliasses[]: array of Alias emails for a specific primary email address.
* The array is taken from the sheet AliasEmails
*/
function AliasEmailsWrite(collectionpath, sheet)
Vermits de alias lijst zou kunnen bestaan door aliasses die ingevuld zijn door verschillende docenten, is het zinvol om deze centraal te bewaren in de db. Door ze te lezen voor een bepaalde cursist, kom je zo achter alle mogelijke aliasses van die cursist.
/**
* AliasEmailsRead (single user read)
*
* @param {string} collectionpath, pathname collection in FS *
* @param {email} email, FS data of aliasses
* @return {array} arr of alias emails
*
* Reads from Firestore an array of Alias emails for a specific primary email address.
* The array is placed into the AliasEmails sheet
*
* Reads from Firestore a document with query on docname field.
* name: primay email address
* fields:
* docname: document name as field, primary email address
* fullname: fullName student
* aliasses[]: array of Alias emails for a specific primary email address.
* The array is taken from the sheet AliasEmails
*/
function AliasEmailsRead(collectionpath, email)
Wrapper functie om voorgaande in een sheet te plaatsen, waarbij voor alle studenten gekeken wordt of er aliasses bestaan.
**
* ListAliasEmails
* Uses Userslist to search for aliases in db and populate Aliaslist
* @param {string} collectionpath, path to the collection of alias objects
* @param {string} aliassheet, sheet with list of aliasses
* @param {string} usersheet, sheet with list of users
* @return {bool} true if ok
*/
function ListAliasEmails(collectionpath, aliassheet, usersheet)
Iedere instructeur kan een lijst maken van studenten waarvan hij docent is, door in de verschillende classrooms waarin hij docent is de studentenlijsten op te vragen.
De lijst wordt bewaard onder de teacher email
/**
* MyStudentListWrite
*
* @param {string} collectionpath
* @param {string} sheet, sheet with active student list:FullName, Email, Photo, StudentInNrCourses, StudentId
* @return {bool} true if successfull
*
* Writes the active student list to Firestore to a document with your emailaddress (active user)
*
* Writes to Firestore a document:
* name: current user = teacher email
* fields:
* teacher: current user = teacher email
* studentlist[]: Array of objects students
* - fullname: fullName student
* - email: primary email address user
* - photo: URL to prifile photo
* - studentinrcourses: count of courses a student or an alias is participating
* - studentid: id user
*
*/
function MyStudentListWrite(collectionpath, sheet)
De studentenlijst kan gelezen worden uit de FS db en gesorteerd worden op een bepaalde header (naam, email etc)
Deze wordt dan terug geplaats in een sheet, zodat ze nadien kan gebruikt worden ter controle en om de selectie van de scores te kunnen maken.
/**
* MyStudentListRead
*
* @param {string} collectionpath
* @param {string} sheet, sheet with active student list:FullName, Email, Photo, StudentInNrCourses, StudentId
* @param {string} sortheader, lable of column used to sort by
* @return {bool} true if successfull
*
* Reads the active student list from Firestore a document with your emailaddress (active user) and polulates sheet
*
* reads from Firestore a document with name "current user"
* name: current user = teacher email
* fields:
* teacher: current user = teacher email
* studentlist[]: Array of objects students
* - fullname: fullName student
* - email: primary email address user
* - photo: URL to prifile photo
* - studentinrcourses: count of courses a student or an alias is participating
* - studentid: id user
*
*/
function MyStudentListRead(collectionpath, sheet, sortheader)