Merge remote-tracking branch 'origin/main' into 254-bookappointment-once-selected-an-appointment-can-be-booked-multiple-times-duplicate

This commit is contained in:
Karl Ludwig Weise
2026-06-18 18:04:40 +02:00
15 changed files with 1042 additions and 47 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",
{
-3
View File
@@ -1,7 +1,6 @@
import type { InferSelectModel } from "drizzle-orm";
import {
boolean,
date,
integer,
json,
pgEnum,
@@ -174,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 */
@@ -47,7 +47,6 @@ const mockAppointment = {
authTag: "auth-tag",
createdAt: new Date(),
updatedAt: new Date(),
expiryDate: null,
};
const mockClientTunnel = {
@@ -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",
},
];
+37 -14
View File
@@ -1,24 +1,25 @@
import { getTenantDb, centralDb } from "../db";
import { ERRORS } from "$lib/errors";
import logger from "$lib/logger";
import type { AppointmentResponse } from "$lib/types/appointment";
import { and, asc, eq, gt, gte, lt, lte, ne, sql } from "drizzle-orm";
import { timingSafeEqual } from "node:crypto";
import { centralDb, getTenantDb } from "../db";
import * as centralSchema from "../db/central-schema";
import * as tenantSchema from "../db/tenant-schema";
import { type SelectAppointment } from "../db/tenant-schema";
import * as centralSchema from "../db/central-schema";
import logger from "$lib/logger";
import { ERRORS } from "$lib/errors";
import { ValidationError, NotFoundError, InternalError, ConflictError } from "../utils/errors";
import { and, asc, eq, gt, gte, lt, lte, ne, sql } from "drizzle-orm";
import type { AppointmentResponse } from "$lib/types/appointment";
import { TenantService } from "../db/tenant-service";
import {
sendAppointmentCreatedEmail,
sendAppointmentRequestEmail,
sendAppointmentCancelledEmail,
getChannelTitle,
sendAppointmentCancelledEmail,
sendAppointmentCreatedEmail,
sendAppointmentRejectedEmail,
sendAppointmentRequestEmail,
} from "../email/email-service";
import { TenantAdminService } from "./tenant-admin-service";
import { NotificationService } from "./notification-service";
import { ConflictError, InternalError, NotFoundError, ValidationError } from "../utils/errors";
import { challengeStore } from "./challenge-store";
import { challengeThrottleService } from "./challenge-throttle";
import { timingSafeEqual } from "node:crypto";
import { NotificationService } from "./notification-service";
import { TenantAdminService } from "./tenant-admin-service";
export interface ClientTunnelData {
tunnelId: string;
@@ -79,6 +80,29 @@ export class AppointmentService {
}
}
async cleanupExpiredAppointments(tenantId: string): Promise<void> {
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() - cutOffDays);
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 });
@@ -811,7 +835,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(tenantData.id);
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"],
@@ -53,11 +53,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,
@@ -0,0 +1 @@
ALTER TABLE "appointment" DROP COLUMN "expiry_date";
+989
View File
@@ -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": {}
}
}
+7
View File
@@ -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
}
]
}