Removed expiry_date from appointments. Added housekeeping task to remove old appointments from db.

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Hendrik Belitz
2026-05-04 15:26:10 +02:00
co-authored by Copilot
parent 2a8477cd0b
commit 7942e151c6
14 changed files with 1064 additions and 30 deletions
-2
View File
@@ -107,8 +107,6 @@ export const tenantConfig = pgTable(
}),
);
// TODO: Filter in API für bestimmte Rollen
export const user = pgTable(
"user",
{
-2
View File
@@ -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 */
+22 -2
View File
@@ -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<void> {
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<boolean> {
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,
});
@@ -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,
@@ -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"],
@@ -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"],
@@ -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"],
@@ -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,
@@ -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"],
@@ -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,
@@ -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,
+1
View File
@@ -0,0 +1 @@
ALTER TABLE "appointment" DROP COLUMN "expiry_date";
File diff suppressed because it is too large Load Diff
+8 -1
View File
@@ -106,6 +106,13 @@
"when": 1775654963847,
"tag": "0014_fluffy_ezekiel",
"breakpoints": true
},
{
"idx": 15,
"version": "7",
"when": 1777901096310,
"tag": "0015_loud_tarot",
"breakpoints": true
}
]
}
}