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
| Table | Key columns |
|---|---|
platform_users | id, email, displayName, status |
platform_roles | userId, role (super_admin, admin) |
platform_events | slug, displayName, status, adminEmail, primaryDomain |
platform_event_domains | eventId, hostname, isPrimary |
platform_organizations | name, slug, createdBy |
platform_org_members | orgId, userId |
platform_audit_logs | actorEmail, action, targetType, targetId |
Event tables
| Table | Key columns |
|---|---|
rsvps | eventId, contactEmail, contactName, status, customFields |
attendees | eventId, rsvpId, name, tierId, email, selectedAddOnIds |
payments | eventId, email, amountCents, status, paymentMethod, stripeSessionId |
payment_items | eventId, paymentId, attendeeId, expenseId, amountCents |
settings | eventId, all config fields (nullable) |
schedule_days | eventId, date, label, sortOrder |
schedule_items | eventId, dayId, time, title, locationName, category |
photos | eventId, r2Key, filename, caption, uploadedBy |
documents | eventId, r2Key, title, requiresAuth, scheduleItemId |
polls | eventId, title, type, status, anonymous |
poll_options | eventId, pollId, label, sortOrder |
poll_votes | eventId, pollId, optionId, voterEmail |
announcements | eventId, subject, deliveryType, status, recipientFilter |
content_sections | eventId, type, title, body, isVisible, sortOrder |
access_requests | eventId, email, name, status |
groups | eventId, name, color, sortOrder |
group_members | eventId, groupId, email |
pricing_tiers | eventId, name, multiplier, sortOrder |
add_ons | eventId, name, amountCents, optional, perAttendee |
custom_fields | eventId, fieldKey, label, type, scope, required |
custom_field_options | eventId, fieldId, value, label |
Permission tables
| Table | Parent | Description |
|---|---|---|
document_permissions | documents.id | Email or group access to documents |
schedule_item_permissions | schedule_items.id | Email or group access to schedule items |
content_section_permissions | content_sections.id | Email or group access to content sections |
Model change workflow
- Update interfaces in
apps/event-site/src/lib/types.ts - Update schema in
apps/event-site/src/lib/server/db/schema.ts - Generate and apply migration:
bash
npm run db:generate
npm run db:migrate:localRelated pages
- Database — Drizzle patterns and migration details
- Routes & APIs — route mapping for each model
- Environment Variables —
AppEnvconfiguration