mirror of
https://github.com/open-reception/appointment-booking-software.git
synced 2026-08-17 21:25:52 +02:00
Merge remote-tracking branch 'origin/main' into 254-bookappointment-once-selected-an-appointment-can-be-booked-multiple-times-duplicate
This commit is contained in:
@@ -107,8 +107,6 @@ export const tenantConfig = pgTable(
|
||||
}),
|
||||
);
|
||||
|
||||
// TODO: Filter in API für bestimmte Rollen
|
||||
|
||||
export const user = pgTable(
|
||||
"user",
|
||||
{
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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"],
|
||||
|
||||
-1
@@ -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"],
|
||||
|
||||
-1
@@ -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";
|
||||
@@ -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": {}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user