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