Skip to content

Zorbit DB Design

The Zorbit database is built on PostgreSQL. The schema is designed to support a comprehensive LinkedIn content generation and management platform with subscription-based access control, usage tracking, and multi-persona content creation capabilities.

All major entities implement soft deletion through the isDeleted boolean field rather than hard deletion. This design decision provides several critical benefits:

  • Data Recovery: Accidental deletions can be reversed without data loss
  • Audit Trail: Historical data remains accessible for analytics and compliance
  • Referential Integrity: Related records maintain their relationships even after “deletion”
  • User Experience: Users can restore content they thought was permanently removed

This pattern is consistently applied across User, Post, Template, Persona, ChatMessage, Thread, ContentIdea, and Comment tables.

The Post table supports multiple content types (post, knowledge_stack, post_stack, idea, comment) and tracks generation sources. This design enables:

  • Unified Content Management: All content types share the same storage and management infrastructure
  • Source Tracking: The source field identifies where content originated (feed, generate, etc.)
  • Metadata Flexibility: The metadata JSON field allows storing varying attributes without schema changes
  • System Persona Support: systemPersonaId enables personas that don’t require database storage

The platform implements a two-tier quota management system:

  1. Plan-Based Limits: Each Plan defines maximum allowances for different features
  2. Usage Tracking: UsageQuota and UsageLog tables provide granular tracking and reset capabilities

This architecture supports:

  • Flexible Billing Cycles: Quotas reset based on resetDate and billing cycle boundaries
  • Detailed Analytics: UsageLog captures metadata about each usage event
  • Multiple Quota Types: Supports post generation, idea generation, personas, image creation, comment generation, and post scheduling

The schema includes fields for external payment provider integration (CustomerId, ProductId, SubscriptionId, InvoiceId). This design:

  • Decouples Payment Logic: Payment provider changes don’t require schema modifications
  • Maintains Local Records: All subscription and payment data remains in the database
  • Enables Multi-Provider Support: Can support multiple payment providers simultaneously

Chat messages are organized into threads, enabling:

  • Conversation Context: Related messages are grouped together
  • Thread Types: Support for different conversation types (post, idea)
  • Scalable History: Users can maintain multiple concurrent conversations

This query retrieves a user’s active subscription and current usage quotas, essential for feature access control:

-- Get user subscription status and plan limits
SELECT
s.status AS subscription_status,
p.post_generation_limit,
p.idea_generation_limit,
p.comment_generation_limit,
p.post_scheduling_limit
FROM "User" u
LEFT JOIN "Subscription" s ON u.id = s."userId"
LEFT JOIN "Plan" p ON s."planId" = p.id
WHERE u.id = $1 AND u."isDeleted" = false;
-- Get current usage quotas
SELECT
type,
used,
limit
FROM "UsageQuota"
WHERE "userId" = $1
AND "resetDate" >= CURRENT_DATE;

Retrieves posts generated using a specific persona, including generation source and metadata:

SELECT
p.id,
p.content,
p.published,
per.name AS persona_name
FROM "Post" p
INNER JOIN "Persona" per ON p."personaId" = per.id
WHERE p."personaId" = $1
AND p."isDeleted" = false
AND p.published = true
ORDER BY p."createdAt" DESC;

Determines if a user has exceeded their quota for a specific feature type:

SELECT
(used < limit) AS has_quota,
(limit - used) AS remaining_quota
FROM "UsageQuota"
WHERE "userId" = $1
AND type = $2
AND "resetDate" >= CURRENT_DATE
LIMIT 1;

Retrieves posts scheduled for publication, critical for automated publishing workflows:

SELECT
p.id,
p.content,
p."scheduledOn",
u.email AS user_email,
s.status AS subscription_status
FROM "Post" p
INNER JOIN "User" u ON p."userId" = u.id
LEFT JOIN "Subscription" s ON u.id = s."userId"
WHERE p."scheduledOn" IS NOT NULL
AND p."scheduledOn" <= CURRENT_TIMESTAMP
AND p."isPostedOnLinkedin" = false
AND p."isDeleted" = false
ORDER BY p."scheduledOn" ASC;

Fetches saved content ideas with their associated chat context:

SELECT
ci.id,
ci.title,
ci.content,
ci."keyTakeaways"
FROM "ContentIdea" ci
INNER JOIN "ChatMessage" cm ON ci."chatMessageId" = cm.id
WHERE ci."isSaved" = true
AND ci."isDeleted" = false
AND cm."userId" = $1
ORDER BY ci."createdAt" DESC;

Database migrations should be managed through a version-controlled migration system that provides:

  • Version Control: Each migration is a numbered SQL file tracked in version control
  • Reproducibility: Migrations can be applied consistently across environments
  • History Tracking: Complete audit trail of all schema changes
  1. Development: Create SQL migration files during development
  2. Review: All migrations are committed to version control for team review
  3. Testing: Migrations are tested in staging before production deployment
  4. Production: Applied using migration tools or scripts in production environments

For rollback scenarios:

  • Create Reverse Migrations: When a rollback is needed, create a new migration that reverses the changes
  • Data Preservation: Soft delete patterns ensure data isn’t lost during rollbacks
  • Staged Rollouts: Use feature flags to disable new features before rolling back migrations
  • Backup Before Migration: Always backup production databases before applying migrations

Several mechanisms ensure data integrity throughout the migration process:

  • Foreign Key Constraints: All relationships use appropriate ON DELETE CASCADE or ON DELETE RESTRICT clauses
  • Transaction Wrappers: Critical migrations are wrapped in database transactions
  • Validation Rules: Database constraints enforce data types, required fields, and unique constraints
  • Soft Delete Consistency: Queries consistently filter isDeleted: false to maintain logical consistency
  • Atomic Changes: Keep migrations focused on single, atomic changes
  • Non-Breaking First: Add new fields as nullable before making them required
  • Index Management: Add indexes in separate migrations to monitor performance impact
  • Data Migrations: Separate schema changes from data transformations
  • Testing: Test migrations on production-like data volumes before deployment

This section provides a complete reference of all database tables with their fields, data types, constraints, and example data.

FieldTypeConstraintsKeyExample
idUUIDPKPK550e8400-e29b-41d4-a716-446655440000
emailVARCHARUNQUNQuser@example.com
nameVARCHARNULL-John Doe
roleVARCHARDEF ‘user’-user
dodoCustomerIdVARCHARNULL-cus_abc123
createdAtTIMESTAMPDEF now()-2024-01-15 10:30:00
updatedAtTIMESTAMPDEF now()-2024-01-15 10:30:00
isDeletedBOOLEANDEF false-false
FieldTypeConstraintsKeyExample
idUUIDPKPK660e8400-e29b-41d4-a716-446655440000
titleVARCHARNULL-My LinkedIn Post
contentTEXT--This is the post content...
imageUrlVARCHARNULL-https://cdn.example.com/image.jpg
imageKeyVARCHARNULL-posts/123/image.jpg
publishedBOOLEANDEF false-true
isPostedOnLinkedinBOOLEANDEF false-false
linkedinUrlVARCHARNULL-https://linkedin.com/posts/123
linkedinPostedAtTIMESTAMPNULL-2024-01-15 12:00:00
scheduledOnTIMESTAMPNULL-2024-01-20 09:00:00
typeVARCHARDEF ‘post’-post
sourceVARCHARNULL-generate
metadataJSONBNULL-{"persona": "tech-expert"}
systemPersonaIdVARCHARNULL-sys_persona_123
createdAtTIMESTAMPDEF now()-2024-01-15 10:30:00
updatedAtTIMESTAMPDEF now()-2024-01-15 10:30:00
isDeletedBOOLEANDEF false-false
isLikedBOOLEANDEF false-false
isDislikedBOOLEANDEF false-false
isSavedBOOLEANDEF false-false
userIdUUIDFKFK → User.id550e8400-e29b-41d4-a716-446655440000
templateIdUUIDNULL, FKFK → Template.id770e8400-e29b-41d4-a716-446655440000
chatMessageIdUUIDNULL, FKFK → ChatMessage.id880e8400-e29b-41d4-a716-446655440000
personaIdUUIDNULL, FKFK → Persona.id990e8400-e29b-41d4-a716-446655440000
FieldTypeConstraintsKeyExample
idUUIDPKPK770e8400-e29b-41d4-a716-446655440000
nameVARCHAR--Weekly Update Template
contentTEXT--Template content here...
createdAtTIMESTAMPDEF now()-2024-01-15 10:30:00
updatedAtTIMESTAMPDEF now()-2024-01-15 10:30:00
isDeletedBOOLEANDEF false-false
userIdUUIDFKFK → User.id550e8400-e29b-41d4-a716-446655440000
FieldTypeConstraintsKeyExample
idUUIDPKPK990e8400-e29b-41d4-a716-446655440000
nameVARCHAR--Tech Thought Leader
descriptionTEXTNULL-Expert in cloud computing...
defaultPromptTEXT--Write as a tech expert...
targetAudienceVARCHARNULL-Software engineers
professionVARCHARNULL-Cloud Architect
relevantKeywordsVARCHARNULL-AWS, cloud, serverless
opinionsTEXTNULL-Believes in microservices...
vocabularyVARCHARNULL-Technical, precise
avoidWordsVARCHARNULL-buzzwords, jargon
endGoalVARCHARNULL-Educate and inspire
customHashtagVARCHARNULL-#CloudTech
fixCtaVARCHARNULL-Learn more in comments
preferencesJSONBNULL-{"tone": "professional"}
createdAtTIMESTAMPDEF now()-2024-01-15 10:30:00
updatedAtTIMESTAMPDEF now()-2024-01-15 10:30:00
isDeletedBOOLEANDEF false-false
userIdUUIDFKFK → User.id550e8400-e29b-41d4-a716-446655440000
FieldTypeConstraintsKeyExample
idUUIDPKPK880e8400-e29b-41d4-a716-446655440000
contentTEXT--Generate a post about AI...
createdAtTIMESTAMPDEF now()-2024-01-15 10:30:00
updatedAtTIMESTAMPDEF now()-2024-01-15 10:30:00
isDeletedBOOLEANDEF false-false
generationTimeFLOATNULL-2.5
userIdUUIDFKFK → User.id550e8400-e29b-41d4-a716-446655440000
threadIdUUIDFKFK → Thread.idaa0e8400-e29b-41d4-a716-446655440000
FieldTypeConstraintsKeyExample
idUUIDPKPKaa0e8400-e29b-41d4-a716-446655440000
titleVARCHARNULL-AI Content Ideas
typeVARCHARNULL, DEF ‘post’-post
createdAtTIMESTAMPDEF now()-2024-01-15 10:30:00
updatedAtTIMESTAMPDEF now()-2024-01-15 10:30:00
isDeletedBOOLEANDEF false-false
userIdUUIDFKFK → User.id550e8400-e29b-41d4-a716-446655440000
FieldTypeConstraintsKeyExample
idUUIDPKPKbb0e8400-e29b-41d4-a716-446655440000
titleVARCHAR--The Future of AI
contentTEXT--Full content idea text...
keyTakeawaysTEXT[]NULL-["AI is transformative", "Ethics matter"]
controversialAngleTEXTNULL-AI will replace jobs...
createdAtTIMESTAMPDEF now()-2024-01-15 10:30:00
updatedAtTIMESTAMPDEF now()-2024-01-15 10:30:00
isDeletedBOOLEANDEF false-false
isSavedBOOLEANDEF false-false
chatMessageIdUUIDFKFK → ChatMessage.id880e8400-e29b-41d4-a716-446655440000
FieldTypeConstraintsKeyExample
idUUIDPKPKcc0e8400-e29b-41d4-a716-446655440000
nameVARCHAR--Pro
descriptionTEXTNULL-Professional plan with advanced features
priceDECIMAL(10,2)--29.99
currencyVARCHARDEF ‘USD’-USD
intervalVARCHARDEF ‘month’-month
featuresJSONBNULL-["feature1", "feature2"]
postGenerationLimitINTEGER--100
ideaGenerationLimitINTEGER--50
commentGenerationLimitINTEGERDEF 0-20
postSchedulingLimitINTEGERDEF 0-30
personaLimitINTEGER--10
imageCreationLimitINTEGER--25
dodoProductIdVARCHARNULL-prod_abc123
isActiveBOOLEANDEF true-true
createdAtTIMESTAMPDEF now()-2024-01-15 10:30:00
updatedAtTIMESTAMPDEF now()-2024-01-15 10:30:00
FieldTypeConstraintsKeyExample
idUUIDPKPKdd0e8400-e29b-41d4-a716-446655440000
statusVARCHAR--active
startDateTIMESTAMP--2024-01-01 00:00:00
endDateTIMESTAMPNULL-2024-12-31 23:59:59
renewalDateTIMESTAMP--2024-02-01 00:00:00
canceledAtTIMESTAMPNULL-NULL
trialEndsAtTIMESTAMPNULL-2024-01-15 00:00:00
userIdUUIDFK, UNQFK → User.id, UNQ550e8400-e29b-41d4-a716-446655440000
planIdUUIDFKFK → Plan.idcc0e8400-e29b-41d4-a716-446655440000
dodoSubscriptionIdVARCHARNULL-sub_abc123
createdAtTIMESTAMPDEF now()-2024-01-15 10:30:00
updatedAtTIMESTAMPDEF now()-2024-01-15 10:30:00
FieldTypeConstraintsKeyExample
idUUIDPKPKee0e8400-e29b-41d4-a716-446655440000
typeVARCHAR--credit_card
cardBrandVARCHARNULL-visa
lastFourVARCHARNULL-4242
expiryMonthINTEGERNULL-12
expiryYearINTEGERNULL-2025
isDefaultBOOLEANDEF false-true
userIdUUIDFKFK → User.id550e8400-e29b-41d4-a716-446655440000
createdAtTIMESTAMPDEF now()-2024-01-15 10:30:00
updatedAtTIMESTAMPDEF now()-2024-01-15 10:30:00
FieldTypeConstraintsKeyExample
idUUIDPKPKff0e8400-e29b-41d4-a716-446655440000
invoiceNumberVARCHARUNQUNQINV-2024-001
amountDECIMAL(10,2)--29.99
currencyVARCHARDEF ‘USD’-USD
statusVARCHAR--paid
billingDateTIMESTAMP--2024-01-15 00:00:00
dueDateTIMESTAMP--2024-01-30 00:00:00
paidDateTIMESTAMPNULL-2024-01-16 14:30:00
stripeInvoiceIdVARCHARNULL-in_abc123
userIdUUIDFKFK → User.id550e8400-e29b-41d4-a716-446655440000
paymentMethodIdUUIDNULL, FKFK → PaymentMethod.idee0e8400-e29b-41d4-a716-446655440000
createdAtTIMESTAMPDEF now()-2024-01-15 10:30:00
updatedAtTIMESTAMPDEF now()-2024-01-15 10:30:00
FieldTypeConstraintsKeyExample
idUUIDPKPK110e8400-e29b-41d4-a716-446655440000
descriptionVARCHAR--Pro Plan - Monthly
quantityINTEGER--1
unitPriceDECIMAL(10,2)--29.99
amountDECIMAL(10,2)--29.99
invoiceIdUUIDFKFK → Invoice.idff0e8400-e29b-41d4-a716-446655440000
createdAtTIMESTAMPDEF now()-2024-01-15 10:30:00
updatedAtTIMESTAMPDEF now()-2024-01-15 10:30:00
FieldTypeConstraintsKeyExample
idUUIDPKPK220e8400-e29b-41d4-a716-446655440000
typeVARCHAR--post_generation
usedINTEGERDEF 0-45
limitINTEGER--100
resetDateTIMESTAMP--2024-02-01 00:00:00
billingCycleStartTIMESTAMP--2024-01-01 00:00:00
billingCycleEndTIMESTAMP--2024-01-31 23:59:59
userIdUUIDFKFK → User.id550e8400-e29b-41d4-a716-446655440000
createdAtTIMESTAMPDEF now()-2024-01-15 10:30:00
updatedAtTIMESTAMPDEF now()-2024-01-15 10:30:00
FieldTypeConstraintsKeyExample
idUUIDPKPK330e8400-e29b-41d4-a716-446655440000
typeVARCHAR--post_generation
descriptionVARCHARNULL-Generated LinkedIn post
metadataJSONBNULL-{"postId": "123", "persona": "tech"}
userIdVARCHAR--550e8400-e29b-41d4-a716-446655440000
quotaIdUUIDFKFK → UsageQuota.id220e8400-e29b-41d4-a716-446655440000
createdAtTIMESTAMPDEF now()-2024-01-15 10:30:00
FieldTypeConstraintsKeyExample
idUUIDPKPK440e8400-e29b-41d4-a716-446655440000
firstNameVARCHAR--Jane
lastNameVARCHAR--Smith
titleVARCHARNULL-Senior Engineer
imageVARCHARNULL-https://cdn.example.com/avatar.jpg
quoteTEXT--Great insights on AI!
dateTIMESTAMPDEF now()-2024-01-15 10:30:00
profileUrlVARCHARNULL-https://linkedin.com/in/janesmith
createdAtTIMESTAMPDEF now()-2024-01-15 10:30:00
updatedAtTIMESTAMPDEF now()-2024-01-15 10:30:00
isDeletedBOOLEANDEF false-false
userIdUUIDFKFK → User.id550e8400-e29b-41d4-a716-446655440000
personaIdUUIDNULL, FKFK → Persona.id990e8400-e29b-41d4-a716-446655440000