diff --git a/src/module/tutor-service/tutorService.controller.ts b/src/module/tutor-service/tutorService.controller.ts index ac5f09b..c7bd510 100644 --- a/src/module/tutor-service/tutorService.controller.ts +++ b/src/module/tutor-service/tutorService.controller.ts @@ -85,6 +85,30 @@ export const getService: Controller = async ( } }; +export const getServiceAvailability: Controller = 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; diff --git a/src/module/tutor-service/tutorService.route.ts b/src/module/tutor-service/tutorService.route.ts index f14d90e..faaa7d5 100644 --- a/src/module/tutor-service/tutorService.route.ts +++ b/src/module/tutor-service/tutorService.route.ts @@ -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); diff --git a/src/module/tutor-service/tutorService.schema.ts b/src/module/tutor-service/tutorService.schema.ts index b3fcc10..1508ddf 100644 --- a/src/module/tutor-service/tutorService.schema.ts +++ b/src/module/tutor-service/tutorService.schema.ts @@ -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) => { diff --git a/src/module/tutor-service/tutorService.seeder.ts b/src/module/tutor-service/tutorService.seeder.ts index bd8145e..ab6c2ef 100644 --- a/src/module/tutor-service/tutorService.seeder.ts +++ b/src/module/tutor-service/tutorService.seeder.ts @@ -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", diff --git a/src/module/tutor-service/tutorService.service.ts b/src/module/tutor-service/tutorService.service.ts index 11ce8b1..92099d3 100644 --- a/src/module/tutor-service/tutorService.service.ts +++ b/src/module/tutor-service/tutorService.service.ts @@ -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["body"], diff --git a/tests/integration/tutorService.test.ts b/tests/integration/tutorService.test.ts index 9f55d34..b581891 100644 --- a/tests/integration/tutorService.test.ts +++ b/tests/integration/tutorService.test.ts @@ -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(); @@ -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; + + 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(); + }); }); diff --git a/tests/services/tutorService.test.ts b/tests/services/tutorService.test.ts index a6ca03a..cfb18ad 100644 --- a/tests/services/tutorService.test.ts +++ b/tests/services/tutorService.test.ts @@ -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 }; @@ -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( @@ -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({});