Fix notifications to show appointment data; reverting comming a65c00bb8c and adding permission check

This commit is contained in:
Karl Ludwig Weise
2026-05-31 21:43:51 +02:00
parent 400ad2574f
commit 16474d96c5
2 changed files with 181 additions and 2 deletions
@@ -1,11 +1,142 @@
import { json } from "@sveltejs/kit";
import { AppointmentService } from "$lib/server/services/appointment-service";
import { BackendError, InternalError, logError, ValidationError } from "$lib/server/utils/errors";
import {
BackendError,
InternalError,
logError,
NotFoundError,
ValidationError,
} from "$lib/server/utils/errors";
import type { RequestHandler } from "@sveltejs/kit";
import { registerOpenAPIRoute } from "$lib/server/openapi";
import logger from "$lib/logger";
import { checkPermission } from "$lib/server/utils/permissions";
// Register OpenAPI documentation for GET
registerOpenAPIRoute("/tenants/{id}/appointments/{appointmentId}", "GET", {
summary: "Get appointment by ID",
description:
"Retrieves a specific appointment by its ID. Accessible to dashboard users. Required for notification previews.",
tags: ["Appointments"],
parameters: [
{
name: "id",
in: "path",
required: true,
schema: { type: "string", format: "uuid" },
description: "Tenant ID",
},
{
name: "appointmentId",
in: "path",
required: true,
schema: { type: "string", format: "uuid" },
description: "Appointment ID",
},
],
responses: {
"200": {
description: "Appointment retrieved successfully",
content: {
"application/json": {
schema: {
type: "object",
properties: {
appointment: {
type: "object",
properties: {
id: { type: "string", format: "uuid", description: "Appointment ID" },
tunnelId: { type: "string", format: "uuid", description: "Client tunnel ID" },
channelId: { type: "string", format: "uuid", description: "Channel ID" },
appointmentDate: {
type: "string",
format: "date-time",
description: "Appointment date and time",
},
expiryDate: {
type: "string",
format: "date",
description: "Data expiry date (nullable)",
},
status: {
type: "string",
enum: ["NEW", "CONFIRMED", "HELD", "REJECTED", "NO_SHOW"],
description: "Appointment status",
},
encryptedPayload: {
type: "string",
description: "Encrypted appointment data (nullable)",
},
iv: {
type: "string",
description: "Initialization vector for encryption (nullable)",
},
authTag: {
type: "string",
description: "Authentication tag for encryption (nullable)",
},
createdAt: {
type: "string",
format: "date-time",
description: "Creation timestamp (nullable)",
},
updatedAt: {
type: "string",
format: "date-time",
description: "Last update timestamp (nullable)",
},
},
required: ["id", "tunnelId", "channelId", "appointmentDate", "status"],
},
},
required: ["appointment"],
},
},
},
},
"400": {
description: "Invalid input data",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/Error" },
},
},
},
"401": {
description: "Authentication required",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/Error" },
},
},
},
"403": {
description: "Insufficient permissions",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/Error" },
},
},
},
"404": {
description: "Appointment not found",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/Error" },
},
},
},
"500": {
description: "Internal server error",
content: {
"application/json": {
schema: { $ref: "#/components/schemas/Error" },
},
},
},
},
});
// Register OpenAPI documentation for DELETE
registerOpenAPIRoute("/tenants/{id}/appointments/{appointmentId}", "DELETE", {
summary: "Delete appointment",
@@ -85,6 +216,52 @@ registerOpenAPIRoute("/tenants/{id}/appointments/{appointmentId}", "DELETE", {
},
});
export const GET: RequestHandler = async ({ params, locals }) => {
const log = logger.setContext("API");
try {
const tenantId = params.id;
const appointmentId = params.appointmentId;
if (!tenantId || !appointmentId) {
throw new ValidationError("Tenant ID and appointment ID are required");
}
checkPermission(locals, tenantId, true);
log.debug("Getting appointment by ID", {
tenantId,
appointmentId,
requestedBy: locals.user?.id,
});
const appointmentService = await AppointmentService.forTenant(tenantId);
const appointment = await appointmentService.getAppointmentById(appointmentId);
if (!appointment) {
throw new NotFoundError("Appointment not found");
}
log.debug("Appointment retrieved successfully", {
tenantId,
appointmentId,
requestedBy: locals.user?.id,
});
return json({
appointment,
});
} catch (error) {
logError(log)("Error getting appointment", error, locals.user?.id, params.id);
if (error instanceof BackendError) {
return error.toJson();
}
return new InternalError().toJson();
}
};
export const DELETE: RequestHandler = async ({ params, locals }) => {
const log = logger.setContext("API");
@@ -381,7 +381,9 @@ export const POST: RequestHandler = async ({ request, params }) => {
(await notificationService).createNotification({
type: "APPOINTMENT_REQUESTED",
channelId: validatedData.channelId,
metaData: { appointmentId: result.id },
metaData: {
appointmentId: result.id,
},
});
}
if (validatedData.clientEmail) {