Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
node_modules
apps/*/node_modules
apps/*/dist
apps/*/coverage
coverage
.git
.gitignore
.dockerignore
*.log
.env
.env.*
!.env.example
Comment thread
coderabbitai[bot] marked this conversation as resolved.
deploy/lightsail/*.env
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ apps/*/node_modules/
apps/*/dist/
apps/*/coverage/
apps/web/.tanstack/

deploy/lightsail/.env
deploy/lightsail/api.env
2 changes: 2 additions & 0 deletions apps/api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ DB_USER=postgres
DB_PASSWORD=postgres
DB_NAME=nail-addison
JWT_SECRET=replace-me
CORS_ORIGINS=http://localhost:5173
COOKIE_SAME_SITE=lax
MAIL_USER=your-email@example.com
MAIL_PASS=replace-me
34 changes: 34 additions & 0 deletions apps/api/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
FROM node:20-alpine AS build

WORKDIR /app

COPY package.json package-lock.json ./
COPY apps/api/package.json apps/api/package.json
COPY apps/web/package.json apps/web/package.json

RUN npm ci

COPY apps/api apps/api

RUN npm run build:api
RUN npm prune --omit=dev

FROM node:20-alpine AS runtime

ENV NODE_ENV=production
ENV PORT=8080

WORKDIR /app

COPY --from=build --chown=node:node /app/package.json /app/package-lock.json ./
COPY --from=build --chown=node:node /app/node_modules ./node_modules
COPY --from=build --chown=node:node /app/apps/api/package.json ./apps/api/package.json
COPY --from=build --chown=node:node /app/apps/api/dist ./apps/api/dist

RUN chown -R node:node /app

USER node

EXPOSE 8080

CMD ["node", "apps/api/dist/server.js"]
22 changes: 22 additions & 0 deletions apps/api/src/config/_tests_/app.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { getAuthCookieOptions } from "../app";

describe("app config", () => {
const originalEnv = { ...process.env };

afterEach(() => {
process.env = { ...originalEnv };
});

it("forces secure cookies when SameSite is none", () => {
process.env.COOKIE_SAME_SITE = "none";
process.env.COOKIE_SECURE = "false";
process.env.NODE_ENV = "development";

expect(getAuthCookieOptions()).toEqual(
expect.objectContaining({
sameSite: "none",
secure: true,
})
);
});
});
37 changes: 37 additions & 0 deletions apps/api/src/config/_tests_/database-startup.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { getDatabaseSyncOptions, syncDatabaseForStartup } from "../database-startup";

describe("database startup sync behavior", () => {
it("skips Sequelize sync in production by default", async () => {
const sequelize = { sync: jest.fn() };

await syncDatabaseForStartup(sequelize, {
NODE_ENV: "production",
});

expect(sequelize.sync).not.toHaveBeenCalled();
});

it("keeps non-production startup sync enabled by default", async () => {
const sequelize = { sync: jest.fn().mockResolvedValue(undefined) };

await syncDatabaseForStartup(sequelize, {
NODE_ENV: "development",
});

expect(sequelize.sync).toHaveBeenCalledWith();
});

it("allows explicit production sync when requested", () => {
expect(getDatabaseSyncOptions({
NODE_ENV: "production",
DB_SYNC: "true",
})).toEqual({});
});

it("preserves explicit alter sync mode", () => {
expect(getDatabaseSyncOptions({
NODE_ENV: "development",
DB_SYNC_ALTER: "true",
})).toEqual({ alter: true });
});
});
67 changes: 67 additions & 0 deletions apps/api/src/config/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type { CookieOptions } from "express";

const DEFAULT_WEB_ORIGIN = "http://localhost:5173";
const ONE_HOUR_MS = 60 * 60 * 1000;

type SameSiteMode = "strict" | "lax" | "none";

function parseList(value: string | undefined) {
return (value ?? "")
.split(",")
.map((item) => item.trim())
.filter(Boolean);
}

function parseBoolean(value: string | undefined) {
if (value === undefined) return undefined;
const normalized = value.trim().toLowerCase();
if (normalized === "true") return true;
if (normalized === "false") return false;
return undefined;
}

export function getCorsOrigins() {
const configuredOrigins = parseList(
process.env.CORS_ORIGINS ?? process.env.WEB_ORIGIN
);

return configuredOrigins.length > 0
? configuredOrigins
: [DEFAULT_WEB_ORIGIN];
Comment on lines +24 to +30
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

How does JavaScript nullish coalescing (??) behave with empty strings, and why is a ?? bdifferent froma || bwhena = ""?

💡 Result:

When a is the empty string "": - ""?? b evaluates to "" (the left operand), because?? only falls back when the left operand is null or undefined, not when it’s an empty string [1]. - "" || b evaluates to b, because || falls back for any falsy left operand value, and the empty string is falsy [1]. So a?? b differs from a || b specifically because "" is falsy but not nullish:?? preserves "" while || treats it like “missing” and replaces it with the right-hand side [1].

Citations:


🏁 Script executed:

# First, let's check if the file exists and read the relevant section
cat -n apps/api/src/config/app.ts | head -50

Repository: Nail-Addison/nail-star

Length of output: 1775


🏁 Script executed:

# Search for the parseList function definition
rg "function parseList|const parseList|export.*parseList" -A 5

Repository: Nail-Addison/nail-star

Length of output: 385


🏁 Script executed:

# Check if there are any test files that might show parseList behavior
fd -e test.ts -e spec.ts -e test.js | xargs grep -l "parseList" 2>/dev/null || echo "No test files found with parseList"

Repository: Nail-Addison/nail-star

Length of output: 101


getCorsOrigins fallback logic skips WEB_ORIGIN when CORS_ORIGINS is empty.

At line 25, when CORS_ORIGINS="", the nullish coalescing operator ?? treats the empty string as a valid value (not null/undefined), so it never evaluates WEB_ORIGIN. Even though parseList("") returns an empty array (due to the filter), the code then defaults to DEFAULT_WEB_ORIGIN instead of checking WEB_ORIGIN as a fallback. This can silently produce the wrong allowlist in deployments where CORS_ORIGINS is set to empty string but WEB_ORIGIN has a valid value.

Suggested patch
 export function getCorsOrigins() {
-    const configuredOrigins = parseList(
-        process.env.CORS_ORIGINS ?? process.env.WEB_ORIGIN
-    );
-
-    return configuredOrigins.length > 0
-        ? configuredOrigins
-        : [DEFAULT_WEB_ORIGIN];
+    const corsOrigins = parseList(process.env.CORS_ORIGINS);
+    if (corsOrigins.length > 0) return corsOrigins;
+
+    const webOrigin = parseList(process.env.WEB_ORIGIN);
+    return webOrigin.length > 0 ? webOrigin : [DEFAULT_WEB_ORIGIN];
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const configuredOrigins = parseList(
process.env.CORS_ORIGINS ?? process.env.WEB_ORIGIN
);
return configuredOrigins.length > 0
? configuredOrigins
: [DEFAULT_WEB_ORIGIN];
export function getCorsOrigins() {
const corsOrigins = parseList(process.env.CORS_ORIGINS);
if (corsOrigins.length > 0) return corsOrigins;
const webOrigin = parseList(process.env.WEB_ORIGIN);
return webOrigin.length > 0 ? webOrigin : [DEFAULT_WEB_ORIGIN];
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/config/app.ts` around lines 24 - 30, getCorsOrigins currently
uses parseList(process.env.CORS_ORIGINS ?? process.env.WEB_ORIGIN) so an empty
CORS_ORIGINS ("") short-circuits WEB_ORIGIN; instead, call parseList on
CORS_ORIGINS first and if that returns an empty array, call parseList on
WEB_ORIGIN, and finally default to [DEFAULT_WEB_ORIGIN] if still empty. Update
getCorsOrigins to: let origins = parseList(process.env.CORS_ORIGINS); if
(origins.length === 0) origins = parseList(process.env.WEB_ORIGIN); return
origins.length > 0 ? origins : [DEFAULT_WEB_ORIGIN]; reference parseList,
getCorsOrigins, CORS_ORIGINS, WEB_ORIGIN, and DEFAULT_WEB_ORIGIN.

}

export function getJwtSecret() {
const secret = process.env.JWT_SECRET?.trim();

if (!secret || secret === "replace-me" || secret === "undefined") {
throw new Error("JWT_SECRET is not configured");
}

return secret;
}

export function getCookieSameSite(): SameSiteMode {
const configured = process.env.COOKIE_SAME_SITE?.trim().toLowerCase();

if (configured === "strict" || configured === "lax" || configured === "none") {
return configured;
}

return "lax";
}

export function getAuthCookieOptions(): CookieOptions {
const sameSite = getCookieSameSite();
const secureOverride = parseBoolean(process.env.COOKIE_SECURE);
const secure = sameSite === "none"
? true
: (secureOverride ?? process.env.NODE_ENV === "production");

return {
httpOnly: true,
secure,
sameSite,
maxAge: ONE_HOUR_MS,
path: "/",
};
}
38 changes: 38 additions & 0 deletions apps/api/src/config/database-startup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { Sequelize } from "sequelize";

type SyncOptions = Parameters<Sequelize["sync"]>[0];
type StartupEnv = Partial<Pick<NodeJS.ProcessEnv, "NODE_ENV" | "DB_SYNC" | "DB_SYNC_ALTER">>;

export function getDatabaseSyncOptions(env: StartupEnv = process.env): SyncOptions | undefined | null {
if (env.DB_SYNC_ALTER === "true") {
return { alter: true };
}

if (env.DB_SYNC === "true") {
return {};
}

if (env.NODE_ENV === "production" && env.DB_SYNC !== "true") {
return null;
Comment on lines +7 to +16
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't let DB_SYNC_ALTER bypass the production safety check.

Line 7 returns { alter: true } before the production guard runs, so DB_SYNC_ALTER=true still mutates the schema on startup in production even when DB_SYNC is unset. That reintroduces risky automatic DDL during deploys.

Suggested fix
 export function getDatabaseSyncOptions(env: StartupEnv = process.env): SyncOptions | undefined | null {
+    const isProduction = env.NODE_ENV === "production";
+
+    if (isProduction && env.DB_SYNC !== "true") {
+        return null;
+    }
+
     if (env.DB_SYNC_ALTER === "true") {
         return { alter: true };
     }
 
     if (env.DB_SYNC === "true") {
         return {};
     }
-
-    if (env.NODE_ENV === "production" && env.DB_SYNC !== "true") {
-        return null;
-    }
 
     return undefined;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (env.DB_SYNC_ALTER === "true") {
return { alter: true };
}
if (env.DB_SYNC === "true") {
return {};
}
if (env.NODE_ENV === "production" && env.DB_SYNC !== "true") {
return null;
export function getDatabaseSyncOptions(env: StartupEnv = process.env): SyncOptions | undefined | null {
const isProduction = env.NODE_ENV === "production";
if (isProduction && env.DB_SYNC !== "true") {
return null;
}
if (env.DB_SYNC_ALTER === "true") {
return { alter: true };
}
if (env.DB_SYNC === "true") {
return {};
}
return undefined;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/config/database-startup.ts` around lines 7 - 16, The current
order lets DB_SYNC_ALTER=true return { alter: true } before the production
guard, so change the conditional logic so production safety wins: check if
env.NODE_ENV === "production" && env.DB_SYNC !== "true" and return null before
honoring DB_SYNC_ALTER, or require both env.DB_SYNC === "true" and
env.DB_SYNC_ALTER === "true" to return { alter: true } (otherwise fall through);
update the conditionals that reference DB_SYNC_ALTER, DB_SYNC and NODE_ENV
accordingly.

}

return undefined;
}

export async function syncDatabaseForStartup(
sequelize: Pick<Sequelize, "sync">,
env: StartupEnv = process.env
) {
const syncOptions = getDatabaseSyncOptions(env);

if (syncOptions === null) {
return;
}

if (syncOptions === undefined) {
await sequelize.sync();
return;
}

await sequelize.sync(syncOptions);
}
37 changes: 37 additions & 0 deletions apps/api/src/controllers/_tests_/check-in.controller.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { NextFunction, Request, Response } from "express";
import { selectAppointmentForCheckIn } from "../check-in.controller";
import * as CheckInService from "../../services/check-in.service";

jest.mock("../../services/check-in.service", () => ({
selectAppointmentForCheckIn: jest.fn(),
}));

describe("Check-in Controller", () => {
let next: jest.MockedFunction<NextFunction>;
let badRequest: jest.Mock;
let ok: jest.Mock;

beforeEach(() => {
jest.clearAllMocks();
next = jest.fn();
badRequest = jest.fn();
ok = jest.fn();
});

it("rejects invalid UUIDs before calling the service", async () => {
const req = {
params: { id: "not-a-uuid" },
body: { appointmentId: "11111111-1111-1111-1111-111111111111" },
} as Partial<Request>;
const res = {
badRequest: badRequest as any,
ok: ok as any,
} as Partial<Response>;

await selectAppointmentForCheckIn(req as Request, res as Response, next);

expect(badRequest).toHaveBeenCalledWith("Invalid check-in ID or appointment ID");
expect(CheckInService.selectAppointmentForCheckIn).not.toHaveBeenCalled();
expect(ok).not.toHaveBeenCalled();
});
});
76 changes: 72 additions & 4 deletions apps/api/src/controllers/appointment.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
ConfirmAppointmentSchema
} from "../utils/zod_schemas/appointment.schema";
import { AppointmentStatus } from "../models/appointment.model";
import { sendMail } from "../services/email.service";
import { getBookingConfirmationTemplate } from "../utils/email-template.util";

const appointmentController = {
reserveAppointment: async (req: Request, res: Response, next: NextFunction) => {
Expand All @@ -25,10 +27,35 @@ const appointmentController = {
}
},

getReservedAppointmentById: async (req: Request, res: Response, next: NextFunction) => {
try {
const { id } = req.params;
const reservation = await appointmentService.getReservedAppointmentById(id as string);
return res.ok(reservation, "Get reservation successfully");
Comment on lines +30 to +34
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject malformed reservation IDs and staffId before calling the service.

These handlers pass arbitrary strings into UUID-backed lookups. Bad input will tend to fail in Sequelize/database code and come back as a 500 instead of a request validation error.

Also applies to: 40-49, 55-59

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/controllers/appointment.controller.ts` around lines 30 - 34,
Validate incoming UUIDs in the controller before calling the service: in
getReservedAppointmentById (and the other handlers referenced around lines 40-49
and 55-59), check that req.params.id and any req.params.staffId are valid UUIDs
(use a UUID validator or a strict regex) and if not return a 400/Bad Request
(e.g., res.status(400).json(...)/res.badRequest) with a clear message instead of
forwarding the malformed value to appointmentService.getReservedAppointmentById
or other service methods; only call the service when validation passes.

} catch (err) {
next(err);
}
},

updateReservedAppointment: async (req: Request, res: Response, next: NextFunction) => {
try {
const { id } = req.params;
const staffId = typeof req.body?.staffId === "string" ? req.body.staffId : undefined;

const reservation = await appointmentService.updateReservedAppointment(id as string, {
staffId,
});

return res.ok(reservation, "Reservation updated successfully");
} catch (err) {
next(err);
}
},

unreserveAppointment: async (req: Request, res: Response, next: NextFunction) => {
try {
const { id } = req.params;
await appointmentService.deleteAppointment(id as string);
await appointmentService.deleteReservedAppointment(id as string);
return res.ok(null, "Slot unreserved successfully");
Comment on lines +30 to 59
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Reservation IDs are acting as bearer tokens here.

Per apps/api/src/routes/appointment.routes.ts:7-10, these handlers are public, and the only authorization input is :id. Anyone who gets a pending reservation UUID can read it, reassign staff, or cancel it. This needs caller-bound protection, e.g. auth ownership checks or a separate signed reservation secret issued at reserve time.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/controllers/appointment.controller.ts` around lines 30 - 59, The
handlers getReservedAppointmentById, updateReservedAppointment, and
unreserveAppointment currently treat the reservation UUID as an open bearer
token; make these endpoints enforce caller-bound authorization by validating
ownership or a signed reservation secret before performing any
read/update/delete. Update each controller to (a) require an authenticated
principal (e.g. req.user.id) or accept a reservationSecret header/body issued at
reservation time, (b) fetch the reservation via
appointmentService.getReservedAppointmentById and verify reservation.ownerId ===
req.user.id (or validate the signed reservationSecret) and only then call
appointmentService.updateReservedAppointment or deleteReservedAppointment; if
validation fails return 401/403. Alternatively, move this logic into an
authorization middleware invoked on these routes and reference the same
appointmentService verification to avoid duplicating checks.

} catch (err) {
next(err);
Expand All @@ -38,7 +65,7 @@ const appointmentController = {
confirmAppointment: async (req: Request, res: Response, next: NextFunction) => {
try {
const { id } = req.params;
const { serviceIds, status, phoneNumber, customerId, customerName } = ConfirmAppointmentSchema.parse(req.body);
const { serviceIds, status, phoneNumber, customerId, customerName, email } = ConfirmAppointmentSchema.parse(req.body);

const appointment = await appointmentService.confirmAppointment(id as string, {
serviceIds,
Expand All @@ -48,6 +75,47 @@ const appointmentController = {
customerName
});

if (email) {
const scheduledAt = new Date(appointment.scheduledAt);
const services = appointment.get("services") as Array<any> | undefined ?? [];
const totalPrice = services.reduce((sum: number, service: any) => {
return sum + Number(service.promotionPrice ?? service.startingPrice ?? 0);
}, 0);

try {
await sendMail(
email,
"Your Nail Session is Confirmed! - Nail Star",
getBookingConfirmationTemplate({
fullName: customerName ?? appointment.customerName ?? "Guest",
email,
phone: phoneNumber ?? appointment.phoneNumber ?? "",
date: new Intl.DateTimeFormat("en-GB", {
day: "2-digit",
month: "2-digit",
year: "numeric",
timeZone: "Asia/Bangkok",
}).format(scheduledAt),
time: new Intl.DateTimeFormat("en-GB", {
hour: "2-digit",
minute: "2-digit",
hour12: false,
timeZone: "Asia/Bangkok",
}).format(scheduledAt),
Comment on lines +93 to +104
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the salon's local timezone in the email.

This email is branded for Addison, Texas in apps/api/src/utils/email-template.util.ts:104-126, but both formatters hard-code Asia/Bangkok. Customers will receive the wrong local date/time.

🕒 Suggested fix
+                const appointmentTimeZone = "America/Chicago";
+
                 try {
                     await sendMail(
                         email,
                         "Your Nail Session is Confirmed! - Nail Star",
                         getBookingConfirmationTemplate({
@@
-                            date: new Intl.DateTimeFormat("en-GB", {
+                            date: new Intl.DateTimeFormat("en-US", {
                                 day: "2-digit",
                                 month: "2-digit",
                                 year: "numeric",
-                                timeZone: "Asia/Bangkok",
+                                timeZone: appointmentTimeZone,
                             }).format(scheduledAt),
-                            time: new Intl.DateTimeFormat("en-GB", {
+                            time: new Intl.DateTimeFormat("en-US", {
                                 hour: "2-digit",
                                 minute: "2-digit",
                                 hour12: false,
-                                timeZone: "Asia/Bangkok",
+                                timeZone: appointmentTimeZone,
                             }).format(scheduledAt),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/controllers/appointment.controller.ts` around lines 93 - 104,
The date/time formatters are hard-coded to "Asia/Bangkok" and must use the
salon's actual timezone when formatting scheduledAt; update the two
Intl.DateTimeFormat calls in apps/api/src/controllers/appointment.controller.ts
to accept a timezone variable (e.g., salon.timeZone or salon.timezone) instead
of the literal "Asia/Bangkok", defaulting to a sensible fallback like "UTC" if
the salon timezone is missing, and apply that same timezone variable to both the
date and time formatters that format scheduledAt.

isGuest: !customerId,
services: services.map((service: any) => ({
name: service.name,
price: Number(service.promotionPrice ?? service.startingPrice ?? 0),
categoryName: service.categoryInfo?.name ?? "",
})),
totalPrice,
})
);
} catch (emailError) {
console.error("Booking confirmation email failed:", emailError);
}
Comment on lines +85 to +116
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't wait on SMTP before returning confirmation success.

The appointment is already confirmed before the mail send starts. Because apps/api/src/services/email.service.ts:11-29 awaits nodemailer and rethrows, this path now makes confirmation latency and availability depend on the mail provider even when the write already succeeded.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/controllers/appointment.controller.ts` around lines 85 - 116,
The controller currently awaits sendMail in appointment confirmation which ties
API latency/availability to SMTP; change the call in appointment.controller.ts
so sending is fire-and-forget instead of awaited: invoke sendMail(...) without
awaiting (e.g., call it and attach a .catch to log errors or use
void/sendMail(...).catch(...)) so confirmation returns immediately while still
logging any email errors; keep the same arguments (sendMail,
getBookingConfirmationTemplate, and the computed template values) and remove the
surrounding await/try that blocks the response.

}

return res.ok(appointment, "Appointment confirmed successfully");
} catch (err) {
next(err);
Expand All @@ -56,7 +124,7 @@ const appointmentController = {

getAllAppointments: async (req: Request, res: Response, next: NextFunction) => {
try {
const appointments = await appointmentService.getAllAppointments();
const appointments = await appointmentService.getAllAppointments(req.user);
return res.ok(appointments, "Get appointments successfully");
} catch (err) {
next(err);
Expand Down Expand Up @@ -93,7 +161,7 @@ const appointmentController = {
getAppointmentById: async (req: Request, res: Response, next: NextFunction) => {
try {
const { id } = req.params;
const appointment = await appointmentService.getAppointmentById(id as string);
const appointment = await appointmentService.getAppointmentById(id as string, req.user);
return res.ok(appointment, "Get appointment successfully");
} catch (err) {
next(err);
Expand Down
Loading