Skip to content

Data Models

TypeScript interfaces and database table mappings for the tripplan.ing domain.

Core interfaces

Source: apps/event-site/src/lib/types.ts

EventConfig

The merged event configuration returned by getMergedConfig():

typescript
interface EventConfig {
  name: string;
  tagline: string;
  description: string;
  startDate: string;
  endDate: string;
  location: string;
  heroImage: string;
  logo?: string;
  favicon?: string;
  mode: 'active' | 'post-event';
  emailFrom?: string;
  emailReplyTo?: string;
  schedule?: ScheduleDay[];
  rsvp?: RsvpConfig;
  contactForm?: { enabled: boolean; recipientEmails: string[] };
  addOns: AddOn[];
  enabledPaymentMethods: ('stripe' | 'paypal')[];
  allowedEmails: string[];
  adminEmails: string[];
  theme: ThemeConfig;
}

Schedule types

typescript
interface ScheduleDay {
  date: string;
  label: string;
  items: ScheduleItem[];
}

interface ScheduleItem {
  id?: string;
  time: string;
  endTime?: string;
  title: string;
  description?: string;
  location?: ScheduleItemLocation;
  notes?: string;                  // Markdown
  heroImageKey?: string;           // R2 key
  galleryImageKeys?: string[];
  requiresAuth?: boolean;
  category?: 'meal' | 'activity' | 'travel' | 'ceremony' | 'free-time' | 'other';
  icon?: string;
  externalUrl?: string;
  requiredAddOnId?: string;
}

interface ScheduleItemLocation {
  name: string;
  address?: string;
  coordinates?: { lat: number; lng: number };
  directionsNote?: string;
}

RSVP and pricing

typescript
interface RsvpConfig {
  enabled: boolean;
  deadline?: string;
  maxAttendeesPerRsvp?: number;    // default: 10
  allowTentative?: boolean;
  collectDietaryInfo?: boolean;
  collectNotes?: boolean;
  pricingTiers: PricingTier[];
  defaultTierId?: string;
  customFields?: CustomField[];
}

interface PricingTier {
  id: string;
  name: string;
  description?: string;
  multiplier: number;              // 1.0 = full, 0.5 = half, 0 = free
  sortOrder?: number;
}

interface AddOn {
  id: string;
  name: string;
  description: string;
  amountCents: number;
  currency: string;
  optional?: boolean;
  perAttendee?: boolean;           // true = per person, false = flat fee
  tierPrices?: Record<string, number>;  // tier ID → amount in cents
}

type RsvpStatus = 'confirmed' | 'tentative' | 'declined';

Custom fields

typescript
interface CustomField {
  id: string;
  label: string;
  type: 'text' | 'textarea' | 'select' | 'multiselect' | 'checkbox' | 'number';
  scope: 'rsvp' | 'attendee';
  required?: boolean;
  placeholder?: string;
  options?: CustomFieldOption[];
  helpText?: string;
  sortOrder?: number;
}

interface CustomFieldOption {
  value: string;
  label: string;
}

type CustomFieldValues = Record<string, string | boolean | number>;

Access and permissions

typescript
interface Group {
  id: string;
  name: string;
  description?: string;
  color?: string;                  // Hex color
  sortOrder: number;
  createdBy: string;
  createdAt: string;
  updatedAt: string;
}

interface GroupMember {
  id: string;
  groupId: string;
  email: string;
  addedBy: string;
  addedAt: string;
}

interface DocumentPermission {
  id: string;
  documentId: string;
  permissionType: 'email' | 'group';
  permissionValue: string;
  createdBy: string;
  createdAt: string;
}

interface ScheduleItemPermission {
  id: string;
  scheduleItemId: string;
  permissionType: 'email' | 'group';
  permissionValue: string;
  createdBy: string;
  createdAt: string;
}

interface PersonWithGroups {
  email: string;
  name?: string;
  source: 'config' | 'approved' | 'pending' | 'denied';
  groups: Group[];
  requestedAt?: string;
}

Content and documents

typescript
type ContentSectionType = 'description' | 'things-to-do' | 'add-ons' | 'custom';

interface ContentSection {
  id: string;
  type: ContentSectionType;
  title: string;
  body?: string;                   // Markdown
  heroImageKey?: string;
  galleryImageKeys?: string[];
  linkUrl?: string;
  linkLabel?: string;
  linkedAddOnIds?: string[];
  sortOrder: number;
  isVisible: boolean;
  requiresAuth?: boolean;
  createdAt: string;
  updatedAt: string;
}

interface Document {
  id: string;
  r2Key: string;
  filename: string;
  contentType: string;
  sizeBytes: number;
  title: string;
  description?: string;
  requiresAuth: boolean;
  scheduleItemId?: string;
  sortOrder: number;
  uploadedBy: string;
  createdAt: string;
  updatedAt: string;
}

Theme and payment

typescript
interface ThemeConfig {
  primaryColor: string;            // Hex, e.g., '#2563EB'
  accentColor: string;
}

type PaymentMethod = 'stripe' | 'paypal' | 'manual';

Runtime interfaces

Source: apps/event-site/src/lib/server/runtime/types.ts

typescript
interface BlobStore {
  put(key: string, data: Uint8Array | ArrayBuffer, contentType?: string): Promise<void>;
  get(key: string): Promise<{ body: ReadableStream; contentType: string } | null>;
  delete(key: string): Promise<void>;
}

interface AppEnv {
  db: Database;
  kv: KvStore;
  blobs: BlobStore;
  STRIPE_SECRET_KEY: string;
  STRIPE_WEBHOOK_SECRET: string;
  PAYPAL_CLIENT_ID: string;
  PAYPAL_CLIENT_SECRET: string;
  PAYPAL_WEBHOOK_ID: string;
  PAYPAL_SANDBOX: string;
  MAILGUN_API_KEY: string;
  MAILGUN_DOMAIN: string;
  PLATFORM_OPERATOR_EMAILS: string;
  PLATFORM_DOMAIN_SUFFIX: string;
}

Database table families

Platform tables

TableKey columns
platform_usersid, email, displayName, status
platform_rolesuserId, role (super_admin, admin)
platform_eventsslug, displayName, status, adminEmail, primaryDomain
platform_event_domainseventId, hostname, isPrimary
platform_organizationsname, slug, createdBy
platform_org_membersorgId, userId
platform_audit_logsactorEmail, action, targetType, targetId

Event tables

TableKey columns
rsvpseventId, contactEmail, contactName, status, customFields
attendeeseventId, rsvpId, name, tierId, email, selectedAddOnIds
paymentseventId, email, amountCents, status, paymentMethod, stripeSessionId
payment_itemseventId, paymentId, attendeeId, expenseId, amountCents
settingseventId, all config fields (nullable)
schedule_dayseventId, date, label, sortOrder
schedule_itemseventId, dayId, time, title, locationName, category
photoseventId, r2Key, filename, caption, uploadedBy
documentseventId, r2Key, title, requiresAuth, scheduleItemId
pollseventId, title, type, status, anonymous
poll_optionseventId, pollId, label, sortOrder
poll_voteseventId, pollId, optionId, voterEmail
announcementseventId, subject, deliveryType, status, recipientFilter
content_sectionseventId, type, title, body, isVisible, sortOrder
access_requestseventId, email, name, status
groupseventId, name, color, sortOrder
group_memberseventId, groupId, email
pricing_tierseventId, name, multiplier, sortOrder
add_onseventId, name, amountCents, optional, perAttendee
custom_fieldseventId, fieldKey, label, type, scope, required
custom_field_optionseventId, fieldId, value, label

Permission tables

TableParentDescription
document_permissionsdocuments.idEmail or group access to documents
schedule_item_permissionsschedule_items.idEmail or group access to schedule items
content_section_permissionscontent_sections.idEmail or group access to content sections

Model change workflow

  1. Update interfaces in apps/event-site/src/lib/types.ts
  2. Update schema in apps/event-site/src/lib/server/db/schema.ts
  3. Generate and apply migration:
bash
npm run db:generate
npm run db:migrate:local

Released under the MIT License.