Skip to Content
Welcome to Reflecto Docs - Your guide to mindful journaling
Developer GuideDatabase Schema

Database Schema

Reflecto uses PostgreSQL with Prisma 6 as the ORM. The schema supports multi-tenant isolation (all queries scoped to userId), soft deletes, and polymorphic entry types via JSON metadata.

Entity Relationship Overview

User ├── 1:N Entry │ ├── N:M Tag (via _EntryTags) │ ├── N:M Person (via _EntryPeople) │ └── 1:N Attachment ├── 1:N Tag ├── 1:N Person ├── 1:N Attachment ├── 1:N ActivityLog ├── 1:N Streak ├── 1:1 UserPreferences ├── 1:N Account (OAuth providers) └── 1:N Session (active sessions)

Models

Entry

The central model. All six workspace types share a single table, differentiated by type and metadata.

FieldTypeDescription
idString (cuid)Primary key
userIdStringOwner reference
typeEntryType enumjournal, dream, highlight, idea, wisdom, note
titleString?Optional title (max 200 chars)
contentText?HTML content from TipTap (max 50,000 chars)
isStarredBooleanFavorited by user
editorModeString?"bullet" or "simple"
metadataJson?Type-specific fields (see Metadata Pattern below)
createdAtDateTimeCreation timestamp
updatedAtDateTimeLast modification
deletedAtDateTime?Soft delete marker

Indexes:

  • (userId, type, createdAt DESC) — List entries by type
  • (userId, createdAt DESC) — Timeline queries
  • (userId, isStarred) — Starred filter
  • (type) — Global type filtering
  • (deletedAt) — Soft delete filtering

User

FieldTypeDescription
idString (cuid)Primary key
emailString (unique)Login email
nameString?Display name
emailVerifiedDateTime?Verification timestamp
imageString?Avatar URL
hashedPasswordString?bcrypt hash (null for OAuth-only users)
createdAtDateTimeRegistration date
updatedAtDateTimeLast profile update

Attachment

FieldTypeDescription
idString (cuid)Primary key
entryIdStringLinked entry
userIdStringOwner reference
fileUrlStringImageKit CDN URL
fileTypeStringMIME type (image/png, etc.)
fileSizeIntSize in bytes
thumbnailUrlString?Auto-generated thumbnail URL
createdAtDateTimeUpload timestamp

Design Patterns

Soft Deletes

Entries are never physically deleted. The deletedAt field is set to the current timestamp, and all queries include deletedAt: null in their where clause.

On soft delete, tag and person associations are disconnected first to keep relation counts accurate:

// 1. Disconnect relations await db.entry.update({ where: { id: entryId }, data: { tags: { set: [] }, people: { set: [] } }, }); // 2. Set soft delete timestamp await db.entry.update({ where: { id: entryId }, data: { deletedAt: new Date() }, });

JSON Metadata Pattern

All six entry types share the Entry table. Type-specific fields are stored in the metadata JSONB column, validated per type:

Entry TypeMetadata Fields
Journalcategory (string), mood (1-5), tags (string[])
Dream(none)
Highlightimportance (“high”, “medium”, “low”), linked_entry_id
Ideastatus (string)
Wisdomwisdom_type (“quote”, “thought”, “fact”, “excerpt”, “lesson”), author, source
Noteis_pinned (boolean), color (string)

Metadata is validated at the service layer using type-specific Zod schemas before persistence. Invalid fields are stripped silently.

This single-table approach avoids complex joins and makes cross-type queries (timeline, search, insights) efficient while preserving type-specific behavior through validated metadata.

Automatic Extraction

On every create and update operation, the entry content is scanned for #tag and @mention patterns. The extraction services:

  1. Parse tag/person names from the content HTML
  2. Create new Tag/Person records if they do not exist (upsert by userId + name)
  3. Sync the many-to-many relationship (connect new, disconnect removed)

This means tags and people are always in sync with content without manual management.

Multi-Tenant Isolation

Every model includes a userId foreign key. All queries are scoped by userId at the service layer. There are no cross-user queries in the application — each user’s data is completely isolated.

Migration Strategy

Prisma Migrate manages schema changes:

# Development: create and apply migration pnpm db:generate # Production: apply pending migrations pnpm db:migrate # Prototyping: push schema without migration file pnpm db:push

Use db:push only during early development. For production deployments, always use db:generate to create migration files that can be reviewed and version-controlled.

Last updated on