Skip to content

Commit

Permalink
Moved Firestore triggers to client-side repositories
Browse files Browse the repository at this point in the history
- Fixed some small bugs related to timestamp/id fields being wrong on persist
  • Loading branch information
BenjaVR committed Feb 5, 2019
1 parent 151ab8d commit a7d8fee
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 43 deletions.
37 changes: 0 additions & 37 deletions functions/server/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import * as admin from "firebase-admin";
import * as functions from "firebase-functions";
import { IDepartment } from "../shared/contract/IDepartment";
import { IStudent } from "../shared/contract/IStudent";
import { ModelMapper } from "./ModelMapper";

Expand All @@ -10,42 +9,6 @@ firestore.settings({
timestampsInSnapshots: true,
});

exports.onEducationDelete = functions.firestore.document("/educations/{id}").onDelete((snapshot) => {
const educationId = snapshot.id;

// Delete relations on departments
const departmentsPromise = firestore.collection("/departments")
.get()
.then((querySnapshot) => {
const innerPromises = [];

const mappedDepartments = ModelMapper.mapDocsToObjects<IDepartment>(querySnapshot.docs);
mappedDepartments.forEach((department) => {
const educations = department.capacityPerEducation;
if (educations === undefined || educations === null || educations.length === 0) {
return;
}

const updatedEducations = educations.filter((edu) => edu.educationId !== educationId);
if (updatedEducations.length === 0) {
return;
}

if (educations.some((edu) => edu.educationId === educationId)) {

const updatedDepartment: Partial<IDepartment> = {
capacityPerEducation: updatedEducations,
};
innerPromises.push(firestore.doc(`/departments/${department.id}`).update(updatedDepartment));
}
});

return Promise.all([innerPromises]);
});

return Promise.all([departmentsPromise]);
});

exports.onSchoolDelete = functions.firestore.document("/schools/{id}").onDelete((snapshot) => {
const schoolId = snapshot.id;

Expand Down
10 changes: 8 additions & 2 deletions web/src/models/ModelBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import moment from "moment";
import { IFirestoreEntityBase } from "../entities/IFirestoreEntityBase";
import { Firebase } from "../services/FirebaseInitializer";

type DatabaseAction = "new" | "update";
type DatabaseAction = "new" | "update" | "updateWithoutTime";

export abstract class ModelBase<T extends IFirestoreEntityBase> {

Expand All @@ -22,15 +22,21 @@ export abstract class ModelBase<T extends IFirestoreEntityBase> {
...this.getEntityInternal(),
};

delete entity.id;

switch (databaseAction) {
case "new":
delete entity.id;
entity.createdTimestamp = Firebase.firestore.FieldValue.serverTimestamp() as firebase.firestore.Timestamp;
entity.updatedTimestamp = Firebase.firestore.FieldValue.serverTimestamp() as firebase.firestore.Timestamp;
break;
case "update":
// Make sure to not overwrite the createdTimestamp.
delete entity.createdTimestamp;
entity.updatedTimestamp = Firebase.firestore.FieldValue.serverTimestamp() as firebase.firestore.Timestamp;
break;
case "updateWithoutTime":
delete entity.createdTimestamp;
break;
default:
throw new Error("Unsupported database action");
}
Expand Down
29 changes: 27 additions & 2 deletions web/src/services/repositories/DepartmentsRepository.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { IDepartment } from "../../entities/IDepartment";
import { nameof } from "../../helpers/nameof";
import { Department } from "../../models/Department";
import { Education } from "../../models/Education";
import { FirebaseModelMapper } from "../FirebaseModelMapper";
import { FirestoreRefs } from "../FirestoreRefs";

Expand All @@ -16,6 +17,15 @@ export class DepartmentsRepository {
});
}

public static async getAllDepartments(): Promise<Department[]> {
const querySnapshot = await FirestoreRefs.getDepartmentCollectionRef()
.get();

const departmentEntities = FirebaseModelMapper.mapDocsToObjects<IDepartment>(querySnapshot.docs);
const departments = departmentEntities.map((entity) => Department.fromEntity(entity));
return departments;
}

public static async getDepartmentsByName(): Promise<Department[]> {
const querySnapshot = await FirestoreRefs.getDepartmentCollectionRef()
.orderBy(nameof<IDepartment>("name"), "asc")
Expand All @@ -31,12 +41,12 @@ export class DepartmentsRepository {
.add(department.getEntity("new"));
}

public static async updateDepartment(department: Department): Promise<void> {
public static async updateDepartment(department: Department, doNotUpdateTimestamp: boolean = false): Promise<void> {
if (department.id === undefined) {
return Promise.reject(Error("Department should have an id"));
}
await FirestoreRefs.getDepartmentDocRef(department.id)
.update(department.getEntity("update"));
.update(department.getEntity(doNotUpdateTimestamp ? "updateWithoutTime" : "update"));
}

public static async deleteDepartment(department: Department): Promise<void> {
Expand All @@ -46,4 +56,19 @@ export class DepartmentsRepository {
await FirestoreRefs.getDepartmentDocRef(department.id)
.delete();
}

public static async deleteEducationRelations(education: Education): Promise<void> {
const departments = await DepartmentsRepository.getAllDepartments();
departments.forEach(async (department) => {
const educations = department.capacityPerEducation;
if (educations === undefined || educations === null || educations.length === 0) {
return;
}
if (educations.some((edu) => edu.educationId === education.id)) {
const educationsToKeep = educations.filter((edu) => edu.educationId !== education.id);
department.capacityPerEducation = educationsToKeep;
await DepartmentsRepository.updateDepartment(department, true);
}
});
}
}
13 changes: 13 additions & 0 deletions web/src/services/repositories/EducationsRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { nameof } from "../../helpers/nameof";
import { Education } from "../../models/Education";
import { FirebaseModelMapper } from "../FirebaseModelMapper";
import { FirestoreRefs } from "../FirestoreRefs";
import { DepartmentsRepository } from "./DepartmentsRepository";
import { StudentsRepository } from "./StudentsRepository";

export class EducationsRepository {

Expand Down Expand Up @@ -43,7 +45,18 @@ export class EducationsRepository {
if (education.id === undefined) {
return Promise.reject(Error("Education should have an id"));
}

await FirestoreRefs.getEducationDocRef(education.id)
.delete();

// Delete relations on departments
await DepartmentsRepository.deleteEducationRelations(education);

// Delete relations on students
const students = await StudentsRepository.getStudentsWithEducation(education);
students.forEach(async (student) => {
student.educationId = undefined;
await StudentsRepository.updateStudent(student, true);
});
}
}
8 changes: 8 additions & 0 deletions web/src/services/repositories/SchoolsRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { nameof } from "../../helpers/nameof";
import { School } from "../../models/School";
import { FirebaseModelMapper } from "../FirebaseModelMapper";
import { FirestoreRefs } from "../FirestoreRefs";
import { StudentsRepository } from "./StudentsRepository";

export class SchoolsRepository {

Expand Down Expand Up @@ -45,5 +46,12 @@ export class SchoolsRepository {
}
await FirestoreRefs.getSchoolDocRef(school.id)
.delete();

// Delete relations on students
const students = await StudentsRepository.getStudentsWithSchool(school);
students.forEach(async (student) => {
student.schoolId = undefined;
await StudentsRepository.updateStudent(student, true);
});
}
}
26 changes: 24 additions & 2 deletions web/src/services/repositories/StudentsRepository.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { IStudent } from "../../entities/IStudent";
import { nameof } from "../../helpers/nameof";
import { Education } from "../../models/Education";
import { School } from "../../models/School";
import { Student } from "../../models/Student";
import { FirebaseModelMapper } from "../FirebaseModelMapper";
import { FirestoreRefs } from "../FirestoreRefs";
Expand Down Expand Up @@ -28,17 +30,37 @@ export class StudentsRepository {
return students;
}

public static async getStudentsWithEducation(education: Education): Promise<Student[]> {
const querySnapshot = await FirestoreRefs.getStudentCollectionRef()
.where(nameof<IStudent>("educationId"), "==", education.id)
.get();

const studentEntities = FirebaseModelMapper.mapDocsToObjects<IStudent>(querySnapshot.docs);
const students = studentEntities.map((entity) => Student.fromEntity(entity));
return students;
}

public static async getStudentsWithSchool(school: School): Promise<Student[]> {
const querySnapshot = await FirestoreRefs.getStudentCollectionRef()
.where(nameof<IStudent>("schoolId"), "==", school.id)
.get();

const studentEntities = FirebaseModelMapper.mapDocsToObjects<IStudent>(querySnapshot.docs);
const students = studentEntities.map((entity) => Student.fromEntity(entity));
return students;
}

public static async addStudent(student: Student): Promise<void> {
await FirestoreRefs.getStudentCollectionRef()
.add(student.getEntity("new"));
}

public static async updateStudent(student: Student): Promise<void> {
public static async updateStudent(student: Student, doNotUpdateTimestamp: boolean = false): Promise<void> {
if (student.id === undefined) {
return Promise.reject(Error("Student should have an id"));
}
await FirestoreRefs.getStudentDocRef(student.id)
.update(student.getEntity("update"));
.update(student.getEntity(doNotUpdateTimestamp ? "updateWithoutTime" : "update"));
}

public static async deleteStudent(student: Student): Promise<void> {
Expand Down

1 comment on commit a7d8fee

@BenjaVR
Copy link
Owner

@BenjaVR BenjaVR commented on a7d8fee Feb 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#48

Please sign in to comment.