Skip to content

Commit

Permalink
Merge branch 'main' of github.com:yeatmanlab/roar-firekit
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasxsong committed Mar 4, 2024
2 parents 4ffa6b8 + 3c12164 commit e8bb2dd
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 38 deletions.
47 changes: 44 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@bdelab/roar-firekit",
"version": "4.6.0",
"version": "4.8.0",
"description": "A library to facilitate Firebase authentication and Cloud Firestore interaction for ROAR apps",
"main": "lib/index.js",
"types": "lib/index.d.ts",
Expand Down Expand Up @@ -53,6 +53,7 @@
"eslint-config-prettier": "^8.5.0",
"jest": "^29.5.0",
"prettier": "^2.5.1",
"process": "^0.11.10",
"ts-jest": "^29.1.0",
"typedoc": "^0.22.13",
"typescript": "^4.6.2"
Expand All @@ -61,10 +62,13 @@
"lib/**/*"
],
"dependencies": {
"@bdelab/roar-firekit": "^4.1.1",
"crc-32": "^1.2.2",
"dot-object": "^2.1.4",
"firebase": "^9.23.0",
"link": "^2.1.0",
"lodash": "^4.17.21",
"vue": "^3.3.4"
"vue": "^3.3.4",
"web-vitals": "^3.4.0"
}
}
11 changes: 10 additions & 1 deletion src/firestore/app/appkit.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { onAuthStateChanged } from 'firebase/auth';
import { updateDoc, arrayRemove, arrayUnion } from 'firebase/firestore';

import { ref, getDownloadURL } from 'firebase/storage';
import { IComputedScores, IRawScores, RoarRun } from './run';
import { ITaskVariantInfo, RoarTaskVariant } from './task';
import { IUserInfo, IUserUpdateInput, RoarAppUser } from './user';
Expand Down Expand Up @@ -303,4 +303,13 @@ export class RoarAppkit {
throw new Error('This run has not started. Use the startRun method first.');
}
}

async getStorageDownloadUrl(filePath: string) {
if (!this._initialized) {
await this._init();
}

const storageRef = ref(this.firebaseProject!.storage, filePath);
return getDownloadURL(storageRef);
}
}
119 changes: 88 additions & 31 deletions src/firestore/firekit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,6 @@ enum AuthProviderType {
USERNAME = 'username',
}

const RoarProviderId = {
...ProviderId,
CLEVER: 'oidc.clever',
ROAR_ADMIN_PROJECT: 'oidc.gse-roar-admin',
};

interface ICreateUserInput {
dob: string;
grade: string;
Expand All @@ -104,6 +98,21 @@ interface ICreateUserInput {
group: { id: string; abbreviation?: string } | null;
}

interface CreateParentInput {
name: {
first: string;
last: string;
};
}

export interface ChildData {
email: string;
password: string;
userData: ICreateUserInput;
familyId: string;
orgCode: string;
}

interface ICurrentAssignments {
assigned: string[];
started: string[];
Expand Down Expand Up @@ -161,6 +170,14 @@ export class RoarFirekit {
this.listenerUpdateCallback = listenerUpdateCallback ?? (() => {});
}

private _getProviderIds() {
return {
...ProviderId,
CLEVER: 'oidc.clever',
ROAR_ADMIN_PROJECT: `oidc.${this.roarConfig.admin.projectId}`,
};
}

private _scrubAuthProperties() {
this.userData = undefined;
this.roarAppUserInfo = undefined;
Expand Down Expand Up @@ -270,6 +287,7 @@ export class RoarFirekit {
async (doc) => {
const data = doc.data();
this._adminOrgs = data?.claims?.adminOrgs;
this._superAdmin = data?.claims?.super_admin;
if (data?.lastUpdated) {
const lastUpdated = new Date(data!.lastUpdated);
if (!firekit.claimsLastUpdated || lastUpdated > firekit.claimsLastUpdated) {
Expand Down Expand Up @@ -301,7 +319,6 @@ export class RoarFirekit {
if (user) {
const idTokenResult = await user.getIdTokenResult(false);
if (_type === 'admin') {
this._superAdmin = Boolean(idTokenResult.claims.super_admin);
this._idTokenReceived = true;
}
this._idTokens[_type] = idTokenResult.token;
Expand Down Expand Up @@ -420,7 +437,8 @@ export class RoarFirekit {
this._verifyInit();
return signInWithEmailLink(this.admin!.auth, email, emailLink)
.then(async (userCredential) => {
const roarAdminProvider = new OAuthProvider(RoarProviderId.ROAR_ADMIN_PROJECT);
const roarProviderIds = this._getProviderIds();
const roarAdminProvider = new OAuthProvider(roarProviderIds.ROAR_ADMIN_PROJECT);
const roarAdminIdToken = await getIdToken(userCredential.user);
const roarAdminCredential = roarAdminProvider.credential({
idToken: roarAdminIdToken,
Expand Down Expand Up @@ -448,7 +466,8 @@ export class RoarFirekit {
if (provider === AuthProviderType.GOOGLE) {
authProvider = new GoogleAuthProvider();
} else if (provider === AuthProviderType.CLEVER) {
authProvider = new OAuthProvider(RoarProviderId.CLEVER);
const roarProviderIds = this._getProviderIds();
authProvider = new OAuthProvider(roarProviderIds.CLEVER);
} else {
throw new Error(`provider must be one of ${allowedProviders.join(', ')}. Received ${provider} instead.`);
}
Expand Down Expand Up @@ -476,7 +495,8 @@ export class RoarFirekit {
// TODO: Find a way to put this in the onAuthStateChanged handler
oAuthAccessToken = credential?.accessToken;

const roarAdminProvider = new OAuthProvider(RoarProviderId.ROAR_ADMIN_PROJECT);
const roarProviderIds = this._getProviderIds();
const roarAdminProvider = new OAuthProvider(roarProviderIds.ROAR_ADMIN_PROJECT);
const roarAdminIdToken = await getIdToken(adminUserCredential.user);
const roarAdminCredential = roarAdminProvider.credential({
idToken: roarAdminIdToken,
Expand Down Expand Up @@ -511,7 +531,8 @@ export class RoarFirekit {
if (provider === AuthProviderType.GOOGLE) {
authProvider = new GoogleAuthProvider();
} else if (provider === AuthProviderType.CLEVER) {
authProvider = new OAuthProvider(RoarProviderId.CLEVER);
const roarProviderIds = this._getProviderIds();
authProvider = new OAuthProvider(roarProviderIds.CLEVER);
} else {
throw new Error(`provider must be one of ${allowedProviders.join(', ')}. Received ${provider} instead.`);
}
Expand All @@ -536,21 +557,22 @@ export class RoarFirekit {
.then(async (adminUserCredential) => {
if (adminUserCredential !== null) {
const providerId = adminUserCredential.providerId;
if (providerId === RoarProviderId.GOOGLE) {
const roarProviderIds = this._getProviderIds();
if (providerId === roarProviderIds.GOOGLE) {
const credential = GoogleAuthProvider.credentialFromResult(adminUserCredential);
// This gives you a Google Access Token. You can use it to access Google APIs.
// TODO: Find a way to put this in the onAuthStateChanged handler
authProvider = AuthProviderType.GOOGLE;
oAuthAccessToken = credential?.accessToken;
return credential;
} else if (providerId === RoarProviderId.CLEVER) {
} else if (providerId === roarProviderIds.CLEVER) {
const credential = OAuthProvider.credentialFromResult(adminUserCredential);
// This gives you a Clever Access Token. You can use it to access Clever APIs.
// TODO: Find a way to put this in the onAuthStateChanged handler
authProvider = AuthProviderType.CLEVER;
oAuthAccessToken = credential?.accessToken;

const roarAdminProvider = new OAuthProvider(RoarProviderId.ROAR_ADMIN_PROJECT);
const roarAdminProvider = new OAuthProvider(roarProviderIds.ROAR_ADMIN_PROJECT);
const roarAdminIdToken = await getIdToken(adminUserCredential.user);
const roarAdminCredential = roarAdminProvider.credential({
idToken: roarAdminIdToken,
Expand Down Expand Up @@ -626,11 +648,11 @@ export class RoarFirekit {
return {
admin: {
headers: { Authorization: `Bearer ${this._idTokens.admin}` },
baseURL: 'https://firestore.googleapis.com/v1/projects/gse-roar-admin/databases/(default)/documents',
baseURL: `https://firestore.googleapis.com/v1/projects/${this.roarConfig.admin.projectId}/databases/(default)/documents`,
},
app: {
headers: { Authorization: `Bearer ${this._idTokens.app}` },
baseURL: 'https://firestore.googleapis.com/v1/projects/gse-roar-assessment/databases/(default)/documents',
baseURL: `https://firestore.googleapis.com/v1/projects/${this.roarConfig.app.projectId}/databases/(default)/documents`,
},
};
}
Expand Down Expand Up @@ -876,29 +898,16 @@ export class RoarFirekit {
throw new Error(`Could not find assessment with taskId ${taskId} in administration ${administrationId}`);
}

// Create the run in the assessment Firestore, record the runId and then
// pass it to the app
const runRef = doc(this.dbRefs!.app.runs);
const runId = runRef.id;

// Check the assignment to see if none of the assessments have been
// started yet. If not, start the assignment
const assignmentDocRef = doc(this.dbRefs!.admin.assignments, administrationId);
const assignmentDocSnap = await transaction.get(assignmentDocRef);
if (assignmentDocSnap.exists()) {
const assignedAssessments = assignmentDocSnap.data().assessments as IAssignedAssessmentData[];
const allRunIdsForThisTask = assignedAssessments.find((a) => a.taskId === taskId)?.allRunIds || [];
allRunIdsForThisTask.push(runId);

const assessmentUpdateData: { startedOn: Date; allRunIds: string[]; runId?: string } = {
const assessmentUpdateData = {
startedOn: new Date(),
allRunIds: allRunIdsForThisTask,
};

if (allRunIdsForThisTask.length === 1) {
assessmentUpdateData.runId = runId;
}

// Append runId to `allRunIds` for this assessment
// in the userId/assignments collection
await this._updateAssignedAssessment(administrationId, taskId, assessmentUpdateData, transaction);
Expand Down Expand Up @@ -949,7 +958,6 @@ export class RoarFirekit {
assigningOrgs,
readOrgs,
assignmentId: administrationId,
runId,
taskInfo,
});
} else {
Expand Down Expand Up @@ -1254,6 +1262,55 @@ export class RoarFirekit {
await cloudCreateStudent({ email, password, userData: userDocData });
}

async createNewFamily(
caretakerEmail: string,
caretakerPassword: string,
caretakerUserData: CreateParentInput,
children: ChildData[],
) {
// Format children objects
const formattedChildren = children.map((child) => {
const returnChild = {
email: child.email,
password: child.password,
};
// Create a PID for the student.
const emailCheckSum = crc32String(child.email!);
const pidParts: string[] = [];
pidParts.push(emailCheckSum);
_set(returnChild, 'userData.assessmentPid', pidParts.join('-'));

// Move attributes into the studentData object.
_set(returnChild, 'userData.username', child.email.split('@')[0]);
if (_get(child, 'userData.name')) _set(returnChild, 'userData.name', child.userData.name);
if (_get(child, 'userData.gender')) _set(returnChild, 'userData.studentData.gender', child.userData.gender);
if (_get(child, 'userData.grade')) _set(returnChild, 'userData.studentData.grade', child.userData.grade);
if (_get(child, 'userData.dob')) _set(returnChild, 'userData.studentData.dob', child.userData.dob);
if (_get(child, 'userData.state_id')) _set(returnChild, 'userData.studentData.state_id', child.userData.state_id);
if (_get(child, 'userData.hispanic_ethnicity'))
_set(returnChild, 'userData.studentData.hispanic_ethnicity', child.userData.hispanic_ethnicity);
if (_get(child, 'userData.ell_status'))
_set(returnChild, 'userData.studentData.ell_status', child.userData.ell_status);
if (_get(child, 'userData.iep_status'))
_set(returnChild, 'userData.studentData.iep_status', child.userData.iep_status);
if (_get(child, 'userData.frl_status'))
_set(returnChild, 'userData.studentData.frl_status', child.userData.frl_status);
if (_get(child, 'userData.race')) _set(returnChild, 'userData.studentData.race', child.userData.race);
if (_get(child, 'userData.home_language'))
_set(returnChild, 'userData.studentData.home_language', child.userData.home_language);
return returnChild;
});

// Call cloud function
const cloudCreateFamily = httpsCallable(this.admin!.functions, 'createnewfamily');
await cloudCreateFamily({
caretakerEmail,
caretakerPassword,
caretakerUserData,
children: formattedChildren,
});
}

async createStudentWithUsernamePassword(username: string, password: string, userData: ICreateUserInput) {
this._verifyAuthentication();
this._verifyAdmin();
Expand Down
Loading

0 comments on commit e8bb2dd

Please sign in to comment.