-
Notifications
You must be signed in to change notification settings - Fork 1
refactor: redirect check-in routes to main check-in page #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f9f17d7
20062af
8bc5fdd
86b40ca
7dcac4b
43e8ad6
e6c5bba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 | ||
| deploy/lightsail/*.env | ||
| 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"] |
| 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, | ||
| }) | ||
| ); | ||
| }); | ||
| }); |
| 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 }); | ||
| }); | ||
| }); |
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 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 -50Repository: Nail-Addison/nail-star Length of output: 1775 🏁 Script executed: # Search for the parseList function definition
rg "function parseList|const parseList|export.*parseList" -A 5Repository: 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
At line 25, when 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| 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: "/", | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't let Line 7 returns 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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(); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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) => { | ||
|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reject malformed reservation IDs and 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 |
||
| } 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reservation IDs are acting as bearer tokens here. Per 🤖 Prompt for AI Agents |
||
| } catch (err) { | ||
| next(err); | ||
|
|
@@ -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, | ||
|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use the salon's local timezone in the email. This email is branded for Addison, Texas in 🕒 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 |
||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't wait on SMTP before returning confirmation success. The appointment is already confirmed before the mail send starts. Because 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| return res.ok(appointment, "Appointment confirmed successfully"); | ||
| } catch (err) { | ||
| next(err); | ||
|
|
@@ -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); | ||
|
|
@@ -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); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.