From 7942e151c643acafe5804ec6b5adffd9d6c5398e Mon Sep 17 00:00:00 2001 From: Hendrik Belitz Date: Mon, 4 May 2026 15:26:10 +0200 Subject: [PATCH 1/6] Removed expiry_date from appointments. Added housekeeping task to remove old appointments from db. Co-authored-by: Copilot --- src/lib/server/db/central-schema.ts | 2 - src/lib/server/db/tenant-schema.ts | 2 - .../server/services/appointment-service.ts | 24 +- src/lib/server/services/startup-service.ts | 8 + .../api/tenants/[id]/appointments/+server.ts | 5 - .../appointments/[appointmentId]/+server.ts | 5 - .../[appointmentId]/cancel/+server.ts | 5 - .../cancel/__tests__/cancel-api.test.ts | 1 - .../[appointmentId]/confirm/+server.ts | 5 - .../confirm/__tests__/confirm-api.test.ts | 1 - .../appointments/add-to-tunnel/+server.ts | 1 - tenant-migrations/0015_loud_tarot.sql | 1 + tenant-migrations/meta/0015_snapshot.json | 1025 +++++++++++++++++ tenant-migrations/meta/_journal.json | 9 +- 14 files changed, 1064 insertions(+), 30 deletions(-) create mode 100644 tenant-migrations/0015_loud_tarot.sql create mode 100644 tenant-migrations/meta/0015_snapshot.json diff --git a/src/lib/server/db/central-schema.ts b/src/lib/server/db/central-schema.ts index c7cf930..bb6bc1f 100644 --- a/src/lib/server/db/central-schema.ts +++ b/src/lib/server/db/central-schema.ts @@ -107,8 +107,6 @@ export const tenantConfig = pgTable( }), ); -// TODO: Filter in API für bestimmte Rollen - export const user = pgTable( "user", { diff --git a/src/lib/server/db/tenant-schema.ts b/src/lib/server/db/tenant-schema.ts index 2f345bb..c67cf5e 100644 --- a/src/lib/server/db/tenant-schema.ts +++ b/src/lib/server/db/tenant-schema.ts @@ -173,8 +173,6 @@ export const appointment = pgTable("appointment", { duration: integer("duration").notNull(), /** Timezone of client booking appointment */ timezone: text("timezone").notNull(), - /** When appointment data expires and can be auto-deleted */ - expiryDate: date("expiry_date"), /** Current status of the appointment - defaults depend on channel's requiresConfirmation setting */ status: appointmentStatusEnum("status").notNull(), /** Encrypted appointment data (name, email, phone) - Legacy field */ diff --git a/src/lib/server/services/appointment-service.ts b/src/lib/server/services/appointment-service.ts index e0be8a6..d790325 100644 --- a/src/lib/server/services/appointment-service.ts +++ b/src/lib/server/services/appointment-service.ts @@ -4,7 +4,7 @@ import { type SelectAppointment } from "../db/tenant-schema"; import * as centralSchema from "../db/central-schema"; import logger from "$lib/logger"; import { ValidationError, NotFoundError, InternalError, ConflictError } from "../utils/errors"; -import { and, eq, gte, lte, asc } from "drizzle-orm"; +import { and, eq, gte, lte, lt, asc } from "drizzle-orm"; import type { AppointmentResponse } from "$lib/types/appointment"; import { sendAppointmentCreatedEmail, @@ -19,6 +19,8 @@ import { challengeStore } from "./challenge-store"; import { challengeThrottleService } from "./challenge-throttle"; import { timingSafeEqual } from "node:crypto"; +const CUTOFF_DAYS = 90; + export interface ClientTunnelData { tunnelId: string; channelId: string; @@ -78,6 +80,25 @@ export class AppointmentService { } } + async cleanupExpiredAppointments(): Promise { + const log = logger.setContext("AppointmentService"); + const db = await this.getDb(); + + const cutoffDate = new Date(); + cutoffDate.setDate(cutoffDate.getDate() - CUTOFF_DAYS); + + const deletedAppointments = await db + .delete(tenantSchema.appointment) + .where(lt(tenantSchema.appointment.appointmentDate, cutoffDate)) + .returning({ id: tenantSchema.appointment.id }); + + log.info("Expired appointments cleaned up", { + tenantId: this.tenantId, + deletedCount: deletedAppointments.length, + cutoffDate: cutoffDate.toISOString(), + }); + } + async hasAppointments(): Promise { const log = logger.setContext("AppointmentService"); log.debug("Checking if tenant has appointments", { tenantId: this.tenantId }); @@ -738,7 +759,6 @@ export class AppointmentService { encryptedPayload: tenantSchema.appointment.encryptedPayload, iv: tenantSchema.appointment.iv, authTag: tenantSchema.appointment.authTag, - expiryDate: tenantSchema.appointment.expiryDate, createdAt: tenantSchema.appointment.createdAt, updatedAt: tenantSchema.appointment.updatedAt, }); diff --git a/src/lib/server/services/startup-service.ts b/src/lib/server/services/startup-service.ts index 91ef37a..ec205b3 100644 --- a/src/lib/server/services/startup-service.ts +++ b/src/lib/server/services/startup-service.ts @@ -6,6 +6,7 @@ import { InviteService } from "./invite-service"; import { SessionService } from "../auth/session-service"; import { ClientPinResetService } from "./client-pin-reset-service"; import { UniversalLogger } from "$lib/logger"; +import { AppointmentService } from "./appointment-service"; const logger = new UniversalLogger().setContext("StartupService"); const TWELVE_HOURS_IN_MS = 12 * 60 * 60 * 1000; @@ -172,6 +173,13 @@ export class StartupService { for (const tenantData of tenants) { try { const pinResetService = await ClientPinResetService.forTenant(tenantData.id); + const appointmentService = await AppointmentService.forTenant(tenantData.id); + await appointmentService.cleanupExpiredAppointments(); + logger.info("Cleaned up expired appointments for tenant", { + tenantId: tenantData.id, + shortName: tenantData.shortName, + }); + const deletedTokens = await pinResetService.cleanupExpiredTokens(); logger.info(`Cleaned up ${deletedTokens} expired PIN reset tokens for tenant`, { tenantId: tenantData.id, diff --git a/src/routes/api/tenants/[id]/appointments/+server.ts b/src/routes/api/tenants/[id]/appointments/+server.ts index 070fb97..c0e4258 100644 --- a/src/routes/api/tenants/[id]/appointments/+server.ts +++ b/src/routes/api/tenants/[id]/appointments/+server.ts @@ -56,11 +56,6 @@ registerOpenAPIRoute("/tenants/{id}/appointments", "GET", { 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"], diff --git a/src/routes/api/tenants/[id]/appointments/[appointmentId]/+server.ts b/src/routes/api/tenants/[id]/appointments/[appointmentId]/+server.ts index 15f2f9c..0c293e0 100644 --- a/src/routes/api/tenants/[id]/appointments/[appointmentId]/+server.ts +++ b/src/routes/api/tenants/[id]/appointments/[appointmentId]/+server.ts @@ -54,11 +54,6 @@ registerOpenAPIRoute("/tenants/{id}/appointments/{appointmentId}", "GET", { 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"], diff --git a/src/routes/api/tenants/[id]/appointments/[appointmentId]/cancel/+server.ts b/src/routes/api/tenants/[id]/appointments/[appointmentId]/cancel/+server.ts index 2a1be64..a4f5d7a 100644 --- a/src/routes/api/tenants/[id]/appointments/[appointmentId]/cancel/+server.ts +++ b/src/routes/api/tenants/[id]/appointments/[appointmentId]/cancel/+server.ts @@ -48,11 +48,6 @@ registerOpenAPIRoute("/tenants/{id}/appointments/{appointmentId}/cancel", "PUT", format: "date-time", description: "Appointment date and time", }, - expiryDate: { - type: "string", - format: "date", - description: "Data expiry date (nullable)", - }, status: { type: "string", enum: ["REJECTED"], diff --git a/src/routes/api/tenants/[id]/appointments/[appointmentId]/cancel/__tests__/cancel-api.test.ts b/src/routes/api/tenants/[id]/appointments/[appointmentId]/cancel/__tests__/cancel-api.test.ts index 79baec1..917040f 100644 --- a/src/routes/api/tenants/[id]/appointments/[appointmentId]/cancel/__tests__/cancel-api.test.ts +++ b/src/routes/api/tenants/[id]/appointments/[appointmentId]/cancel/__tests__/cancel-api.test.ts @@ -53,7 +53,6 @@ describe("Appointment Cancel API Route", () => { tunnelId: "tunnel-123", channelId: "channel-123", appointmentDate: "2024-01-01T10:00:00.000Z", - expiryDate: null, status: "REJECTED", encryptedData: null, dataKey: null, diff --git a/src/routes/api/tenants/[id]/appointments/[appointmentId]/confirm/+server.ts b/src/routes/api/tenants/[id]/appointments/[appointmentId]/confirm/+server.ts index 29b8c70..f7eb857 100644 --- a/src/routes/api/tenants/[id]/appointments/[appointmentId]/confirm/+server.ts +++ b/src/routes/api/tenants/[id]/appointments/[appointmentId]/confirm/+server.ts @@ -79,11 +79,6 @@ registerOpenAPIRoute("/tenants/{id}/appointments/{appointmentId}/confirm", "PUT" format: "date-time", description: "Appointment date and time", }, - expiryDate: { - type: "string", - format: "date", - description: "Data expiry date (nullable)", - }, status: { type: "string", enum: ["CONFIRMED"], diff --git a/src/routes/api/tenants/[id]/appointments/[appointmentId]/confirm/__tests__/confirm-api.test.ts b/src/routes/api/tenants/[id]/appointments/[appointmentId]/confirm/__tests__/confirm-api.test.ts index 948cab6..8c16d9d 100644 --- a/src/routes/api/tenants/[id]/appointments/[appointmentId]/confirm/__tests__/confirm-api.test.ts +++ b/src/routes/api/tenants/[id]/appointments/[appointmentId]/confirm/__tests__/confirm-api.test.ts @@ -53,7 +53,6 @@ describe("Appointment Confirm API Route", () => { tunnelId: "tunnel-123", channelId: "channel-123", appointmentDate: "2024-01-01T10:00:00.000Z", - expiryDate: null, status: "CONFIRMED", encryptedData: null, dataKey: null, diff --git a/src/routes/api/tenants/[id]/appointments/add-to-tunnel/+server.ts b/src/routes/api/tenants/[id]/appointments/add-to-tunnel/+server.ts index 615dd2f..ffd0dd5 100644 --- a/src/routes/api/tenants/[id]/appointments/add-to-tunnel/+server.ts +++ b/src/routes/api/tenants/[id]/appointments/add-to-tunnel/+server.ts @@ -269,7 +269,6 @@ export const POST: RequestHandler = async ({ request, params }) => { agentId: appointment.agentId, appointmentDate: appointment.appointmentDate, timezone: appointment.timezone, - expiryDate: appointment.expiryDate, status: appointment.status, encryptedPayload: appointment.encryptedPayload, duration: appointment.duration, diff --git a/tenant-migrations/0015_loud_tarot.sql b/tenant-migrations/0015_loud_tarot.sql new file mode 100644 index 0000000..bfc2fc5 --- /dev/null +++ b/tenant-migrations/0015_loud_tarot.sql @@ -0,0 +1 @@ +ALTER TABLE "appointment" DROP COLUMN "expiry_date"; \ No newline at end of file diff --git a/tenant-migrations/meta/0015_snapshot.json b/tenant-migrations/meta/0015_snapshot.json new file mode 100644 index 0000000..f1736d9 --- /dev/null +++ b/tenant-migrations/meta/0015_snapshot.json @@ -0,0 +1,1025 @@ +{ + "id": "f2dfb72a-1a9d-4c2b-ade6-d7b5414a02ac", + "prevId": "7e551bff-abaf-48b5-bb08-0ba14cb3292d", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.agent": { + "name": "agent", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "descriptions": { + "name": "descriptions", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "varchar(250000)", + "primaryKey": false, + "notNull": false + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_absence": { + "name": "agent_absence", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "start_date": { + "name": "start_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "end_date": { + "name": "end_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "absence_type": { + "name": "absence_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_absence_agent_id_agent_id_fk": { + "name": "agent_absence_agent_id_agent_id_fk", + "tableFrom": "agent_absence", + "tableTo": "agent", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.appointment": { + "name": "appointment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tunnel_id": { + "name": "tunnel_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "appointment_date": { + "name": "appointment_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "appointment_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "encrypted_data": { + "name": "encrypted_data", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data_key": { + "name": "data_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_payload": { + "name": "encrypted_payload", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "iv": { + "name": "iv", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_tag": { + "name": "auth_tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "appointment_tunnel_id_client_appointment_tunnel_id_fk": { + "name": "appointment_tunnel_id_client_appointment_tunnel_id_fk", + "tableFrom": "appointment", + "tableTo": "client_appointment_tunnel", + "columnsFrom": [ + "tunnel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "appointment_channel_id_channel_id_fk": { + "name": "appointment_channel_id_channel_id_fk", + "tableFrom": "appointment", + "tableTo": "channel", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "appointment_agent_id_agent_id_fk": { + "name": "appointment_agent_id_agent_id_fk", + "tableFrom": "appointment", + "tableTo": "agent", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.appointment_key_share": { + "name": "appointment_key_share", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "appointment_id": { + "name": "appointment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "appointment_key_share_appointment_id_appointment_id_fk": { + "name": "appointment_key_share_appointment_id_appointment_id_fk", + "tableFrom": "appointment_key_share", + "tableTo": "appointment", + "columnsFrom": [ + "appointment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_challenge": { + "name": "auth_challenge", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "challenge": { + "name": "challenge", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_hash": { + "name": "email_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "consumed": { + "name": "consumed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.booking_access_token": { + "name": "booking_access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "email_hash": { + "name": "email_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tunnel_id": { + "name": "tunnel_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "client_public_key": { + "name": "client_public_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "consumed": { + "name": "consumed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.channel": { + "name": "channel", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "names": { + "name": "names", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused": { + "name": "paused", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "descriptions": { + "name": "descriptions", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "requires_confirmation": { + "name": "requires_confirmation", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.channel_agent": { + "name": "channel_agent", + "schema": "", + "columns": { + "channel_id": { + "name": "channel_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "channel_agent_channel_id_channel_id_fk": { + "name": "channel_agent_channel_id_channel_id_fk", + "tableFrom": "channel_agent", + "tableTo": "channel", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "channel_agent_agent_id_agent_id_fk": { + "name": "channel_agent_agent_id_agent_id_fk", + "tableFrom": "channel_agent", + "tableTo": "agent", + "columnsFrom": [ + "agent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.channel_slot_template": { + "name": "channel_slot_template", + "schema": "", + "columns": { + "channel_id": { + "name": "channel_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "slot_template_id": { + "name": "slot_template_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "channel_slot_template_channel_id_channel_id_fk": { + "name": "channel_slot_template_channel_id_channel_id_fk", + "tableFrom": "channel_slot_template", + "tableTo": "channel", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "channel_slot_template_slot_template_id_slotTemplate_id_fk": { + "name": "channel_slot_template_slot_template_id_slotTemplate_id_fk", + "tableFrom": "channel_slot_template", + "tableTo": "slotTemplate", + "columnsFrom": [ + "slot_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.channel_staff": { + "name": "channel_staff", + "schema": "", + "columns": { + "channel_id": { + "name": "channel_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "staff_id": { + "name": "staff_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "channel_staff_channel_id_channel_id_fk": { + "name": "channel_staff_channel_id_channel_id_fk", + "tableFrom": "channel_staff", + "tableTo": "channel", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.client_appointment_tunnel": { + "name": "client_appointment_tunnel", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "email_hash": { + "name": "email_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_public_key": { + "name": "client_public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key_share": { + "name": "private_key_share", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_key_share": { + "name": "client_key_share", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "client_appointment_tunnel_email_hash_unique": { + "name": "client_appointment_tunnel_email_hash_unique", + "nullsNotDistinct": false, + "columns": [ + "email_hash" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.client_pin_reset_token": { + "name": "client_pin_reset_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "token": { + "name": "token", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "email_hash": { + "name": "email_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "used": { + "name": "used", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "client_pin_reset_token_token_unique": { + "name": "client_pin_reset_token_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.client_tunnel_staff_key_share": { + "name": "client_tunnel_staff_key_share", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tunnel_id": { + "name": "tunnel_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "encrypted_tunnel_key": { + "name": "encrypted_tunnel_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "client_tunnel_staff_key_share_tunnel_id_client_appointment_tunnel_id_fk": { + "name": "client_tunnel_staff_key_share_tunnel_id_client_appointment_tunnel_id_fk", + "tableFrom": "client_tunnel_staff_key_share", + "tableTo": "client_appointment_tunnel", + "columnsFrom": [ + "tunnel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification": { + "name": "notification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "staff_id": { + "name": "staff_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "notification_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'APPOINTMENT_CONFIRMED'" + }, + "meta_data": { + "name": "meta_data", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "is_read": { + "name": "is_read", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slotTemplate": { + "name": "slotTemplate", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "weekdays": { + "name": "weekdays", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "from": { + "name": "from", + "type": "time", + "primaryKey": false, + "notNull": true + }, + "to": { + "name": "to", + "type": "time", + "primaryKey": false, + "notNull": true + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.staff_crypto": { + "name": "staff_crypto", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key_share": { + "name": "private_key_share", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "passkey_id": { + "name": "passkey_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.appointment_status": { + "name": "appointment_status", + "schema": "public", + "values": [ + "NEW", + "CONFIRMED", + "HELD", + "REJECTED", + "NO_SHOW" + ] + }, + "public.notification_type": { + "name": "notification_type", + "schema": "public", + "values": [ + "APPOINTMENT_CONFIRMED", + "APPOINTMENT_CANCELLED", + "APPOINTMENT_REQUESTED" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/tenant-migrations/meta/_journal.json b/tenant-migrations/meta/_journal.json index fa1c97d..241bd64 100644 --- a/tenant-migrations/meta/_journal.json +++ b/tenant-migrations/meta/_journal.json @@ -106,6 +106,13 @@ "when": 1775654963847, "tag": "0014_fluffy_ezekiel", "breakpoints": true + }, + { + "idx": 15, + "version": "7", + "when": 1777901096310, + "tag": "0015_loud_tarot", + "breakpoints": true } ] -} +} \ No newline at end of file From 1f2cbb3a0161a961c1e4f6dba83e9feb5c03ba7a Mon Sep 17 00:00:00 2001 From: Hendrik Belitz Date: Fri, 8 May 2026 15:41:01 +0200 Subject: [PATCH 2/6] Appointment service tests --- .../server/services/__tests__/appointment-service.test.ts | 1 - src/lib/server/services/__tests__/schedule-service.test.ts | 5 ----- 2 files changed, 6 deletions(-) diff --git a/src/lib/server/services/__tests__/appointment-service.test.ts b/src/lib/server/services/__tests__/appointment-service.test.ts index afc1027..b4c1def 100644 --- a/src/lib/server/services/__tests__/appointment-service.test.ts +++ b/src/lib/server/services/__tests__/appointment-service.test.ts @@ -47,7 +47,6 @@ const mockAppointment = { authTag: "auth-tag", createdAt: new Date(), updatedAt: new Date(), - expiryDate: null, }; const mockClientTunnel = { diff --git a/src/lib/server/services/__tests__/schedule-service.test.ts b/src/lib/server/services/__tests__/schedule-service.test.ts index c6cec60..210b6af 100644 --- a/src/lib/server/services/__tests__/schedule-service.test.ts +++ b/src/lib/server/services/__tests__/schedule-service.test.ts @@ -431,7 +431,6 @@ describe("ScheduleService", () => { agentId: "agent1", appointmentDate: "2024-01-01T09:00:00.000Z", // 09:00 UTC duration: 60, - expiryDate: "2024-01-01", status: "NEW", }, ]; @@ -508,7 +507,6 @@ describe("ScheduleService", () => { agentId: "agent1", appointmentDate: "2024-01-01T10:00:00.000Z", // 10:00 UTC duration: 60, - expiryDate: "2024-01-01", status: "CONFIRMED", }, { @@ -518,7 +516,6 @@ describe("ScheduleService", () => { agentId: "agent1", appointmentDate: "2024-01-01T14:00:00.000Z", // 14:00 UTC duration: 60, - expiryDate: "2024-01-01", status: "NEW", }, ]; @@ -691,7 +688,6 @@ describe("ScheduleService", () => { agentId: "agent1", appointmentDate: "2024-01-01T09:00:00.000Z", duration: 60, - expiryDate: "2024-01-01", status: "CONFIRMED", }, ]; @@ -796,7 +792,6 @@ describe("ScheduleService", () => { agentId: "agent1", appointmentDate: "2024-01-01T10:00:00.000Z", duration: 60, - expiryDate: "2024-01-01", status: "CONFIRMED", }, ]; From 44011d63663abab13bd4efed031b5f0bee678370 Mon Sep 17 00:00:00 2001 From: Karl Ludwig Weise Date: Thu, 18 Jun 2026 17:28:52 +0200 Subject: [PATCH 3/6] Remove migration --- tenant-migrations/0015_loud_tarot.sql | 1 - tenant-migrations/meta/0015_snapshot.json | 1025 --------------------- tenant-migrations/meta/_journal.json | 9 +- 3 files changed, 1 insertion(+), 1034 deletions(-) delete mode 100644 tenant-migrations/0015_loud_tarot.sql delete mode 100644 tenant-migrations/meta/0015_snapshot.json diff --git a/tenant-migrations/0015_loud_tarot.sql b/tenant-migrations/0015_loud_tarot.sql deleted file mode 100644 index bfc2fc5..0000000 --- a/tenant-migrations/0015_loud_tarot.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE "appointment" DROP COLUMN "expiry_date"; \ No newline at end of file diff --git a/tenant-migrations/meta/0015_snapshot.json b/tenant-migrations/meta/0015_snapshot.json deleted file mode 100644 index f1736d9..0000000 --- a/tenant-migrations/meta/0015_snapshot.json +++ /dev/null @@ -1,1025 +0,0 @@ -{ - "id": "f2dfb72a-1a9d-4c2b-ade6-d7b5414a02ac", - "prevId": "7e551bff-abaf-48b5-bb08-0ba14cb3292d", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.agent": { - "name": "agent", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "descriptions": { - "name": "descriptions", - "type": "json", - "primaryKey": false, - "notNull": true - }, - "image": { - "name": "image", - "type": "varchar(250000)", - "primaryKey": false, - "notNull": false - }, - "archived": { - "name": "archived", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.agent_absence": { - "name": "agent_absence", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "agent_id": { - "name": "agent_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "start_date": { - "name": "start_date", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "end_date": { - "name": "end_date", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "absence_type": { - "name": "absence_type", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "''" - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "agent_absence_agent_id_agent_id_fk": { - "name": "agent_absence_agent_id_agent_id_fk", - "tableFrom": "agent_absence", - "tableTo": "agent", - "columnsFrom": [ - "agent_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.appointment": { - "name": "appointment", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "tunnel_id": { - "name": "tunnel_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "channel_id": { - "name": "channel_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "agent_id": { - "name": "agent_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "appointment_date": { - "name": "appointment_date", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "duration": { - "name": "duration", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "timezone": { - "name": "timezone", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "appointment_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "encrypted_data": { - "name": "encrypted_data", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "data_key": { - "name": "data_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "encrypted_payload": { - "name": "encrypted_payload", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "iv": { - "name": "iv", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "auth_tag": { - "name": "auth_tag", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "appointment_tunnel_id_client_appointment_tunnel_id_fk": { - "name": "appointment_tunnel_id_client_appointment_tunnel_id_fk", - "tableFrom": "appointment", - "tableTo": "client_appointment_tunnel", - "columnsFrom": [ - "tunnel_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "appointment_channel_id_channel_id_fk": { - "name": "appointment_channel_id_channel_id_fk", - "tableFrom": "appointment", - "tableTo": "channel", - "columnsFrom": [ - "channel_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "appointment_agent_id_agent_id_fk": { - "name": "appointment_agent_id_agent_id_fk", - "tableFrom": "appointment", - "tableTo": "agent", - "columnsFrom": [ - "agent_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.appointment_key_share": { - "name": "appointment_key_share", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "appointment_id": { - "name": "appointment_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "encrypted_key": { - "name": "encrypted_key", - "type": "text", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "appointment_key_share_appointment_id_appointment_id_fk": { - "name": "appointment_key_share_appointment_id_appointment_id_fk", - "tableFrom": "appointment_key_share", - "tableTo": "appointment", - "columnsFrom": [ - "appointment_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.auth_challenge": { - "name": "auth_challenge", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "challenge": { - "name": "challenge", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email_hash": { - "name": "email_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "consumed": { - "name": "consumed", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.booking_access_token": { - "name": "booking_access_token", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tenant_id": { - "name": "tenant_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "email_hash": { - "name": "email_hash", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tunnel_id": { - "name": "tunnel_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "client_public_key": { - "name": "client_public_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "consumed": { - "name": "consumed", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.channel": { - "name": "channel", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "names": { - "name": "names", - "type": "json", - "primaryKey": false, - "notNull": true - }, - "color": { - "name": "color", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "paused": { - "name": "paused", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "descriptions": { - "name": "descriptions", - "type": "json", - "primaryKey": false, - "notNull": true - }, - "is_public": { - "name": "is_public", - "type": "boolean", - "primaryKey": false, - "notNull": false - }, - "requires_confirmation": { - "name": "requires_confirmation", - "type": "boolean", - "primaryKey": false, - "notNull": false - }, - "archived": { - "name": "archived", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.channel_agent": { - "name": "channel_agent", - "schema": "", - "columns": { - "channel_id": { - "name": "channel_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "agent_id": { - "name": "agent_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "channel_agent_channel_id_channel_id_fk": { - "name": "channel_agent_channel_id_channel_id_fk", - "tableFrom": "channel_agent", - "tableTo": "channel", - "columnsFrom": [ - "channel_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "channel_agent_agent_id_agent_id_fk": { - "name": "channel_agent_agent_id_agent_id_fk", - "tableFrom": "channel_agent", - "tableTo": "agent", - "columnsFrom": [ - "agent_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.channel_slot_template": { - "name": "channel_slot_template", - "schema": "", - "columns": { - "channel_id": { - "name": "channel_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "slot_template_id": { - "name": "slot_template_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "channel_slot_template_channel_id_channel_id_fk": { - "name": "channel_slot_template_channel_id_channel_id_fk", - "tableFrom": "channel_slot_template", - "tableTo": "channel", - "columnsFrom": [ - "channel_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "channel_slot_template_slot_template_id_slotTemplate_id_fk": { - "name": "channel_slot_template_slot_template_id_slotTemplate_id_fk", - "tableFrom": "channel_slot_template", - "tableTo": "slotTemplate", - "columnsFrom": [ - "slot_template_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.channel_staff": { - "name": "channel_staff", - "schema": "", - "columns": { - "channel_id": { - "name": "channel_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "staff_id": { - "name": "staff_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "channel_staff_channel_id_channel_id_fk": { - "name": "channel_staff_channel_id_channel_id_fk", - "tableFrom": "channel_staff", - "tableTo": "channel", - "columnsFrom": [ - "channel_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.client_appointment_tunnel": { - "name": "client_appointment_tunnel", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "email_hash": { - "name": "email_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "client_public_key": { - "name": "client_public_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "private_key_share": { - "name": "private_key_share", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "client_key_share": { - "name": "client_key_share", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "client_appointment_tunnel_email_hash_unique": { - "name": "client_appointment_tunnel_email_hash_unique", - "nullsNotDistinct": false, - "columns": [ - "email_hash" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.client_pin_reset_token": { - "name": "client_pin_reset_token", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "token": { - "name": "token", - "type": "uuid", - "primaryKey": false, - "notNull": true, - "default": "gen_random_uuid()" - }, - "email_hash": { - "name": "email_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "used": { - "name": "used", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "client_pin_reset_token_token_unique": { - "name": "client_pin_reset_token_token_unique", - "nullsNotDistinct": false, - "columns": [ - "token" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.client_tunnel_staff_key_share": { - "name": "client_tunnel_staff_key_share", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "tunnel_id": { - "name": "tunnel_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "encrypted_tunnel_key": { - "name": "encrypted_tunnel_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "client_tunnel_staff_key_share_tunnel_id_client_appointment_tunnel_id_fk": { - "name": "client_tunnel_staff_key_share_tunnel_id_client_appointment_tunnel_id_fk", - "tableFrom": "client_tunnel_staff_key_share", - "tableTo": "client_appointment_tunnel", - "columnsFrom": [ - "tunnel_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.notification": { - "name": "notification", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "staff_id": { - "name": "staff_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "notification_type", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'APPOINTMENT_CONFIRMED'" - }, - "meta_data": { - "name": "meta_data", - "type": "json", - "primaryKey": false, - "notNull": false - }, - "is_read": { - "name": "is_read", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.slotTemplate": { - "name": "slotTemplate", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "weekdays": { - "name": "weekdays", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "from": { - "name": "from", - "type": "time", - "primaryKey": false, - "notNull": true - }, - "to": { - "name": "to", - "type": "time", - "primaryKey": false, - "notNull": true - }, - "duration": { - "name": "duration", - "type": "integer", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.staff_crypto": { - "name": "staff_crypto", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "public_key": { - "name": "public_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "private_key_share": { - "name": "private_key_share", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "passkey_id": { - "name": "passkey_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "is_active": { - "name": "is_active", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.appointment_status": { - "name": "appointment_status", - "schema": "public", - "values": [ - "NEW", - "CONFIRMED", - "HELD", - "REJECTED", - "NO_SHOW" - ] - }, - "public.notification_type": { - "name": "notification_type", - "schema": "public", - "values": [ - "APPOINTMENT_CONFIRMED", - "APPOINTMENT_CANCELLED", - "APPOINTMENT_REQUESTED" - ] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/tenant-migrations/meta/_journal.json b/tenant-migrations/meta/_journal.json index 241bd64..fa1c97d 100644 --- a/tenant-migrations/meta/_journal.json +++ b/tenant-migrations/meta/_journal.json @@ -106,13 +106,6 @@ "when": 1775654963847, "tag": "0014_fluffy_ezekiel", "breakpoints": true - }, - { - "idx": 15, - "version": "7", - "when": 1777901096310, - "tag": "0015_loud_tarot", - "breakpoints": true } ] -} \ No newline at end of file +} From 23a2d13b6f0578a72c6e5c7fb097cc0ad2bbf45f Mon Sep 17 00:00:00 2001 From: Karl Ludwig Weise Date: Thu, 18 Jun 2026 17:29:45 +0200 Subject: [PATCH 4/6] Added regenerated migrations --- tenant-migrations/0016_mighty_gamma_corps.sql | 1 + tenant-migrations/meta/0016_snapshot.json | 989 ++++++++++++++++++ tenant-migrations/meta/_journal.json | 7 + 3 files changed, 997 insertions(+) create mode 100644 tenant-migrations/0016_mighty_gamma_corps.sql create mode 100644 tenant-migrations/meta/0016_snapshot.json diff --git a/tenant-migrations/0016_mighty_gamma_corps.sql b/tenant-migrations/0016_mighty_gamma_corps.sql new file mode 100644 index 0000000..bfc2fc5 --- /dev/null +++ b/tenant-migrations/0016_mighty_gamma_corps.sql @@ -0,0 +1 @@ +ALTER TABLE "appointment" DROP COLUMN "expiry_date"; \ No newline at end of file diff --git a/tenant-migrations/meta/0016_snapshot.json b/tenant-migrations/meta/0016_snapshot.json new file mode 100644 index 0000000..d8455fd --- /dev/null +++ b/tenant-migrations/meta/0016_snapshot.json @@ -0,0 +1,989 @@ +{ + "id": "65117ae1-b30e-4c15-9db1-bea7b51df27c", + "prevId": "3cdabf94-a461-4a66-a9a2-de9158b45984", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.agent": { + "name": "agent", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "descriptions": { + "name": "descriptions", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "varchar(250000)", + "primaryKey": false, + "notNull": false + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_absence": { + "name": "agent_absence", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "start_date": { + "name": "start_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "end_date": { + "name": "end_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "absence_type": { + "name": "absence_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_absence_agent_id_agent_id_fk": { + "name": "agent_absence_agent_id_agent_id_fk", + "tableFrom": "agent_absence", + "tableTo": "agent", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.appointment": { + "name": "appointment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tunnel_id": { + "name": "tunnel_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "appointment_date": { + "name": "appointment_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "appointment_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "encrypted_data": { + "name": "encrypted_data", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data_key": { + "name": "data_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_payload": { + "name": "encrypted_payload", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "iv": { + "name": "iv", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_tag": { + "name": "auth_tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "appointment_tunnel_id_client_appointment_tunnel_id_fk": { + "name": "appointment_tunnel_id_client_appointment_tunnel_id_fk", + "tableFrom": "appointment", + "tableTo": "client_appointment_tunnel", + "columnsFrom": ["tunnel_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "appointment_channel_id_channel_id_fk": { + "name": "appointment_channel_id_channel_id_fk", + "tableFrom": "appointment", + "tableTo": "channel", + "columnsFrom": ["channel_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "appointment_agent_id_agent_id_fk": { + "name": "appointment_agent_id_agent_id_fk", + "tableFrom": "appointment", + "tableTo": "agent", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.appointment_key_share": { + "name": "appointment_key_share", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "appointment_id": { + "name": "appointment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "appointment_key_share_appointment_id_appointment_id_fk": { + "name": "appointment_key_share_appointment_id_appointment_id_fk", + "tableFrom": "appointment_key_share", + "tableTo": "appointment", + "columnsFrom": ["appointment_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_challenge": { + "name": "auth_challenge", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "challenge": { + "name": "challenge", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_hash": { + "name": "email_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "consumed": { + "name": "consumed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.booking_access_token": { + "name": "booking_access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "email_hash": { + "name": "email_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tunnel_id": { + "name": "tunnel_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "client_public_key": { + "name": "client_public_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "consumed": { + "name": "consumed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.channel": { + "name": "channel", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "names": { + "name": "names", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "paused": { + "name": "paused", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "descriptions": { + "name": "descriptions", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "requires_confirmation": { + "name": "requires_confirmation", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.channel_agent": { + "name": "channel_agent", + "schema": "", + "columns": { + "channel_id": { + "name": "channel_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "channel_agent_channel_id_channel_id_fk": { + "name": "channel_agent_channel_id_channel_id_fk", + "tableFrom": "channel_agent", + "tableTo": "channel", + "columnsFrom": ["channel_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "channel_agent_agent_id_agent_id_fk": { + "name": "channel_agent_agent_id_agent_id_fk", + "tableFrom": "channel_agent", + "tableTo": "agent", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.channel_slot_template": { + "name": "channel_slot_template", + "schema": "", + "columns": { + "channel_id": { + "name": "channel_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "slot_template_id": { + "name": "slot_template_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "channel_slot_template_channel_id_channel_id_fk": { + "name": "channel_slot_template_channel_id_channel_id_fk", + "tableFrom": "channel_slot_template", + "tableTo": "channel", + "columnsFrom": ["channel_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "channel_slot_template_slot_template_id_slotTemplate_id_fk": { + "name": "channel_slot_template_slot_template_id_slotTemplate_id_fk", + "tableFrom": "channel_slot_template", + "tableTo": "slotTemplate", + "columnsFrom": ["slot_template_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.channel_staff": { + "name": "channel_staff", + "schema": "", + "columns": { + "channel_id": { + "name": "channel_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "staff_id": { + "name": "staff_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "channel_staff_channel_id_channel_id_fk": { + "name": "channel_staff_channel_id_channel_id_fk", + "tableFrom": "channel_staff", + "tableTo": "channel", + "columnsFrom": ["channel_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.client_appointment_tunnel": { + "name": "client_appointment_tunnel", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "email_hash": { + "name": "email_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_public_key": { + "name": "client_public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key_share": { + "name": "private_key_share", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_key_share": { + "name": "client_key_share", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "client_appointment_tunnel_email_hash_unique": { + "name": "client_appointment_tunnel_email_hash_unique", + "nullsNotDistinct": false, + "columns": ["email_hash"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.client_pin_reset_token": { + "name": "client_pin_reset_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "token": { + "name": "token", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "email_hash": { + "name": "email_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "used": { + "name": "used", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "client_pin_reset_token_token_unique": { + "name": "client_pin_reset_token_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.client_tunnel_staff_key_share": { + "name": "client_tunnel_staff_key_share", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tunnel_id": { + "name": "tunnel_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "encrypted_tunnel_key": { + "name": "encrypted_tunnel_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "client_tunnel_staff_key_share_tunnel_id_client_appointment_tunnel_id_fk": { + "name": "client_tunnel_staff_key_share_tunnel_id_client_appointment_tunnel_id_fk", + "tableFrom": "client_tunnel_staff_key_share", + "tableTo": "client_appointment_tunnel", + "columnsFrom": ["tunnel_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification": { + "name": "notification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "staff_id": { + "name": "staff_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "notification_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'APPOINTMENT_CONFIRMED'" + }, + "meta_data": { + "name": "meta_data", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "is_read": { + "name": "is_read", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slotTemplate": { + "name": "slotTemplate", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "weekdays": { + "name": "weekdays", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "from": { + "name": "from", + "type": "time", + "primaryKey": false, + "notNull": true + }, + "to": { + "name": "to", + "type": "time", + "primaryKey": false, + "notNull": true + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.staff_crypto": { + "name": "staff_crypto", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key_share": { + "name": "private_key_share", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "passkey_id": { + "name": "passkey_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": { + "staff_crypto_ua_idx": { + "name": "staff_crypto_ua_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.appointment_status": { + "name": "appointment_status", + "schema": "public", + "values": ["NEW", "CONFIRMED", "HELD", "REJECTED", "NO_SHOW"] + }, + "public.notification_type": { + "name": "notification_type", + "schema": "public", + "values": ["APPOINTMENT_CONFIRMED", "APPOINTMENT_CANCELLED", "APPOINTMENT_REQUESTED"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/tenant-migrations/meta/_journal.json b/tenant-migrations/meta/_journal.json index 902fb44..4cc8414 100644 --- a/tenant-migrations/meta/_journal.json +++ b/tenant-migrations/meta/_journal.json @@ -113,6 +113,13 @@ "when": 1778225646617, "tag": "0015_strange_warbird", "breakpoints": true + }, + { + "idx": 16, + "version": "7", + "when": 1781796561280, + "tag": "0016_mighty_gamma_corps", + "breakpoints": true } ] } From 297c0f6bd9ba8442fb2510a147a7ae99faa3add2 Mon Sep 17 00:00:00 2001 From: Karl Ludwig Weise Date: Thu, 18 Jun 2026 17:34:01 +0200 Subject: [PATCH 5/6] Fix lint --- src/lib/server/db/tenant-schema.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/server/db/tenant-schema.ts b/src/lib/server/db/tenant-schema.ts index 7254d71..45336cf 100644 --- a/src/lib/server/db/tenant-schema.ts +++ b/src/lib/server/db/tenant-schema.ts @@ -1,7 +1,6 @@ import type { InferSelectModel } from "drizzle-orm"; import { boolean, - date, integer, json, pgEnum, From ad40be1ea0b93bff69c8e8d460e1c24eec568e58 Mon Sep 17 00:00:00 2001 From: Karl Ludwig Weise Date: Thu, 18 Jun 2026 17:56:02 +0200 Subject: [PATCH 6/6] Use tenant-specific cut-off date --- src/lib/server/services/appointment-service.ts | 11 +++++++---- src/lib/server/services/startup-service.ts | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/lib/server/services/appointment-service.ts b/src/lib/server/services/appointment-service.ts index 0b58372..264c7d1 100644 --- a/src/lib/server/services/appointment-service.ts +++ b/src/lib/server/services/appointment-service.ts @@ -18,8 +18,7 @@ import { NotificationService } from "./notification-service"; import { challengeStore } from "./challenge-store"; import { challengeThrottleService } from "./challenge-throttle"; import { timingSafeEqual } from "node:crypto"; - -const CUTOFF_DAYS = 90; +import { TenantService } from "../db/tenant-service"; export interface ClientTunnelData { tunnelId: string; @@ -80,12 +79,16 @@ export class AppointmentService { } } - async cleanupExpiredAppointments(): Promise { + async cleanupExpiredAppointments(tenantId: string): Promise { const log = logger.setContext("AppointmentService"); const db = await this.getDb(); + const tenant = new TenantService(tenantId); + const config = await tenant.getConfig(); + + const cutOffDays = typeof config.autoDeleteDays === "number" ? config.autoDeleteDays : 90; const cutoffDate = new Date(); - cutoffDate.setDate(cutoffDate.getDate() - CUTOFF_DAYS); + cutoffDate.setDate(cutoffDate.getDate() - cutOffDays); const deletedAppointments = await db .delete(tenantSchema.appointment) diff --git a/src/lib/server/services/startup-service.ts b/src/lib/server/services/startup-service.ts index ec205b3..7903c21 100644 --- a/src/lib/server/services/startup-service.ts +++ b/src/lib/server/services/startup-service.ts @@ -174,7 +174,7 @@ export class StartupService { try { const pinResetService = await ClientPinResetService.forTenant(tenantData.id); const appointmentService = await AppointmentService.forTenant(tenantData.id); - await appointmentService.cleanupExpiredAppointments(); + await appointmentService.cleanupExpiredAppointments(tenantData.id); logger.info("Cleaned up expired appointments for tenant", { tenantId: tenantData.id, shortName: tenantData.shortName,