@agent-native/scheduling 0.2.1 → 0.2.2-nightly-20260927013707
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/_helpers.d.ts +0 -13
- package/dist/actions/_helpers.d.ts.map +1 -1
- package/dist/actions/_helpers.js +0 -16
- package/dist/actions/_helpers.js.map +1 -1
- package/dist/actions/create-team.js +0 -1
- package/dist/actions/create-team.js.map +1 -1
- package/dist/actions/list-bookings.js +0 -4
- package/dist/actions/list-bookings.js.map +1 -1
- package/dist/actions/refresh-busy-times.js +0 -1
- package/dist/actions/refresh-busy-times.js.map +1 -1
- package/dist/actions/revoke-private-link.js +0 -2
- package/dist/actions/revoke-private-link.js.map +1 -1
- package/dist/actions/update-profile.js +0 -5
- package/dist/actions/update-profile.js.map +1 -1
- package/dist/core/buffers.d.ts +0 -18
- package/dist/core/buffers.d.ts.map +1 -1
- package/dist/core/buffers.js +0 -6
- package/dist/core/buffers.js.map +1 -1
- package/dist/core/conflicts.d.ts +0 -4
- package/dist/core/conflicts.d.ts.map +1 -1
- package/dist/core/conflicts.js +0 -1
- package/dist/core/conflicts.js.map +1 -1
- package/dist/core/limits.d.ts +0 -8
- package/dist/core/limits.d.ts.map +1 -1
- package/dist/core/limits.js.map +1 -1
- package/dist/core/round-robin.d.ts +0 -17
- package/dist/core/round-robin.d.ts.map +1 -1
- package/dist/core/round-robin.js.map +1 -1
- package/dist/core/rrule.d.ts.map +1 -1
- package/dist/core/rrule.js +0 -7
- package/dist/core/rrule.js.map +1 -1
- package/dist/core/rules.d.ts +0 -17
- package/dist/core/rules.d.ts.map +1 -1
- package/dist/core/rules.js +0 -8
- package/dist/core/rules.js.map +1 -1
- package/dist/core/slots.d.ts +0 -31
- package/dist/core/slots.d.ts.map +1 -1
- package/dist/core/slots.js +0 -9
- package/dist/core/slots.js.map +1 -1
- package/dist/core/time.d.ts +0 -24
- package/dist/core/time.d.ts.map +1 -1
- package/dist/core/time.js +0 -37
- package/dist/core/time.js.map +1 -1
- package/dist/index.d.ts +0 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -10
- package/dist/index.js.map +1 -1
- package/dist/manifest.d.ts +0 -9
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +0 -18
- package/dist/manifest.js.map +1 -1
- package/dist/react/components/SlotPicker.d.ts +0 -8
- package/dist/react/components/SlotPicker.d.ts.map +1 -1
- package/dist/react/components/SlotPicker.js.map +1 -1
- package/dist/react/components/TimezoneSelect.d.ts.map +1 -1
- package/dist/react/components/TimezoneSelect.js +0 -2
- package/dist/react/components/TimezoneSelect.js.map +1 -1
- package/dist/react/components/booking-links/AvailabilityEditor.d.ts +0 -4
- package/dist/react/components/booking-links/AvailabilityEditor.d.ts.map +1 -1
- package/dist/react/components/booking-links/AvailabilityEditor.js +0 -15
- package/dist/react/components/booking-links/AvailabilityEditor.js.map +1 -1
- package/dist/react/components/booking-links/BookingLinkCreateDialog.d.ts +0 -6
- package/dist/react/components/booking-links/BookingLinkCreateDialog.d.ts.map +1 -1
- package/dist/react/components/booking-links/BookingLinkCreateDialog.js +0 -16
- package/dist/react/components/booking-links/BookingLinkCreateDialog.js.map +1 -1
- package/dist/react/components/booking-links/ConferencingSelector.d.ts +0 -2
- package/dist/react/components/booking-links/ConferencingSelector.d.ts.map +1 -1
- package/dist/react/components/booking-links/ConferencingSelector.js +0 -24
- package/dist/react/components/booking-links/ConferencingSelector.js.map +1 -1
- package/dist/react/components/booking-links/CustomFieldsEditor.d.ts +0 -1
- package/dist/react/components/booking-links/CustomFieldsEditor.d.ts.map +1 -1
- package/dist/react/components/booking-links/CustomFieldsEditor.js +0 -14
- package/dist/react/components/booking-links/CustomFieldsEditor.js.map +1 -1
- package/dist/react/components/booking-links/DurationPicker.d.ts +0 -1
- package/dist/react/components/booking-links/DurationPicker.d.ts.map +1 -1
- package/dist/react/components/booking-links/DurationPicker.js +1 -11
- package/dist/react/components/booking-links/DurationPicker.js.map +1 -1
- package/dist/react/components/booking-links/SlugEditor.d.ts +0 -3
- package/dist/react/components/booking-links/SlugEditor.d.ts.map +1 -1
- package/dist/react/components/booking-links/SlugEditor.js +0 -11
- package/dist/react/components/booking-links/SlugEditor.js.map +1 -1
- package/dist/react/components/booking-links/index.d.ts +0 -9
- package/dist/react/components/booking-links/index.d.ts.map +1 -1
- package/dist/react/components/booking-links/index.js +0 -9
- package/dist/react/components/booking-links/index.js.map +1 -1
- package/dist/react/components/index.d.ts +0 -10
- package/dist/react/components/index.d.ts.map +1 -1
- package/dist/react/components/index.js +0 -10
- package/dist/react/components/index.js.map +1 -1
- package/dist/react/hooks/useBookingFlow.d.ts.map +1 -1
- package/dist/react/hooks/useBookingFlow.js +0 -10
- package/dist/react/hooks/useBookingFlow.js.map +1 -1
- package/dist/react/hooks/useEventType.d.ts.map +1 -1
- package/dist/react/hooks/useEventType.js +0 -4
- package/dist/react/hooks/useEventType.js.map +1 -1
- package/dist/react/hooks/useReschedule.d.ts.map +1 -1
- package/dist/react/hooks/useReschedule.js +0 -4
- package/dist/react/hooks/useReschedule.js.map +1 -1
- package/dist/react/hooks/useSlots.d.ts +0 -2
- package/dist/react/hooks/useSlots.d.ts.map +1 -1
- package/dist/react/hooks/useSlots.js +0 -8
- package/dist/react/hooks/useSlots.js.map +1 -1
- package/dist/react/hooks/useTimezone.d.ts.map +1 -1
- package/dist/react/hooks/useTimezone.js +0 -4
- package/dist/react/hooks/useTimezone.js.map +1 -1
- package/dist/react/index.d.ts +0 -5
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +0 -5
- package/dist/react/index.js.map +1 -1
- package/dist/schema/bookings.d.ts +0 -7
- package/dist/schema/bookings.d.ts.map +1 -1
- package/dist/schema/bookings.js +0 -29
- package/dist/schema/bookings.js.map +1 -1
- package/dist/schema/calendar-cache.d.ts.map +1 -1
- package/dist/schema/calendar-cache.js +0 -11
- package/dist/schema/calendar-cache.js.map +1 -1
- package/dist/schema/credentials.d.ts +0 -8
- package/dist/schema/credentials.d.ts.map +1 -1
- package/dist/schema/credentials.js +0 -28
- package/dist/schema/credentials.js.map +1 -1
- package/dist/schema/event-types.d.ts +0 -10
- package/dist/schema/event-types.d.ts.map +1 -1
- package/dist/schema/event-types.js +0 -42
- package/dist/schema/event-types.js.map +1 -1
- package/dist/schema/index.d.ts +0 -11
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +0 -11
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/routing-forms.d.ts.map +1 -1
- package/dist/schema/routing-forms.js +0 -17
- package/dist/schema/routing-forms.js.map +1 -1
- package/dist/schema/schedules.d.ts +0 -12
- package/dist/schema/schedules.d.ts.map +1 -1
- package/dist/schema/schedules.js +0 -25
- package/dist/schema/schedules.js.map +1 -1
- package/dist/schema/teams.d.ts.map +1 -1
- package/dist/schema/teams.js +0 -7
- package/dist/schema/teams.js.map +1 -1
- package/dist/schema/workflows.d.ts +0 -7
- package/dist/schema/workflows.d.ts.map +1 -1
- package/dist/schema/workflows.js +0 -19
- package/dist/schema/workflows.js.map +1 -1
- package/dist/server/availability-engine.d.ts +0 -12
- package/dist/server/availability-engine.d.ts.map +1 -1
- package/dist/server/availability-engine.js +0 -20
- package/dist/server/availability-engine.js.map +1 -1
- package/dist/server/booking-service.d.ts +0 -14
- package/dist/server/booking-service.d.ts.map +1 -1
- package/dist/server/booking-service.js +0 -8
- package/dist/server/booking-service.js.map +1 -1
- package/dist/server/bookings-repo.d.ts +0 -7
- package/dist/server/bookings-repo.d.ts.map +1 -1
- package/dist/server/bookings-repo.js +0 -3
- package/dist/server/bookings-repo.js.map +1 -1
- package/dist/server/context.d.ts +0 -16
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/context.js.map +1 -1
- package/dist/server/db-types.d.ts +0 -11
- package/dist/server/db-types.d.ts.map +1 -1
- package/dist/server/event-types-repo.d.ts +0 -10
- package/dist/server/event-types-repo.d.ts.map +1 -1
- package/dist/server/event-types-repo.js +0 -21
- package/dist/server/event-types-repo.js.map +1 -1
- package/dist/server/hooks.d.ts.map +1 -1
- package/dist/server/hooks.js +0 -11
- package/dist/server/hooks.js.map +1 -1
- package/dist/server/ics.d.ts +0 -7
- package/dist/server/ics.d.ts.map +1 -1
- package/dist/server/ics.js.map +1 -1
- package/dist/server/providers/builtin-video.d.ts +0 -5
- package/dist/server/providers/builtin-video.d.ts.map +1 -1
- package/dist/server/providers/builtin-video.js.map +1 -1
- package/dist/server/providers/google-calendar.d.ts +0 -3
- package/dist/server/providers/google-calendar.d.ts.map +1 -1
- package/dist/server/providers/google-calendar.js +0 -2
- package/dist/server/providers/google-calendar.js.map +1 -1
- package/dist/server/providers/google-meet.d.ts +0 -7
- package/dist/server/providers/google-meet.d.ts.map +1 -1
- package/dist/server/providers/google-meet.js.map +1 -1
- package/dist/server/providers/office365.d.ts +0 -6
- package/dist/server/providers/office365.d.ts.map +1 -1
- package/dist/server/providers/office365.js.map +1 -1
- package/dist/server/providers/registry.d.ts +0 -4
- package/dist/server/providers/registry.d.ts.map +1 -1
- package/dist/server/providers/registry.js.map +1 -1
- package/dist/server/providers/teams.d.ts +0 -6
- package/dist/server/providers/teams.d.ts.map +1 -1
- package/dist/server/providers/teams.js +0 -2
- package/dist/server/providers/teams.js.map +1 -1
- package/dist/server/providers/types.d.ts +0 -27
- package/dist/server/providers/types.d.ts.map +1 -1
- package/dist/server/providers/zoom.d.ts +0 -19
- package/dist/server/providers/zoom.d.ts.map +1 -1
- package/dist/server/providers/zoom.js +0 -2
- package/dist/server/providers/zoom.js.map +1 -1
- package/dist/server/schedules-repo.d.ts.map +1 -1
- package/dist/server/schedules-repo.js +0 -4
- package/dist/server/schedules-repo.js.map +1 -1
- package/dist/server/video-oauth-state.d.ts +0 -5
- package/dist/server/video-oauth-state.d.ts.map +1 -1
- package/dist/server/video-oauth-state.js +1 -9
- package/dist/server/video-oauth-state.js.map +1 -1
- package/dist/server/video-oauth.d.ts +0 -4
- package/dist/server/video-oauth.d.ts.map +1 -1
- package/dist/server/video-oauth.js +0 -23
- package/dist/server/video-oauth.js.map +1 -1
- package/dist/shared/index.d.ts +0 -37
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +0 -4
- package/dist/shared/index.js.map +1 -1
- package/package.json +4 -4
- package/src/actions/_helpers.ts +0 -16
- package/src/actions/booking-authz.spec.ts +0 -6
- package/src/actions/create-team.ts +0 -1
- package/src/actions/event-type-authz.spec.ts +0 -11
- package/src/actions/list-bookings.ts +0 -4
- package/src/actions/refresh-busy-times.ts +0 -1
- package/src/actions/revoke-private-link.ts +0 -2
- package/src/actions/update-profile.ts +0 -5
- package/src/core/buffers.ts +0 -18
- package/src/core/conflicts.ts +0 -4
- package/src/core/limits.ts +0 -8
- package/src/core/round-robin.test.ts +0 -1
- package/src/core/round-robin.ts +0 -17
- package/src/core/rrule.ts +0 -7
- package/src/core/rules.ts +0 -17
- package/src/core/slots.test.ts +1 -11
- package/src/core/slots.ts +0 -40
- package/src/core/time.ts +0 -37
- package/src/index.ts +0 -11
- package/src/manifest.ts +0 -19
- package/src/react/components/SlotPicker.tsx +0 -8
- package/src/react/components/TimezoneSelect.tsx +0 -2
- package/src/react/components/_shadcn-shims.d.ts +0 -35
- package/src/react/components/booking-links/AvailabilityEditor.tsx +0 -15
- package/src/react/components/booking-links/BookingLinkCreateDialog.tsx +0 -22
- package/src/react/components/booking-links/ConferencingSelector.tsx +0 -26
- package/src/react/components/booking-links/CustomFieldsEditor.tsx +0 -15
- package/src/react/components/booking-links/DurationPicker.tsx +1 -12
- package/src/react/components/booking-links/SlugEditor.tsx +0 -14
- package/src/react/components/booking-links/index.ts +0 -9
- package/src/react/components/index.ts +0 -10
- package/src/react/hooks/useBookingFlow.ts +0 -10
- package/src/react/hooks/useEventType.ts +0 -4
- package/src/react/hooks/useReschedule.ts +0 -4
- package/src/react/hooks/useSlots.ts +0 -10
- package/src/react/hooks/useTimezone.ts +0 -4
- package/src/react/index.ts +0 -5
- package/src/schema/bookings.ts +0 -29
- package/src/schema/calendar-cache.ts +0 -11
- package/src/schema/credentials.ts +0 -28
- package/src/schema/event-types.ts +0 -42
- package/src/schema/index.ts +0 -11
- package/src/schema/routing-forms.ts +0 -17
- package/src/schema/schedules.ts +0 -25
- package/src/schema/teams.ts +0 -7
- package/src/schema/workflows.ts +0 -19
- package/src/server/availability-engine.test.ts +0 -1
- package/src/server/availability-engine.ts +0 -22
- package/src/server/booking-service.spec.ts +0 -15
- package/src/server/booking-service.ts +0 -22
- package/src/server/bookings-repo.ts +0 -10
- package/src/server/context.ts +0 -16
- package/src/server/db-types.ts +0 -11
- package/src/server/event-types-repo.ts +0 -31
- package/src/server/hooks.ts +0 -11
- package/src/server/ics.ts +0 -7
- package/src/server/providers/builtin-video.ts +0 -5
- package/src/server/providers/google-calendar.ts +0 -5
- package/src/server/providers/google-meet.ts +0 -7
- package/src/server/providers/office365.ts +0 -6
- package/src/server/providers/registry.ts +0 -4
- package/src/server/providers/teams.ts +0 -8
- package/src/server/providers/types.ts +0 -27
- package/src/server/providers/zoom.ts +0 -21
- package/src/server/schedules-repo.ts +0 -4
- package/src/server/video-oauth-state.ts +1 -10
- package/src/server/video-oauth.ts +0 -23
- package/src/shared/index.ts +0 -38
|
@@ -1,51 +1,27 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Credentials — thin view over framework `oauth_tokens` that records which
|
|
3
|
-
* calendar / video integrations a user has connected. The actual OAuth
|
|
4
|
-
* tokens live in core's `oauth_tokens` table; this table stores display
|
|
5
|
-
* metadata and flags needed by the scheduling UI (display name, invalid
|
|
6
|
-
* flag, app icon, default-for-type).
|
|
7
|
-
*/
|
|
8
1
|
import { table, text } from "@agent-native/core/db/schema";
|
|
9
2
|
import { boolean } from "drizzle-orm/pg-core";
|
|
10
3
|
|
|
11
4
|
export const schedulingCredentials = table("scheduling_credentials", {
|
|
12
5
|
id: text("id").primaryKey(),
|
|
13
|
-
/** Provider slug: "google_calendar", "office365_calendar", "zoom_video", "daily_video", … */
|
|
14
6
|
type: text("type").notNull(),
|
|
15
|
-
/** Owner (user) — for team credentials, use teamId too */
|
|
16
7
|
userEmail: text("user_email"),
|
|
17
8
|
teamId: text("team_id"),
|
|
18
|
-
/** App id from framework app store */
|
|
19
9
|
appId: text("app_id"),
|
|
20
|
-
/** Core `oauth_tokens.id` if applicable */
|
|
21
10
|
oauthTokenId: text("oauth_token_id"),
|
|
22
|
-
/** Human-friendly label shown in UI */
|
|
23
11
|
displayName: text("display_name"),
|
|
24
|
-
/** External account email (e.g. Google account email) */
|
|
25
12
|
externalEmail: text("external_email"),
|
|
26
|
-
/** True after a 401/403 — UI prompts re-connect */
|
|
27
13
|
invalid: boolean("invalid").notNull().default(false),
|
|
28
|
-
/** Whether this is the user's default for its category (e.g. default video) */
|
|
29
14
|
isDefault: boolean("is_default").notNull().default(false),
|
|
30
15
|
createdAt: text("created_at").notNull(),
|
|
31
16
|
updatedAt: text("updated_at").notNull(),
|
|
32
17
|
});
|
|
33
18
|
|
|
34
|
-
/**
|
|
35
|
-
* Which external calendars a credential reads "busy" times from.
|
|
36
|
-
* A single credential (e.g. a Google account) may expose multiple calendars
|
|
37
|
-
* (primary, vacation, shared). Users pick a subset to check for conflicts,
|
|
38
|
-
* plus exactly one as the destination for new events.
|
|
39
|
-
*/
|
|
40
19
|
export const selectedCalendars = table("selected_calendars", {
|
|
41
20
|
id: text("id").primaryKey(),
|
|
42
21
|
credentialId: text("credential_id").notNull(),
|
|
43
22
|
userEmail: text("user_email").notNull(),
|
|
44
|
-
/** External calendar id from the provider */
|
|
45
23
|
externalId: text("external_id").notNull(),
|
|
46
|
-
/** Provider name for this entry */
|
|
47
24
|
integration: text("integration").notNull(),
|
|
48
|
-
/** Optional event-type-level override (only check this calendar for that event) */
|
|
49
25
|
eventTypeId: text("event_type_id"),
|
|
50
26
|
createdAt: text("created_at").notNull(),
|
|
51
27
|
});
|
|
@@ -54,16 +30,13 @@ export const destinationCalendars = table("destination_calendars", {
|
|
|
54
30
|
id: text("id").primaryKey(),
|
|
55
31
|
credentialId: text("credential_id").notNull(),
|
|
56
32
|
userEmail: text("user_email").notNull(),
|
|
57
|
-
/** Provider name */
|
|
58
33
|
integration: text("integration").notNull(),
|
|
59
34
|
externalId: text("external_id").notNull(),
|
|
60
35
|
primaryEmail: text("primary_email"),
|
|
61
|
-
/** Optional event-type-level override */
|
|
62
36
|
eventTypeId: text("event_type_id"),
|
|
63
37
|
createdAt: text("created_at").notNull(),
|
|
64
38
|
});
|
|
65
39
|
|
|
66
|
-
/** Verified sender emails for workflows sending from a custom address. */
|
|
67
40
|
export const verifiedEmails = table("verified_emails", {
|
|
68
41
|
id: text("id").primaryKey(),
|
|
69
42
|
email: text("email").notNull(),
|
|
@@ -72,7 +45,6 @@ export const verifiedEmails = table("verified_emails", {
|
|
|
72
45
|
verifiedAt: text("verified_at").notNull(),
|
|
73
46
|
});
|
|
74
47
|
|
|
75
|
-
/** Verified phone numbers for workflows sending SMS. */
|
|
76
48
|
export const verifiedNumbers = table("verified_numbers", {
|
|
77
49
|
id: text("id").primaryKey(),
|
|
78
50
|
phoneNumber: text("phone_number").notNull(),
|
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Event types — the top-level bookable definition.
|
|
3
|
-
*
|
|
4
|
-
* An EventType represents "a 30-minute intro call", "a team round-robin sales
|
|
5
|
-
* demo", or "a managed internal 1:1". It is the primary resource users create,
|
|
6
|
-
* edit, and share booking links for.
|
|
7
|
-
*
|
|
8
|
-
* Ownership: personal event types are owned by a user (ownerEmail);
|
|
9
|
-
* team event types are owned by a team (teamId). Sharing is via the framework
|
|
10
|
-
* sharing system (`event_type_shares`).
|
|
11
|
-
*/
|
|
12
1
|
import {
|
|
13
2
|
table,
|
|
14
3
|
text,
|
|
@@ -21,44 +10,33 @@ import { boolean } from "drizzle-orm/pg-core";
|
|
|
21
10
|
export const eventTypes = table("event_types", {
|
|
22
11
|
id: text("id").primaryKey(),
|
|
23
12
|
|
|
24
|
-
// Identity
|
|
25
13
|
title: text("title").notNull(),
|
|
26
14
|
slug: text("slug").notNull(),
|
|
27
15
|
description: text("description"),
|
|
28
|
-
/** Default duration in minutes */
|
|
29
16
|
length: integer("length").notNull().default(30),
|
|
30
|
-
/** JSON array of additional duration choices, e.g. [15, 30, 60] */
|
|
31
17
|
durations: text("durations"),
|
|
32
18
|
position: integer("position").notNull().default(0),
|
|
33
19
|
hidden: boolean("hidden").notNull().default(false),
|
|
34
20
|
color: text("color"),
|
|
35
21
|
|
|
36
|
-
// Scheduling model
|
|
37
22
|
schedulingType: text("scheduling_type", {
|
|
38
23
|
enum: ["personal", "collective", "round-robin", "managed"],
|
|
39
24
|
})
|
|
40
25
|
.notNull()
|
|
41
26
|
.default("personal"),
|
|
42
|
-
/** Team id if this is a team event type; null for personal */
|
|
43
27
|
teamId: text("team_id"),
|
|
44
28
|
|
|
45
|
-
// Locations (JSON array of Location objects)
|
|
46
29
|
locations: text("locations"),
|
|
47
30
|
|
|
48
|
-
// Custom fields (JSON array of CustomField objects)
|
|
49
31
|
customFields: text("custom_fields"),
|
|
50
32
|
|
|
51
|
-
// Default schedule reference
|
|
52
33
|
scheduleId: text("schedule_id"),
|
|
53
34
|
|
|
54
|
-
// Buffers & notice
|
|
55
35
|
minimumBookingNotice: integer("minimum_booking_notice").notNull().default(0),
|
|
56
36
|
beforeEventBuffer: integer("before_event_buffer").notNull().default(0),
|
|
57
37
|
afterEventBuffer: integer("after_event_buffer").notNull().default(0),
|
|
58
|
-
/** Slot granularity in minutes; null = use duration */
|
|
59
38
|
slotInterval: integer("slot_interval"),
|
|
60
39
|
|
|
61
|
-
// Booking window
|
|
62
40
|
periodType: text("period_type", {
|
|
63
41
|
enum: ["unlimited", "rolling", "range"],
|
|
64
42
|
})
|
|
@@ -68,7 +46,6 @@ export const eventTypes = table("event_types", {
|
|
|
68
46
|
periodStartDate: text("period_start_date"),
|
|
69
47
|
periodEndDate: text("period_end_date"),
|
|
70
48
|
|
|
71
|
-
// Capacity & policies
|
|
72
49
|
seatsPerTimeSlot: integer("seats_per_time_slot"),
|
|
73
50
|
requiresConfirmation: boolean("requires_confirmation")
|
|
74
51
|
.notNull()
|
|
@@ -77,16 +54,12 @@ export const eventTypes = table("event_types", {
|
|
|
77
54
|
hideCalendarNotes: boolean("hide_calendar_notes").notNull().default(false),
|
|
78
55
|
successRedirectUrl: text("success_redirect_url"),
|
|
79
56
|
bookingLimits: text("booking_limits"),
|
|
80
|
-
/** Lock timezone on Booker (for in-person or local-only events) */
|
|
81
57
|
lockTimeZoneToggle: boolean("lock_time_zone_toggle").notNull().default(false),
|
|
82
58
|
|
|
83
|
-
// Recurring (JSON RecurringEventRule) — Tier 2
|
|
84
59
|
recurringEvent: text("recurring_event"),
|
|
85
60
|
|
|
86
|
-
// Templated event name: "{name} + {host}"
|
|
87
61
|
eventName: text("event_name"),
|
|
88
62
|
|
|
89
|
-
// Misc
|
|
90
63
|
metadata: text("metadata"),
|
|
91
64
|
createdAt: text("created_at").notNull(),
|
|
92
65
|
updatedAt: text("updated_at").notNull(),
|
|
@@ -94,26 +67,17 @@ export const eventTypes = table("event_types", {
|
|
|
94
67
|
...ownableColumns(),
|
|
95
68
|
});
|
|
96
69
|
|
|
97
|
-
/**
|
|
98
|
-
* Host assignments for team / round-robin / collective event types.
|
|
99
|
-
* Composite key (userEmail, eventTypeId).
|
|
100
|
-
*/
|
|
101
70
|
export const eventTypeHosts = table("event_type_hosts", {
|
|
102
71
|
eventTypeId: text("event_type_id").notNull(),
|
|
103
72
|
userEmail: text("user_email").notNull(),
|
|
104
|
-
/** Fixed hosts always attend; non-fixed hosts rotate in round-robin */
|
|
105
73
|
isFixed: boolean("is_fixed").notNull().default(false),
|
|
106
|
-
/** Relative weight for weighted round-robin (default 1) */
|
|
107
74
|
weight: integer("weight").notNull().default(1),
|
|
108
|
-
/** Lower number = higher priority in round-robin tiebreak */
|
|
109
75
|
priority: integer("priority").notNull().default(2),
|
|
110
|
-
/** Host-specific schedule override; null = use host's default */
|
|
111
76
|
scheduleId: text("schedule_id"),
|
|
112
77
|
groupId: text("group_id"),
|
|
113
78
|
createdAt: text("created_at").notNull(),
|
|
114
79
|
});
|
|
115
80
|
|
|
116
|
-
/** Host groups — collective assignment within a round-robin group. */
|
|
117
81
|
export const eventTypeHostGroups = table("event_type_host_groups", {
|
|
118
82
|
id: text("id").primaryKey(),
|
|
119
83
|
eventTypeId: text("event_type_id").notNull(),
|
|
@@ -121,19 +85,13 @@ export const eventTypeHostGroups = table("event_type_host_groups", {
|
|
|
121
85
|
createdAt: text("created_at").notNull(),
|
|
122
86
|
});
|
|
123
87
|
|
|
124
|
-
/**
|
|
125
|
-
* Rename history — e.g. `/me/old-slug` → `/me/new-slug`.
|
|
126
|
-
* Public booker redirects the old slug to the new one.
|
|
127
|
-
*/
|
|
128
88
|
export const eventTypeSlugRedirects = table("event_type_slug_redirects", {
|
|
129
|
-
/** Composite: "ownerEmail:slug" or "team:teamId:slug" — unique across ownership scope */
|
|
130
89
|
oldKey: text("old_key").primaryKey(),
|
|
131
90
|
newKey: text("new_key").notNull(),
|
|
132
91
|
eventTypeId: text("event_type_id"),
|
|
133
92
|
createdAt: text("created_at").notNull(),
|
|
134
93
|
});
|
|
135
94
|
|
|
136
|
-
/** Hashed private links (`/d/:hash/:slug`) — shareable tokens that don't expose the owner's username. */
|
|
137
95
|
export const hashedLinks = table("hashed_links", {
|
|
138
96
|
id: text("id").primaryKey(),
|
|
139
97
|
hash: text("hash").notNull().unique(),
|
package/src/schema/index.ts
CHANGED
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Barrel export for all scheduling schema tables.
|
|
3
|
-
*
|
|
4
|
-
* Consumer apps compose this with their own schema:
|
|
5
|
-
*
|
|
6
|
-
* // server/db/schema.ts
|
|
7
|
-
* export * from "@agent-native/scheduling/schema";
|
|
8
|
-
* export * from "./local-tables.js";
|
|
9
|
-
*
|
|
10
|
-
* Drizzle's schema scanner picks up all tables from the combined namespace.
|
|
11
|
-
*/
|
|
12
1
|
export * from "./event-types.js";
|
|
13
2
|
export * from "./schedules.js";
|
|
14
3
|
export * from "./bookings.js";
|
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Routing forms — ChiliPiper-style pre-booking forms that route prospects to
|
|
3
|
-
* the right event type based on their answers.
|
|
4
|
-
*
|
|
5
|
-
* A form has fields (text/select/multi), rules (match conditions → action),
|
|
6
|
-
* and a fallback. Public form URL is `/forms/:formId`; on submit we evaluate
|
|
7
|
-
* rules in order and either redirect to an event-type booker, open an
|
|
8
|
-
* external URL, or show a custom message.
|
|
9
|
-
*/
|
|
10
1
|
import {
|
|
11
2
|
table,
|
|
12
3
|
text,
|
|
@@ -21,11 +12,8 @@ export const routingForms = table("routing_forms", {
|
|
|
21
12
|
description: text("description"),
|
|
22
13
|
teamId: text("team_id"),
|
|
23
14
|
disabled: boolean("disabled").notNull().default(false),
|
|
24
|
-
/** JSON array of RoutingFormField */
|
|
25
15
|
fields: text("fields").notNull().default("[]"),
|
|
26
|
-
/** JSON array of RoutingFormRule */
|
|
27
16
|
rules: text("rules").notNull().default("[]"),
|
|
28
|
-
/** JSON RoutingFormRule action object for when no rules match */
|
|
29
17
|
fallback: text("fallback"),
|
|
30
18
|
createdAt: text("created_at").notNull(),
|
|
31
19
|
updatedAt: text("updated_at").notNull(),
|
|
@@ -35,15 +23,10 @@ export const routingForms = table("routing_forms", {
|
|
|
35
23
|
export const routingFormResponses = table("routing_form_responses", {
|
|
36
24
|
id: text("id").primaryKey(),
|
|
37
25
|
formId: text("form_id").notNull(),
|
|
38
|
-
/** JSON object of fieldId → value */
|
|
39
26
|
response: text("response").notNull(),
|
|
40
|
-
/** Booking id if the response resulted in a booking */
|
|
41
27
|
bookingId: text("booking_id"),
|
|
42
|
-
/** Which rule matched (null if fallback) */
|
|
43
28
|
matchedRuleId: text("matched_rule_id"),
|
|
44
|
-
/** Resulting action description for quick filtering */
|
|
45
29
|
routedTo: text("routed_to"),
|
|
46
|
-
/** Submitter context */
|
|
47
30
|
submitterEmail: text("submitter_email"),
|
|
48
31
|
submitterIp: text("submitter_ip"),
|
|
49
32
|
createdAt: text("created_at").notNull(),
|
package/src/schema/schedules.ts
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Schedules — named availability presets ("Working Hours", "Evenings").
|
|
3
|
-
*
|
|
4
|
-
* A Schedule has weekly rules (e.g. Mon-Fri 9am-5pm) stored as rows in
|
|
5
|
-
* `schedule_availability`, plus one-off overrides stored in `date_overrides`.
|
|
6
|
-
*
|
|
7
|
-
* Each user has one default schedule; event types can pick any of the user's
|
|
8
|
-
* schedules, or a host-specific override for team events.
|
|
9
|
-
*/
|
|
10
1
|
import {
|
|
11
2
|
table,
|
|
12
3
|
text,
|
|
@@ -26,38 +17,23 @@ export const schedules = table("schedules", {
|
|
|
26
17
|
...ownableColumns(),
|
|
27
18
|
});
|
|
28
19
|
|
|
29
|
-
/**
|
|
30
|
-
* Weekly availability rules.
|
|
31
|
-
* Each row is one interval on one day-of-week for one schedule.
|
|
32
|
-
* Example: scheduleId=X, day=1 (Mon), startTime="09:00", endTime="12:00".
|
|
33
|
-
*/
|
|
34
20
|
export const scheduleAvailability = table("schedule_availability", {
|
|
35
21
|
id: text("id").primaryKey(),
|
|
36
22
|
scheduleId: text("schedule_id").notNull(),
|
|
37
|
-
/** 0=Sunday … 6=Saturday */
|
|
38
23
|
day: integer("day").notNull(),
|
|
39
|
-
/** "HH:MM" in schedule's timezone */
|
|
40
24
|
startTime: text("start_time").notNull(),
|
|
41
25
|
endTime: text("end_time").notNull(),
|
|
42
26
|
createdAt: text("created_at").notNull(),
|
|
43
27
|
});
|
|
44
28
|
|
|
45
|
-
/**
|
|
46
|
-
* Date-specific overrides.
|
|
47
|
-
* Example: scheduleId=X, date="2026-04-25", intervals=[] (fully blocked).
|
|
48
|
-
* Or intervals=[{"09:00"-"11:00"}] (only 9-11 on that day).
|
|
49
|
-
*/
|
|
50
29
|
export const dateOverrides = table("date_overrides", {
|
|
51
30
|
id: text("id").primaryKey(),
|
|
52
31
|
scheduleId: text("schedule_id").notNull(),
|
|
53
|
-
/** YYYY-MM-DD in schedule's timezone */
|
|
54
32
|
date: text("date").notNull(),
|
|
55
|
-
/** JSON array of {startTime,endTime}. Empty = fully blocked. */
|
|
56
33
|
intervals: text("intervals").notNull().default("[]"),
|
|
57
34
|
createdAt: text("created_at").notNull(),
|
|
58
35
|
});
|
|
59
36
|
|
|
60
|
-
/** Time-zoned travel windows that override the user's default timezone. */
|
|
61
37
|
export const travelSchedules = table("travel_schedules", {
|
|
62
38
|
id: text("id").primaryKey(),
|
|
63
39
|
userEmail: text("user_email").notNull(),
|
|
@@ -67,7 +43,6 @@ export const travelSchedules = table("travel_schedules", {
|
|
|
67
43
|
createdAt: text("created_at").notNull(),
|
|
68
44
|
});
|
|
69
45
|
|
|
70
|
-
/** Out of office — blocks bookings and optionally redirects to a coworker. */
|
|
71
46
|
export const outOfOfficeEntries = table("out_of_office_entries", {
|
|
72
47
|
id: text("id").primaryKey(),
|
|
73
48
|
userEmail: text("user_email").notNull(),
|
package/src/schema/teams.ts
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Teams — groupings of users who share event types, round-robin across hosts,
|
|
3
|
-
* and present a team booking page at `/team/:slug`.
|
|
4
|
-
*
|
|
5
|
-
* Framework `org` provides heavier identity (for full multi-tenant orgs);
|
|
6
|
-
* teams here are lighter-weight and may be scoped to a single org.
|
|
7
|
-
*/
|
|
8
1
|
import {
|
|
9
2
|
table,
|
|
10
3
|
text,
|
package/src/schema/workflows.ts
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Workflows — trigger-based automations ("email attendee 1h before event").
|
|
3
|
-
*
|
|
4
|
-
* Each workflow has a trigger + ordered steps. Steps run on a schedule via
|
|
5
|
-
* the framework's recurring-jobs mechanism: when a booking fires a trigger,
|
|
6
|
-
* we materialize scheduled reminders; a job processes them when due.
|
|
7
|
-
*/
|
|
8
1
|
import {
|
|
9
2
|
table,
|
|
10
3
|
text,
|
|
@@ -29,7 +22,6 @@ export const workflows = table("workflows", {
|
|
|
29
22
|
}).notNull(),
|
|
30
23
|
teamId: text("team_id"),
|
|
31
24
|
disabled: boolean("disabled").notNull().default(false),
|
|
32
|
-
/** JSON array of eventTypeIds this workflow runs on. Empty = none. */
|
|
33
25
|
activeOnEventTypeIds: text("active_on_event_type_ids")
|
|
34
26
|
.notNull()
|
|
35
27
|
.default("[]"),
|
|
@@ -53,24 +45,16 @@ export const workflowSteps = table("workflow_steps", {
|
|
|
53
45
|
"webhook",
|
|
54
46
|
],
|
|
55
47
|
}).notNull(),
|
|
56
|
-
/** Offset in minutes from the trigger. Negative = before (for before-event). */
|
|
57
48
|
offsetMinutes: integer("offset_minutes").notNull().default(0),
|
|
58
|
-
/** Target email / phone / url depending on action */
|
|
59
49
|
sendTo: text("send_to"),
|
|
60
50
|
emailSubject: text("email_subject"),
|
|
61
51
|
emailBody: text("email_body"),
|
|
62
52
|
smsBody: text("sms_body"),
|
|
63
53
|
webhookUrl: text("webhook_url"),
|
|
64
|
-
/** Named template key (e.g. "default-reminder") */
|
|
65
54
|
template: text("template"),
|
|
66
55
|
createdAt: text("created_at").notNull(),
|
|
67
56
|
});
|
|
68
57
|
|
|
69
|
-
/**
|
|
70
|
-
* Materialized reminders waiting to fire.
|
|
71
|
-
* Written by the booking-service hook when a trigger occurs.
|
|
72
|
-
* Drained by a recurring job that processes due rows.
|
|
73
|
-
*/
|
|
74
58
|
export const scheduledReminders = table("scheduled_reminders", {
|
|
75
59
|
id: text("id").primaryKey(),
|
|
76
60
|
bookingId: text("booking_id").notNull(),
|
|
@@ -87,14 +71,12 @@ export const scheduledReminders = table("scheduled_reminders", {
|
|
|
87
71
|
createdAt: text("created_at").notNull(),
|
|
88
72
|
});
|
|
89
73
|
|
|
90
|
-
/** Outgoing webhook subscriptions. */
|
|
91
74
|
export const webhooks = table("webhooks", {
|
|
92
75
|
id: text("id").primaryKey(),
|
|
93
76
|
name: text("name"),
|
|
94
77
|
subscriberUrl: text("subscriber_url").notNull(),
|
|
95
78
|
secret: text("secret"),
|
|
96
79
|
active: boolean("active").notNull().default(true),
|
|
97
|
-
/** JSON array of trigger keys */
|
|
98
80
|
eventTriggers: text("event_triggers").notNull().default("[]"),
|
|
99
81
|
teamId: text("team_id"),
|
|
100
82
|
eventTypeId: text("event_type_id"),
|
|
@@ -114,7 +96,6 @@ export const webhookDeliveries = table("webhook_deliveries", {
|
|
|
114
96
|
attempts: integer("attempts").notNull().default(0),
|
|
115
97
|
});
|
|
116
98
|
|
|
117
|
-
/** API keys for programmatic access. */
|
|
118
99
|
export const apiKeys = table("api_keys", {
|
|
119
100
|
id: text("id").primaryKey(),
|
|
120
101
|
hashedKey: text("hashed_key").notNull().unique(),
|
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Availability engine — combines an event type, schedule, and calendar busy
|
|
3
|
-
* intervals into the slot list shown to the Booker.
|
|
4
|
-
*
|
|
5
|
-
* This is where the pure `computeAvailableSlots` gets plugged into the real
|
|
6
|
-
* world: it fetches the schedule, merges busy times from the selected
|
|
7
|
-
* calendars + existing bookings, and applies booking limits.
|
|
8
|
-
*/
|
|
9
1
|
import { eq, gte, lt, and, inArray } from "drizzle-orm";
|
|
10
2
|
|
|
11
3
|
import { expandSlotForConflictCheck } from "../core/buffers.js";
|
|
@@ -39,16 +31,9 @@ export interface AssertSlotAvailableInput {
|
|
|
39
31
|
endTime: string;
|
|
40
32
|
beforeEventBuffer?: number;
|
|
41
33
|
afterEventBuffer?: number;
|
|
42
|
-
/** Booking uid to ignore when checking conflicts (e.g. the one being rescheduled). */
|
|
43
34
|
excludeBookingUid?: string;
|
|
44
35
|
}
|
|
45
36
|
|
|
46
|
-
/**
|
|
47
|
-
* Re-validate a single requested interval right before it's written —
|
|
48
|
-
* cheap by design: aggregates busy only over the buffer-expanded window
|
|
49
|
-
* instead of scanning a full day like `getAvailableSlots`. Throws
|
|
50
|
-
* `SlotConflictError` if the window collides with existing busy time.
|
|
51
|
-
*/
|
|
52
37
|
export async function assertSlotAvailable(
|
|
53
38
|
input: AssertSlotAvailableInput,
|
|
54
39
|
): Promise<void> {
|
|
@@ -73,7 +58,6 @@ export async function assertSlotAvailable(
|
|
|
73
58
|
|
|
74
59
|
export interface GetSlotsInput {
|
|
75
60
|
eventType: EventType;
|
|
76
|
-
/** The user we're finding availability for — their schedule + calendars. */
|
|
77
61
|
forUserEmail: string;
|
|
78
62
|
rangeStart: Date;
|
|
79
63
|
rangeEnd: Date;
|
|
@@ -141,10 +125,6 @@ async function resolveDefaultSchedule(
|
|
|
141
125
|
return getScheduleById(rows[0].id);
|
|
142
126
|
}
|
|
143
127
|
|
|
144
|
-
/**
|
|
145
|
-
* Merge busy intervals from: (a) existing bookings for the user, (b) selected
|
|
146
|
-
* external calendars (via providers), (c) the calendar cache.
|
|
147
|
-
*/
|
|
148
128
|
export async function aggregateBusy(input: {
|
|
149
129
|
userEmail: string;
|
|
150
130
|
rangeStart: Date;
|
|
@@ -154,7 +134,6 @@ export async function aggregateBusy(input: {
|
|
|
154
134
|
const db = getDb();
|
|
155
135
|
const busy: BusyInterval[] = [];
|
|
156
136
|
|
|
157
|
-
// Existing bookings where this user is the host
|
|
158
137
|
const bookings = await db
|
|
159
138
|
.select()
|
|
160
139
|
.from(schema.bookings)
|
|
@@ -174,7 +153,6 @@ export async function aggregateBusy(input: {
|
|
|
174
153
|
});
|
|
175
154
|
}
|
|
176
155
|
|
|
177
|
-
// External calendars via registered providers
|
|
178
156
|
const creds = await db
|
|
179
157
|
.select()
|
|
180
158
|
.from(schema.schedulingCredentials)
|
|
@@ -305,7 +305,6 @@ afterEach(async () => {
|
|
|
305
305
|
rmSync(dbDir, { recursive: true, force: true });
|
|
306
306
|
});
|
|
307
307
|
|
|
308
|
-
/** `rescheduleBooking` re-loads the event type by id, so it must exist. */
|
|
309
308
|
async function seedEventType(eventType: EventType): Promise<void> {
|
|
310
309
|
await execute({
|
|
311
310
|
sql: `INSERT INTO event_types (
|
|
@@ -405,9 +404,6 @@ describe("insertBooking", () => {
|
|
|
405
404
|
timezone: "UTC",
|
|
406
405
|
attendees: [
|
|
407
406
|
{ email: ATTENDEE_EMAIL, name: "Attendee One" },
|
|
408
|
-
// A null email violates the NOT NULL constraint after the first
|
|
409
|
-
// attendee (and the booking row) have already been written —
|
|
410
|
-
// proving the whole write is one atomic unit.
|
|
411
407
|
{ email: null as unknown as string, name: "Bad Attendee" },
|
|
412
408
|
],
|
|
413
409
|
ownerEmail: HOST_EMAIL,
|
|
@@ -499,8 +495,6 @@ describe("createBooking", () => {
|
|
|
499
495
|
attendee: { email: ATTENDEE_EMAIL, name: "Attendee One" },
|
|
500
496
|
});
|
|
501
497
|
|
|
502
|
-
// Starts exactly when the first booking ends, so the raw windows don't
|
|
503
|
-
// overlap — only the buffered event type's 15-minute lead-in collides.
|
|
504
498
|
const bufferedEventType = makeEventType({
|
|
505
499
|
id: "event-type-buffered",
|
|
506
500
|
beforeEventBuffer: 15,
|
|
@@ -518,9 +512,6 @@ describe("createBooking", () => {
|
|
|
518
512
|
});
|
|
519
513
|
|
|
520
514
|
it("allows an out-of-availability free slot once the conflicting booking is cancelled", async () => {
|
|
521
|
-
// Sanity check that the conflict guard is scoped to the requested
|
|
522
|
-
// window, not a blanket rejection — a later, non-overlapping slot for
|
|
523
|
-
// the same host must still succeed.
|
|
524
515
|
const eventType = makeEventType();
|
|
525
516
|
await createBooking({
|
|
526
517
|
eventType,
|
|
@@ -556,9 +547,6 @@ describe("rescheduleBooking", () => {
|
|
|
556
547
|
attendee: { email: ATTENDEE_EMAIL, name: "Attendee One" },
|
|
557
548
|
});
|
|
558
549
|
|
|
559
|
-
// Reschedule to an overlapping-but-shifted slot; if the original
|
|
560
|
-
// booking's own busy interval weren't excluded, this would always
|
|
561
|
-
// conflict with itself.
|
|
562
550
|
const rescheduled = await rescheduleBooking({
|
|
563
551
|
uid: original.uid,
|
|
564
552
|
newStartTime: "2026-08-08T10:15:00.000Z",
|
|
@@ -597,9 +585,6 @@ describe("rescheduleBooking", () => {
|
|
|
597
585
|
}),
|
|
598
586
|
).rejects.toBeInstanceOf(SlotConflictError);
|
|
599
587
|
|
|
600
|
-
// The original booking must stay intact after a failed reschedule
|
|
601
|
-
// attempt — it should never be left marked "rescheduled" with no
|
|
602
|
-
// successor.
|
|
603
588
|
const stillOriginal = await getBookingByUid(original.uid);
|
|
604
589
|
expect(stillOriginal?.status).toBe("confirmed");
|
|
605
590
|
});
|
|
@@ -1,17 +1,5 @@
|
|
|
1
1
|
import { eq } from "drizzle-orm";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Booking lifecycle — create, reschedule, cancel — wired to providers and
|
|
5
|
-
* workflow hooks.
|
|
6
|
-
*
|
|
7
|
-
* The booking flow:
|
|
8
|
-
* 1. Load event type and resolve host (1:1, round-robin).
|
|
9
|
-
* 2. Validate requested slot against availability engine.
|
|
10
|
-
* 3. Insert the booking row + attendees.
|
|
11
|
-
* 4. For each video/calendar provider, create external events and attach
|
|
12
|
-
* references to the booking.
|
|
13
|
-
* 5. Emit lifecycle event → workflows materialize scheduled reminders.
|
|
14
|
-
*/
|
|
15
3
|
import type {
|
|
16
4
|
Booking,
|
|
17
5
|
Attendee,
|
|
@@ -50,9 +38,7 @@ export interface CreateBookingInput {
|
|
|
50
38
|
iCalUid?: string;
|
|
51
39
|
iCalSequence?: number;
|
|
52
40
|
orgId?: string;
|
|
53
|
-
/** If set, we're rescheduling from this booking uid */
|
|
54
41
|
fromReschedule?: string;
|
|
55
|
-
/** Keep Zoom reschedules uncommitted until the replacement meeting is usable. */
|
|
56
42
|
requireZoomMeeting?: boolean;
|
|
57
43
|
}
|
|
58
44
|
|
|
@@ -107,7 +93,6 @@ export async function createBooking(
|
|
|
107
93
|
|
|
108
94
|
let usableZoomMeeting = false;
|
|
109
95
|
|
|
110
|
-
// Create video meeting if location is a video kind
|
|
111
96
|
if (booking.location && isVideoKind(booking.location.kind)) {
|
|
112
97
|
const provider = getVideoProvider(
|
|
113
98
|
videoProviderKindFor(booking.location.kind),
|
|
@@ -150,10 +135,8 @@ export async function createBooking(
|
|
|
150
135
|
);
|
|
151
136
|
}
|
|
152
137
|
|
|
153
|
-
// Write to destination calendar
|
|
154
138
|
await writeToDestinationCalendars(booking);
|
|
155
139
|
|
|
156
|
-
// Fire workflow hooks
|
|
157
140
|
await onBookingCreated(booking);
|
|
158
141
|
|
|
159
142
|
const final = await getBookingByUid(booking.uid);
|
|
@@ -175,9 +158,6 @@ export async function rescheduleBooking(input: {
|
|
|
175
158
|
if (!eventType) throw new Error("Event type missing");
|
|
176
159
|
requireZoomMeetingResolution(original, input.zoomMeetingResolved);
|
|
177
160
|
|
|
178
|
-
// Create the new booking (validated against availability, ignoring the
|
|
179
|
-
// original's own slot) before touching the original, so a slot conflict
|
|
180
|
-
// never leaves the original marked "rescheduled" with no successor.
|
|
181
161
|
const attendee = original.attendees[0];
|
|
182
162
|
const guests = original.attendees.slice(1);
|
|
183
163
|
const newBooking = await createBooking({
|
|
@@ -217,12 +197,10 @@ export async function rescheduleBooking(input: {
|
|
|
217
197
|
throw error;
|
|
218
198
|
}
|
|
219
199
|
|
|
220
|
-
// Mark old as rescheduled now that the replacement exists.
|
|
221
200
|
await updateBookingStatus(input.uid, "rescheduled", {
|
|
222
201
|
reschedulingReason: input.reason,
|
|
223
202
|
});
|
|
224
203
|
|
|
225
|
-
// Update external calendar events (PATCH, not delete+create)
|
|
226
204
|
for (const ref of original.references) {
|
|
227
205
|
const provider = getCalendarProvider(ref.type);
|
|
228
206
|
if (provider?.updateEvent && ref.credentialId) {
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import { accessFilter } from "@agent-native/core/sharing";
|
|
2
|
-
/**
|
|
3
|
-
* Data access for bookings + their attendees, references, and notes.
|
|
4
|
-
*/
|
|
5
2
|
import { eq, and, gte, lt, or, desc, asc, isNotNull } from "drizzle-orm";
|
|
6
3
|
import { nanoid } from "nanoid";
|
|
7
4
|
|
|
@@ -98,16 +95,9 @@ export interface ListBookingsFilter {
|
|
|
98
95
|
eventTypeId?: string;
|
|
99
96
|
status?: BookingStatus | "upcoming" | "past" | "unconfirmed" | "recurring";
|
|
100
97
|
attendeeEmail?: string;
|
|
101
|
-
/** Inclusive start (ISO) */
|
|
102
98
|
from?: string;
|
|
103
|
-
/** Exclusive end (ISO) */
|
|
104
99
|
to?: string;
|
|
105
100
|
limit?: number;
|
|
106
|
-
/**
|
|
107
|
-
* If true, admit any booking the current user owns, has been shared on, or
|
|
108
|
-
* matches via org-visibility — in addition to the explicit `hostEmail`
|
|
109
|
-
* filter (which still narrows further when set).
|
|
110
|
-
*/
|
|
111
101
|
useAccessFilter?: boolean;
|
|
112
102
|
}
|
|
113
103
|
|
package/src/server/context.ts
CHANGED
|
@@ -1,32 +1,16 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Server-side context — the package's view of the world.
|
|
3
|
-
*
|
|
4
|
-
* Actions and server logic call `getSchedulingContext()` to get handles to
|
|
5
|
-
* the DB, provider registry, current user, and configuration. The consumer
|
|
6
|
-
* wires this up at app startup via `setSchedulingContext()`.
|
|
7
|
-
*
|
|
8
|
-
* The context is pinned on `globalThis` under a symbol key so Vite SSR +
|
|
9
|
-
* Nitro can safely end up with two module instances (e.g. source via
|
|
10
|
-
* workspace linking vs. dist/) and still share state. Same pattern core uses
|
|
11
|
-
* for the secrets registry.
|
|
12
|
-
*/
|
|
13
1
|
import type { GetDbFn, SchedulingSchema } from "./db-types.js";
|
|
14
2
|
|
|
15
3
|
export interface SchedulingContext {
|
|
16
4
|
getDb: GetDbFn;
|
|
17
5
|
schema: SchedulingSchema;
|
|
18
|
-
/** How the consumer resolves the current user's email (typically from request ctx). */
|
|
19
6
|
getCurrentUserEmail: () => string | undefined;
|
|
20
|
-
/** How the consumer resolves the current user's org id (if multi-tenant). */
|
|
21
7
|
getCurrentOrgId?: () => string | undefined;
|
|
22
|
-
/** Default brand/timezone/week-start for a user (from settings). */
|
|
23
8
|
getUserPreferences?: (email: string) => Promise<{
|
|
24
9
|
timezone?: string;
|
|
25
10
|
weekStartsOn?: 0 | 1;
|
|
26
11
|
brandColor?: string;
|
|
27
12
|
darkBrandColor?: string;
|
|
28
13
|
}>;
|
|
29
|
-
/** Base public URL, e.g. "https://sched.example.com" — used in emails + ICS. */
|
|
30
14
|
publicBaseUrl?: string;
|
|
31
15
|
}
|
|
32
16
|
|
package/src/server/db-types.ts
CHANGED
|
@@ -1,16 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DB shape contracts.
|
|
3
|
-
*
|
|
4
|
-
* The package doesn't own a live DB connection — it takes `getDb()` from the
|
|
5
|
-
* consumer, which returns a Drizzle client wired to the consumer's schema.
|
|
6
|
-
* This file declares the minimum shape the package needs.
|
|
7
|
-
*/
|
|
8
1
|
import type * as schemaAll from "../schema/index.js";
|
|
9
2
|
|
|
10
3
|
export type SchedulingSchema = typeof schemaAll;
|
|
11
4
|
|
|
12
|
-
/**
|
|
13
|
-
* GetDb accessor — consumer-provided. Typically returns the Drizzle client
|
|
14
|
-
* used everywhere in the template.
|
|
15
|
-
*/
|
|
16
5
|
export type GetDbFn = () => any;
|