Spaces:
Running
Running
File size: 5,735 Bytes
b17baae | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | # MicProtocol β Database Schema
## Entity Relationship Diagram
```mermaid
erDiagram
USER ||--o{ WORKSPACE_MEMBER : member_of
WORKSPACE ||--o{ WORKSPACE_MEMBER : has_members
WORKSPACE ||--o{ PODCAST : owns
PODCAST ||--o{ GUEST : prospects
PODCAST ||--o{ EPISODE_TEMPLATE : defines
PODCAST ||--o{ TAG : categorizes
GUEST ||--o{ GUEST_TAG : tagged
TAG ||--o{ GUEST_TAG : categorized
GUEST ||--o{ OUTREACH_SEQUENCE : enrolled_in
OUTREACH_SEQUENCE ||--o{ OUTREACH_MESSAGE : contains
GUEST ||--o{ EPISODE : featured_in
PODCAST ||--o{ EPISODE : produces
EPISODE ||--o{ BOOKING : scheduled_via
EPISODE ||--o{ ASSET_REQUEST : requires
ASSET_REQUEST ||--o{ ASSET_SUBMISSION : fulfilled_by
EPISODE ||--o{ PROMO_TASK : generates
USER {
uuid id PK
string email UK
string clerk_id UK
json profile
string default_timezone
timestamp created_at
timestamp updated_at
}
WORKSPACE {
uuid id PK
string name
string slug UK
json billing_settings
timestamp created_at
timestamp updated_at
}
WORKSPACE_MEMBER {
uuid id PK
uuid user_id FK
uuid workspace_id FK
enum role "owner|admin|editor|viewer"
timestamp joined_at
}
PODCAST {
uuid id PK
uuid workspace_id FK
string title
string rss_feed_url
json recording_settings
json outreach_defaults
string calendar_integration_id
timestamp created_at
timestamp updated_at
}
GUEST {
uuid id PK
uuid podcast_id FK
string email "unique per podcast"
string full_name
string company
string title
json social_links
text bio
enum status "prospect|contacted|scheduled|recorded|published"
int priority_score "0-100"
timestamp last_contact_at
json custom_fields
timestamp created_at
timestamp updated_at
}
TAG {
uuid id PK
uuid podcast_id FK
string name "unique per podcast"
string color
}
GUEST_TAG {
uuid id PK
uuid guest_id FK
uuid tag_id FK
timestamp added_at
}
OUTREACH_SEQUENCE {
uuid id PK
uuid guest_id FK
enum status "active|paused|completed"
uuid template_id FK
int current_step
timestamp started_at
timestamp updated_at
}
OUTREACH_MESSAGE {
uuid id PK
uuid sequence_id FK
enum type "initial|followup|booking|reminder"
string subject
text body_html
text body_text
timestamp sent_at
timestamp opened_at
timestamp clicked_at
json metadata
}
EPISODE {
uuid id PK
uuid podcast_id FK
uuid guest_id FK
string title
enum status "draft|scheduled|recorded|editing|published"
timestamp scheduled_at
int duration_minutes
text host_notes
text guest_instructions
enum recording_platform "riverside|zoom|studio"
timestamp created_at
timestamp updated_at
}
EPISODE_TEMPLATE {
uuid id PK
uuid podcast_id FK
string name
int duration_minutes
json required_assets
text guest_instructions
}
BOOKING {
uuid id PK
uuid episode_id FK
string calendar_event_id
timestamp start_time
timestamp end_time
string timezone
string meet_link
json calendar_provider_data
boolean is_confirmed
}
ASSET_REQUEST {
uuid id PK
uuid episode_id FK
enum type "headshot|bio|social_links|intro_audio|talking_points"
boolean is_required
text instructions
timestamp due_date
}
ASSET_SUBMISSION {
uuid id PK
uuid request_id FK
string file_url
string file_type
int file_size
enum status "pending|approved|rejected"
timestamp submitted_at
}
PROMO_TASK {
uuid id PK
uuid episode_id FK
string title
text description
string platform
timestamp scheduled_at
timestamp completed_at
json metadata
}
```
## Key Design Decisions
### Guest Uniqueness is Per-Podcast
A guest can appear on multiple shows with different statuses. The composite unique constraint `@@unique([podcastId, email])` allows this while preventing duplicates within a single podcast.
### Outreach Sequences are State Machines
Sequences track `currentStep` and have `active|paused|completed` status, enabling A/B testing of cadence (e.g., Day 0, Day 3, Day 7 follow-ups). Messages track `sentAt`, `openedAt`, and `clickedAt` independently.
### Asset Requests are Template-Driven
Podcasts define required assets per episode template via JSON in `EpisodeTemplate.requiredAssets`. When an episode is created from a template, asset requests are automatically generated.
### Row-Level Security
The workspace β podcast β guest hierarchy ensures data isolation. All queries scope through `podcastId`, which chains to `workspaceId`, preventing cross-workspace data leakage.
### Index Strategy
Performance-critical queries are covered by composite indexes:
- `guests(podcast_id, status)` β Kanban board column queries
- `guests(podcast_id, priority_score)` β priority sorting
- `outreach_sequences(guest_id, status)` β active sequence lookups
- `bookings(start_time, end_time)` β calendar overlap detection
- `asset_requests(episode_id, type)` β completion status checks
|