Skip to content

Commit

Permalink
feat!(tutorservice): get tutor service availability
Browse files Browse the repository at this point in the history
BREAKING CHANGE: We will use the day index instead of actual day name in
firestore. Ex: "Sunday" > 0, "Monday" > 1, etc.
  • Loading branch information
Veirt committed Nov 21, 2024
1 parent b65de4e commit 4b82acf
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 20 deletions.
24 changes: 24 additions & 0 deletions src/module/tutor-service/tutorService.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,30 @@ export const getService: Controller<GetServiceSchema> = async (
}
};

export const getServiceAvailability: Controller<GetServiceSchema> = async (
req,
res,
) => {
const tutorServiceId = req.params.tutorServiceId;

try {
const availability =
await tutorServiceService.getTutorServiceAvailability(tutorServiceId);

res.json({
status: "success",
data: availability,
});
} catch (error) {
logger.error(`Failed to get tutor service availability: ${error}`);

res.status(500).json({
status: "error",
message: `Failed to get tutor service availability`,
});
}
};

export const getMyServices: Controller = async (req, res) => {
const tutorId = req.tutor.id;

Expand Down
4 changes: 4 additions & 0 deletions src/module/tutor-service/tutorService.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ tutorServiceRouter.use(firebaseAuthMiddleware);

tutorServiceRouter.get("/", tutorServiceController.getServices);
tutorServiceRouter.get("/:tutorServiceId", tutorServiceController.getService);
tutorServiceRouter.get(
"/:tutorServiceId/availability",
tutorServiceController.getServiceAvailability,
);

tutorServiceRouter.use(verifyTutor);

Expand Down
14 changes: 7 additions & 7 deletions src/module/tutor-service/tutorService.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,13 @@ export const tutorServiceSchema = z.object({
}),
availability: z
.object({
sunday: zodTimesArray,
monday: zodTimesArray,
tuesday: zodTimesArray,
wednesday: zodTimesArray,
thursday: zodTimesArray,
friday: zodTimesArray,
saturday: zodTimesArray,
0: zodTimesArray,
1: zodTimesArray,
2: zodTimesArray,
3: zodTimesArray,
4: zodTimesArray,
5: zodTimesArray,
6: zodTimesArray,
})
.refine(
(availability) => {
Expand Down
10 changes: 1 addition & 9 deletions src/module/tutor-service/tutorService.seeder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,7 @@ const generateTeachingMethodology = async (
};

const generateRandomAvailability = () => {
const days = [
"sunday",
"monday",
"tuesday",
"wednesday",
"thursday",
"friday",
"saturday",
] as const;
const days = [0, 1, 2, 3, 4, 5, 6] as const;

const timeSlots = [
"09:00",
Expand Down
57 changes: 57 additions & 0 deletions src/module/tutor-service/tutorService.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,63 @@ export class TutorServiceService {
}
}

async getTutorServiceAvailability(serviceId: string) {
try {
const tutorServiceDoc = await this.firestore
.collection("tutor_services")
.doc(serviceId)
.get();

const tutorService = tutorServiceDoc.data();
if (!tutorService) {
throw new Error("Tutor service not found");
}

const { availability } = tutorService;

const today = new Date();
const next7DaysAvailability: Date[] = [];

// TODO: handle when there is already order that has status 'scheduled'
// remove the time from availability

// Calculate availability for the next 7 days
for (let i = 0; i < 7; i++) {
const date = new Date(today);
date.setDate(today.getDate() + i);

// 0: Sunday .. 6: Saturday
const dayIndex = date.getUTCDay();
const times = availability[dayIndex] || [];

times.forEach((time: string) => {
const [hours, minutes] = time.split(":").map(Number);

const datetime = new Date(
Date.UTC(
date.getUTCFullYear(),
date.getUTCMonth(),
date.getUTCDate(),
hours,
minutes,
),
);

// Skip past times
if (datetime < today) {
return;
}

next7DaysAvailability.push(datetime);
});
}

return next7DaysAvailability;
} catch (error) {
throw new Error(`Failed to get tutor service availability: ${error}`);
}
}

async createTutorService(
tutorId: string,
data: z.infer<typeof createTutorServiceSchema>["body"],
Expand Down
78 changes: 77 additions & 1 deletion tests/integration/tutorService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,52 @@ import { app } from "@/main";
import { seedTutors } from "@/module/tutor/tutor.seeder";
import { seedServices } from "@/module/tutor-service/tutorService.seeder";
import supertest from "supertest";
import { beforeAll, describe, test } from "vitest";
import { beforeAll, describe, expect, test } from "vitest";
import { faker } from "@faker-js/faker";
import { auth, firestore } from "@/config";
import {
connectAuthEmulator,
getAuth,
signInWithCustomToken,
} from "firebase/auth";
import { initializeApp } from "firebase/app";
import { TutorServiceService } from "@/module/tutor-service/tutorService.service";

const tutorServiceService = new TutorServiceService({ firestore });

const firebaseApp = initializeApp({
apiKey: "test-api-key",
authDomain: "localhost",
projectId: "tutortoise-test",
});
const clientAuth = getAuth(firebaseApp);
connectAuthEmulator(clientAuth, "http://localhost:9099");

async function getIdToken(userId: string) {
const customToken = await auth.createCustomToken(userId);
const { user } = await signInWithCustomToken(clientAuth, customToken);
return user.getIdToken();
}

async function registerLearner() {
const newLearner = {
name: faker.person.fullName(),
email: faker.internet.email(),
password: faker.internet.password(),
role: "learner",
};

const res = await supertest(app)
.post("/api/v1/auth/register")
.send(newLearner)
.expect(201);

const userId = res.body.data.userId;
expect(userId).toBeDefined();

const idToken = await getIdToken(userId);
return { idToken, userId };
}

beforeAll(async () => {
await seedTutors();
Expand All @@ -13,4 +58,35 @@ describe("Get tutor services", async () => {
test("Get all tutor services without token", async () => {
await supertest(app).get("/api/v1/tutors/services").expect(401);
});

test("Get all tutor services with token", async () => {
const { idToken } = await registerLearner();
await supertest(app)
.get("/api/v1/tutors/services")
.set("Authorization", `Bearer ${idToken}`)
.expect(200);
});
});

describe("Get tutor service availability", async () => {
const tutorServices = await tutorServiceService.getTutorServices();
const tutorServiceId = tutorServices[0].id;

Check failure on line 73 in tests/integration/tutorService.test.ts

View workflow job for this annotation

GitHub Actions / build (22.x)

tests/integration/tutorService.test.ts

TypeError: Cannot read properties of undefined (reading 'id') ❯ tests/integration/tutorService.test.ts:73:43

const { idToken } = await registerLearner();

test("Get tutor service availability without token", async () => {
await supertest(app)
.get(`/api/v1/tutors/services/${tutorServiceId}/availability`)
.expect(401);
});

test("Get tutor service availability with token", async () => {
const res = await supertest(app)
.get(`/api/v1/tutors/services/${tutorServiceId}/availability`)
.set("Authorization", `Bearer ${idToken}`)
.expect(200);

console.log(res.body.data);
expect(res.body.data).toBeTruthy();
});
});
6 changes: 3 additions & 3 deletions tests/services/tutorService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe("TutorServiceService", () => {
50_000, 100_000, 150_000, 200_000,
]),
typeLesson: faker.helpers.arrayElement(["online", "offline", "both"]),
availability: { monday: ["08:00", "10:00"] },
availability: { 1: ["08:00", "10:00"] },
};

const tutorRef = { id: tutorId };
Expand Down Expand Up @@ -91,7 +91,7 @@ describe("TutorServiceService", () => {
50_000, 100_000, 150_000, 200_000,
]),
typeLesson: faker.helpers.arrayElement(["online", "offline", "both"]),
availability: { monday: ["08:00", "10:00"] },
availability: { 0: ["08:00", "10:00"] },
};

await expect(
Expand Down Expand Up @@ -334,7 +334,7 @@ describe("TutorServiceService", () => {
hourlyRate: faker.helpers.arrayElement([
50_000, 100_000, 150_000, 200_000,
]),
availability: { sunday: ["10:00"] },
availability: { 0: ["10:00"] },
};

const updateMock = vi.fn().mockResolvedValue({});
Expand Down

0 comments on commit 4b82acf

Please sign in to comment.