Skip to content

Zaptam Data Models

Complete Prisma schema documentation with 12 models and 13 enums.


Schema Overview

┌─────────────────────────────────────────────────────────────┐
│                        User                                 │
│  (Central entity - all other models relate to User)        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │ UserSettings │  │    Photo     │  │ RefreshToken │      │
│  │   (1:1)      │  │   (1:many)   │  │   (1:many)   │      │
│  └──────────────┘  └──────────────┘  └──────────────┘      │
│                                                             │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │   Interest   │  │   Message    │  │Conversation  │      │
│  │  (sender/    │  │ (sender/     │  │(participant1/│      │
│  │  recipient)  │  │  recipient)  │  │ participant2)│      │
│  └──────────────┘  └──────────────┘  └──────────────┘      │
│                                                             │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐      │
│  │Verification  │  │   Report     │  │   OtpCode    │      │
│  │   (1:many)   │  │(reporter/    │  │   (1:many)   │      │
│  │              │  │  reported)   │  │              │      │
│  └──────────────┘  └──────────────┘  └──────────────┘      │
│                                                             │
│  ┌──────────────┐                                           │
│  │   Wallet     │  ┌──────────────────────────────────────┐│
│  │ Transaction  │  │        MembershipTier               ││
│  │   (1:many)   │  │      (standalone, no FK)            ││
│  └──────────────┘  └──────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘

Models

User

The central model representing all platform users.

prisma
model User {
  id                String            @id @default(uuid()) @db.Uuid
  phoneNumber       String            @unique @map("phone_number")
  email             String?           @unique
  password          String?
  role              UserRole          @default(USER)
  gender            Gender?
  status            UserStatus        @default(PENDING)

  // Profile
  alias             String?           @unique
  name              String?
  bio               String?
  occupation        String?
  intro             String?
  netWorth          String?           @map("net_worth")
  dateOfBirth       DateTime?         @map("date_of_birth")

  // Trust & Verification
  trustScore        Int               @default(50) @map("trust_score")
  verificationLevel VerificationLevel @default(NONE) @map("verification_level")

  // Timestamps
  lastActiveAt      DateTime?         @map("last_active_at")
  createdAt         DateTime          @default(now()) @map("created_at")
  updatedAt         DateTime          @updatedAt @map("updated_at")
  deletedAt         DateTime?         @map("deleted_at")

  // Relations
  settings             UserSettings?
  photos               Photo[]
  sentMessages         Message[]            @relation("sender")
  receivedMessages     Message[]            @relation("recipient")
  sentInterests        Interest[]           @relation("sender")
  receivedInterests    Interest[]           @relation("recipient")
  verifications        Verification[]
  walletTransactions   WalletTransaction[]
  reports              Report[]             @relation("reporter")
  reportedBy           Report[]             @relation("reported")
  conversations1       Conversation[]       @relation("participant1")
  conversations2       Conversation[]       @relation("participant2")
  refreshTokens        RefreshToken[]
  otpCodes             OtpCode[]

  @@index([phoneNumber])
  @@index([status])
  @@index([gender])
  @@map("users")
}

Field Details

FieldTypeDescription
idUUIDPrimary key
phoneNumberStringUnique, international format (+268...)
emailString?Optional, unique if provided
passwordString?Bcrypt hash, null during pending registration
roleUserRolePermission level (default: USER)
genderGender?MALE or FEMALE
statusUserStatusAccount status (default: PENDING)
aliasString?Display name, unique
nameString?Real name (rarely used)
bioString?Profile description
occupationString?For male users
introString?For female users
netWorthString?Male users: '50k-100k', '100k-500k', '500k-1m', '1m-5m', '5m+'
dateOfBirthDateTime?For age calculation
trustScoreInt0-100, default 50
verificationLevelVerificationLevelNONE, IDENTITY, or FULL
lastActiveAtDateTime?Updated on socket connection
deletedAtDateTime?Soft delete timestamp

UserSettings

Privacy and preferences for each user.

prisma
model UserSettings {
  id               String  @id @default(uuid()) @db.Uuid
  userId           String  @unique @map("user_id") @db.Uuid
  user             User    @relation(fields: [userId], references: [id], onDelete: Cascade)

  aliasMode        Boolean @default(true) @map("alias_mode")
  showOnlineStatus Boolean @default(false) @map("show_online_status")
  regionMasking    Boolean @default(true) @map("region_masking")
  photoBlurLevel   Int     @default(100) @map("photo_blur_level")
  disappearingMsgs Int?    @map("disappearing_msgs_hours")

  @@map("user_settings")
}

Field Details

FieldTypeDefaultDescription
aliasModeBooleantrueShow alias instead of real name
showOnlineStatusBooleanfalseLet others see if online
regionMaskingBooleantrueHide location/region
photoBlurLevelInt100Default blur for photos (0-100)
disappearingMsgsInt?nullAuto-delete messages after N hours

Photo

User profile photos.

prisma
model Photo {
  id        String   @id @default(uuid()) @db.Uuid
  userId    String   @map("user_id") @db.Uuid
  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  url       String
  blurLevel Int      @default(100) @map("blur_level")
  isPrimary Boolean  @default(false) @map("is_primary")
  order     Int      @default(0)
  createdAt DateTime @default(now()) @map("created_at")

  @@map("photos")
}

Field Details

FieldTypeDescription
urlStringCloudflare R2 URL
blurLevelInt0 (clear) to 100 (fully blurred)
isPrimaryBooleanMain profile photo
orderIntDisplay order in gallery

Interest

Express interest between users.

prisma
model Interest {
  id          String         @id @default(uuid()) @db.Uuid
  senderId    String         @map("sender_id") @db.Uuid
  recipientId String         @map("recipient_id") @db.Uuid
  sender      User           @relation("sender", fields: [senderId], references: [id], onDelete: Cascade)
  recipient   User           @relation("recipient", fields: [recipientId], references: [id], onDelete: Cascade)
  status      InterestStatus @default(PENDING)
  createdAt   DateTime       @default(now()) @map("created_at")

  @@unique([senderId, recipientId])
  @@map("interests")
}

Matching Logic

  1. User A expresses interest in User B → creates PENDING Interest (A→B)
  2. User B expresses interest in User A → checks for existing Interest (A→B)
  3. If found and PENDING:
    • Update A→B to ACCEPTED
    • Create B→A as ACCEPTED
    • Create Conversation
    • It's a match!

Conversation

Container for messages between matched users.

prisma
model Conversation {
  id             String    @id @default(uuid()) @db.Uuid
  participant1Id String    @map("participant1_id") @db.Uuid
  participant2Id String    @map("participant2_id") @db.Uuid
  participant1   User      @relation("participant1", fields: [participant1Id], references: [id], onDelete: Cascade)
  participant2   User      @relation("participant2", fields: [participant2Id], references: [id], onDelete: Cascade)
  messages       Message[]
  createdAt      DateTime  @default(now()) @map("created_at")
  updatedAt      DateTime  @updatedAt @map("updated_at")

  @@unique([participant1Id, participant2Id])
  @@map("conversations")
}

Message

Individual chat messages.

prisma
model Message {
  id             String       @id @default(uuid()) @db.Uuid
  conversationId String       @map("conversation_id") @db.Uuid
  conversation   Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
  senderId       String       @map("sender_id") @db.Uuid
  sender         User         @relation("sender", fields: [senderId], references: [id], onDelete: Cascade)
  recipientId    String       @map("recipient_id") @db.Uuid
  recipient      User         @relation("recipient", fields: [recipientId], references: [id], onDelete: Cascade)
  content        String?
  mediaUrl       String?      @map("media_url")
  isRead         Boolean      @default(false) @map("is_read")
  expiresAt      DateTime?    @map("expires_at")
  deletedAt      DateTime?    @map("deleted_at")
  createdAt      DateTime     @default(now()) @map("created_at")

  @@index([conversationId])
  @@index([senderId])
  @@index([recipientId])
  @@map("messages")
}

Field Details

FieldTypeDescription
contentString?Text content (max 2000 chars)
mediaUrlString?Attached media URL
isReadBooleanRead receipt
expiresAtDateTime?Disappearing message expiry
deletedAtDateTime?Soft delete for sender

Verification

Verification submissions and reviews.

prisma
model Verification {
  id         String             @id @default(uuid()) @db.Uuid
  userId     String             @map("user_id") @db.Uuid
  user       User               @relation(fields: [userId], references: [id], onDelete: Cascade)
  type       VerificationType
  status     VerificationStatus @default(PENDING)
  documents  Json?
  notes      String?
  reviewedBy String?            @map("reviewed_by") @db.Uuid
  reviewedAt DateTime?          @map("reviewed_at")
  createdAt  DateTime           @default(now()) @map("created_at")

  @@map("verifications")
}

Verification Types

TypeGenderPurpose
IDENTITYBothID verification (selfie + ID photo)
WORTHMaleProof of net worth
VALUEFemaleProfile value verification

Progression

NONE → (IDENTITY approved) → IDENTITY → (WORTH/VALUE approved) → FULL

WalletTransaction

Financial transactions.

prisma
model WalletTransaction {
  id        String            @id @default(uuid()) @db.Uuid
  userId    String            @map("user_id") @db.Uuid
  user      User              @relation(fields: [userId], references: [id], onDelete: Cascade)
  type      TransactionType
  amount    Decimal           @db.Decimal(10, 2)
  currency  String            @default("USD")
  status    TransactionStatus @default(PENDING)
  reference String?
  createdAt DateTime          @default(now()) @map("created_at")

  @@index([userId])
  @@map("wallet_transactions")
}

Transaction Types

TypeDirectionDescription
MEMBERSHIPDebitMonthly subscription
CREDIT_PURCHASECreditBuying credits (male)
EARNINGCreditEarnings from engagement (female)
WITHDRAWALDebitWithdrawing earnings (female)
BOOSTDebitProfile boost purchase

Report

User reports for moderation.

prisma
model Report {
  id          String       @id @default(uuid()) @db.Uuid
  reporterId  String       @map("reporter_id") @db.Uuid
  reporter    User         @relation("reporter", fields: [reporterId], references: [id], onDelete: Cascade)
  reportedId  String       @map("reported_id") @db.Uuid
  reported    User         @relation("reported", fields: [reportedId], references: [id], onDelete: Cascade)
  type        ReportType
  description String
  evidence    Json?
  status      ReportStatus @default(PENDING)
  resolution  String?
  resolvedBy  String?      @map("resolved_by") @db.Uuid
  resolvedAt  DateTime?    @map("resolved_at")
  createdAt   DateTime     @default(now()) @map("created_at")

  @@map("reports")
}

Report Types

TypeSeverityDescription
BLACKMAILCriticalExtortion attempts
HARASSMENTHighUnwanted contact/messages
FAKE_PROFILEMediumSuspected fake identity
INAPPROPRIATE_CONTENTMediumInappropriate photos/messages
OTHERVariableOther violations

MembershipTier

Subscription tiers (standalone, no user FK).

prisma
model MembershipTier {
  id        String   @id @default(uuid()) @db.Uuid
  name      String
  price     Decimal  @db.Decimal(10, 2)
  currency  String   @default("USD")
  duration  Int
  features  Json
  isActive  Boolean  @default(true) @map("is_active")
  createdAt DateTime @default(now()) @map("created_at")

  @@map("membership_tiers")
}

Default Tiers

NamePriceDurationKey Features
Standard$4930 daysWorth verification, basic access
Premium$14930 daysWorth badge, priority discovery
Elite$49930 daysPersonal curator, 24/7 concierge

RefreshToken

JWT refresh tokens.

prisma
model RefreshToken {
  id        String   @id @default(uuid()) @db.Uuid
  userId    String   @map("user_id") @db.Uuid
  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  token     String   @unique
  expiresAt DateTime @map("expires_at")
  createdAt DateTime @default(now()) @map("created_at")

  @@index([userId])
  @@index([token])
  @@map("refresh_tokens")
}

OtpCode

One-time passwords for verification.

prisma
model OtpCode {
  id        String   @id @default(uuid()) @db.Uuid
  userId    String?  @map("user_id") @db.Uuid
  user      User?    @relation(fields: [userId], references: [id], onDelete: Cascade)
  phone     String
  code      String
  purpose   OtpPurpose
  expiresAt DateTime @map("expires_at")
  usedAt    DateTime? @map("used_at")
  createdAt DateTime @default(now()) @map("created_at")

  @@index([phone, code])
  @@map("otp_codes")
}

Enums

UserRole

prisma
enum UserRole {
  USER         // Regular user
  CURATOR      // Can review verifications, reports
  ADMIN        // Can manage users, suspend/ban
  SUPER_ADMIN  // Full access
}

Role Hierarchy: USER (1) < CURATOR (2) < ADMIN (3) < SUPER_ADMIN (4)


Gender

prisma
enum Gender {
  MALE
  FEMALE
}

UserStatus

prisma
enum UserStatus {
  PENDING    // Registration incomplete
  ACTIVE     // Full access
  SUSPENDED  // Temporarily restricted
  BANNED     // Permanently blocked
  DELETED    // Soft deleted
}

VerificationLevel

prisma
enum VerificationLevel {
  NONE      // No verification
  IDENTITY  // ID verified
  FULL      // ID + worth/value verified
}

VerificationType

prisma
enum VerificationType {
  IDENTITY  // ID document + selfie
  WORTH     // Net worth proof (male)
  VALUE     // Profile value (female)
}

VerificationStatus

prisma
enum VerificationStatus {
  PENDING   // Awaiting review
  APPROVED  // Verified
  REJECTED  // Failed verification
}

InterestStatus

prisma
enum InterestStatus {
  PENDING   // Interest expressed, awaiting reciprocation
  ACCEPTED  // Mutual match
  DECLINED  // Rejected
}

TransactionType

prisma
enum TransactionType {
  MEMBERSHIP       // Subscription payment
  CREDIT_PURCHASE  // Buying credits
  EARNING          // Platform earnings
  WITHDRAWAL       // Cashing out
  BOOST            // Profile boost
}

TransactionStatus

prisma
enum TransactionStatus {
  PENDING    // Processing
  COMPLETED  // Success
  FAILED     // Failed
  REFUNDED   // Reversed
}

ReportType

prisma
enum ReportType {
  BLACKMAIL             // Extortion
  HARASSMENT            // Unwanted contact
  FAKE_PROFILE          // Suspected fake
  INAPPROPRIATE_CONTENT // Violations
  OTHER                 // Miscellaneous
}

ReportStatus

prisma
enum ReportStatus {
  PENDING       // New report
  INVESTIGATING // Under review
  RESOLVED      // Action taken
  DISMISSED     // No action needed
}

OtpPurpose

prisma
enum OtpPurpose {
  REGISTRATION    // New account
  LOGIN           // Login verification
  PASSWORD_RESET  // Reset password
}

Database Indexes

TableIndexPurpose
usersphone_numberFast login lookup
usersstatusFilter active users
usersgenderDiscovery filtering
messagesconversation_idMessage retrieval
messagessender_idSent messages
messagesrecipient_idReceived messages
wallet_transactionsuser_idUser's transactions
refresh_tokensuser_idUser's tokens
refresh_tokenstokenToken validation
otp_codesphone, codeOTP lookup

Cascade Deletes

All user-related models use onDelete: Cascade, meaning:

  • When a User is deleted, all related records are automatically deleted
  • This includes: settings, photos, messages, interests, verifications, transactions, reports, tokens, OTPs

One chat. Everything done.