# Data model (entity–relationship) Generated from `backend/prisma/schema.prisma` — 24 entities. Regenerate after any schema change; the schema is the source of truth. ## ER diagram ```mermaid erDiagram AgentProfile ||--o{ AgentProfileFieldValue : agentProfile AgentProfile ||--o{ ConnectionRequest : agentProfile AgentProfile ||--o{ Conversation : agentProfile AgentProfile ||--o{ Testimonial : agentProfile AgentProfile ||--o{ VerificationHistory : agentProfile AgentSubscription o|--o{ Payment : subscription AgentType o|--o{ AgentProfile : agentType AgentType ||--o{ AgentTypeSection : agentType Conversation ||--o{ Message : conversation ProfileField ||--o{ AgentProfileFieldValue : field ProfileSection ||--o{ AgentTypeSection : section ProfileSection ||--o{ ProfileField : section SubscriptionPlan ||--o{ AgentSubscription : plan SupportChat ||--o{ SupportMessage : chat User o|--o{ AuditLog : actor User o|--o{ VerificationHistory : admin User ||--o{ AgentSubscription : user User ||--o{ ConnectionRequest : user User ||--o{ Conversation : user User ||--o{ Message : sender User ||--o{ Notification : user User ||--o{ Payment : user User ||--o{ Session : user User ||--o{ SupportChat : user User ||--o{ UserReport : reportedUser User ||--o{ UserReport : reporter User ||--o| AgentProfile : user User ||--o| UserProfile : user ``` ## Entities ### AgentProfile | Field | Type | Notes | |---|---|---| | `id` | `String` | PK, default `uuid(` | | `userId` | `String` | unique | | `slug` | `String` | unique | | `agentTypeId` | `String?` | | | `firstName` | `String?` | | | `lastName` | `String?` | | | `phone` | `String?` | | | `avatar` | `String?` | | | `bio` | `String?` | | | `headline` | `String?` | | | `city` | `String?` | | | `state` | `String?` | | | `country` | `String?` | | | `address` | `String?` | | | `zipCode` | `String?` | | | `latitude` | `Float?` | | | `longitude` | `Float?` | | | `yearsOfExperience` | `Int?` | | | `licenseNumber` | `String?` | | | `companyName` | `String?` | | | `website` | `String?` | | | `facebookUrl` | `String?` | | | `twitterUrl` | `String?` | | | `linkedinUrl` | `String?` | | | `instagramUrl` | `String?` | | | `isVerified` | `Boolean` | default `false` | | `verificationStatus` | `VerificationStatus` | enum, default `NONE` | | `verificationNote` | `String?` | Admin note (rejection reason) | | `verifiedAt` | `DateTime?` | | | `verifiedBy` | `String?` | Admin user ID who verified | | `isProfileComplete` | `Boolean` | default `false` | | `profileCompleteness` | `Int` | default `0` | | `isPublic` | `Boolean` | default `true` | | `isFeatured` | `Boolean` | default `false` | | `isAvailable` | `Boolean` | default `true`, Agent availability status for connect requests | | `subscriptionStatus` | `String?` | "ACTIVE", "NONE", etc. | | `totalReviews` | `Int` | default `0` | | `averageRating` | `Float` | default `0` | | `createdAt` | `DateTime` | default `now(` | | `updatedAt` | `DateTime` | | | `user` | `User` | FK, relation | | `agentType` | `AgentType?` | FK, relation | | `fieldValues` | `AgentProfileFieldValue[]` | relation | | `testimonialToken` | `String?` | unique | | `connectionRequests` | `ConnectionRequest[]` | relation | | `conversations` | `Conversation[]` | relation | | `testimonials` | `Testimonial[]` | relation | | `verificationHistory` | `VerificationHistory[]` | relation | ### AgentProfileFieldValue | Field | Type | Notes | |---|---|---| | `id` | `String` | PK, default `uuid(` | | `agentProfileId` | `String` | | | `fieldId` | `String` | | | `textValue` | `String?` | For TEXT, TEXTAREA | | `numberValue` | `Float?` | For NUMBER, RANGE | | `booleanValue` | `Boolean?` | For single CHECKBOX | | `jsonValue` | `Json?` | For MULTI_SELECT, RADIO, complex data | | `dateValue` | `DateTime?` | For DATE | | `createdAt` | `DateTime` | default `now(` | | `updatedAt` | `DateTime` | | | `agentProfile` | `AgentProfile` | FK, relation | | `field` | `ProfileField` | FK, relation | ### AgentSubscription | Field | Type | Notes | |---|---|---| | `id` | `String` | PK, default `uuid(` | | `userId` | `String` | | | `planId` | `String` | | | `stripeCustomerId` | `String` | cus_xxx | | `stripeSubscriptionId` | `String?` | unique, sub_xxx | | `status` | `SubscriptionStatus` | enum, default `INCOMPLETE` | | `currentPeriodStart` | `DateTime?` | | | `currentPeriodEnd` | `DateTime?` | | | `cancelAtPeriodEnd` | `Boolean` | default `false` | | `canceledAt` | `DateTime?` | | | `createdAt` | `DateTime` | default `now(` | | `updatedAt` | `DateTime` | | | `user` | `User` | FK, relation | | `plan` | `SubscriptionPlan` | FK, relation | | `payments` | `Payment[]` | relation | ### AgentType | Field | Type | Notes | |---|---|---| | `id` | `String` | PK, default `uuid(` | | `name` | `String` | unique | | `description` | `String?` | | | `icon` | `String?` | | | `isActive` | `Boolean` | default `true` | | `sortOrder` | `Int` | default `0` | | `createdAt` | `DateTime` | default `now(` | | `updatedAt` | `DateTime` | | | `agents` | `AgentProfile[]` | relation | | `agentTypeSections` | `AgentTypeSection[]` | relation | ### AgentTypeSection | Field | Type | Notes | |---|---|---| | `id` | `String` | PK, default `uuid(` | | `agentTypeId` | `String` | | | `sectionId` | `String` | | | `sortOrder` | `Int` | default `0`, Order specific to this agent type | | `isRequired` | `Boolean` | default `false`, Is this section required for this type | | `createdAt` | `DateTime` | default `now(` | | `updatedAt` | `DateTime` | | | `agentType` | `AgentType` | FK, relation | | `section` | `ProfileSection` | FK, relation | ### AuditLog | Field | Type | Notes | |---|---|---| | `id` | `String` | PK, default `uuid(` | | `actorId` | `String?` | null = system / unauthenticated | | `actorRole` | `String?` | USER, AGENT, ADMIN, SUPER_ADMIN, SYSTEM | | `action` | `String` | see AuditAction enum in audit.constants.ts | | `resourceType` | `String?` | User, AgentProfile, Subscription, Payment, etc. | | `resourceId` | `String?` | | | `metadata` | `Json?` | { before, after, reason, params, ... } | | `ipAddress` | `String?` | | | `userAgent` | `String?` | | | `createdAt` | `DateTime` | default `now(` | | `actor` | `User?` | FK, relation | ### CmsContent | Field | Type | Notes | |---|---|---| | `id` | `String` | PK, default `uuid(` | | `pageSlug` | `String` | | | `sectionKey` | `String` | | | `content` | `Json` | | | `isPublished` | `Boolean` | default `true` | | `createdAt` | `DateTime` | default `now(` | | `updatedAt` | `DateTime` | | ### ConnectionRequest | Field | Type | Notes | |---|---|---| | `id` | `String` | PK, default `uuid(` | | `userId` | `String` | User sending the request | | `agentProfileId` | `String` | Agent receiving the request | | `status` | `ConnectionStatus` | enum, default `PENDING` | | `message` | `String?` | Optional message from user | | `createdAt` | `DateTime` | default `now(` | | `updatedAt` | `DateTime` | | | `respondedAt` | `DateTime?` | When agent responded | | `user` | `User` | FK, relation | | `agentProfile` | `AgentProfile` | FK, relation | ### ContactMessage | Field | Type | Notes | |---|---|---| | `id` | `String` | PK, default `uuid(` | | `name` | `String` | | | `email` | `String` | | | `phone` | `String?` | | | `message` | `String` | | | `isRead` | `Boolean` | default `false` | | `createdAt` | `DateTime` | default `now(` | ### Conversation | Field | Type | Notes | |---|---|---| | `id` | `String` | PK, default `uuid(` | | `userId` | `String` | Regular user in the conversation | | `agentProfileId` | `String` | Agent in the conversation | | `lastMessageAt` | `DateTime?` | | | `lastMessageText` | `String?` | | | `userUnreadCount` | `Int` | default `0`, Unread count for the user | | `agentUnreadCount` | `Int` | default `0`, Unread count for the agent | | `userMuted` | `Boolean` | default `false` | | `agentMuted` | `Boolean` | default `false` | | `userFavorited` | `Boolean` | default `false` | | `agentFavorited` | `Boolean` | default `false` | | `userClearedAt` | `DateTime?` | | | `agentClearedAt` | `DateTime?` | | | `userDeletedAt` | `DateTime?` | | | `agentDeletedAt` | `DateTime?` | | | `createdAt` | `DateTime` | default `now(` | | `updatedAt` | `DateTime` | | | `user` | `User` | FK, relation | | `agentProfile` | `AgentProfile` | FK, relation | | `messages` | `Message[]` | relation | ### Message | Field | Type | Notes | |---|---|---| | `id` | `String` | PK, default `uuid(` | | `conversationId` | `String` | | | `senderId` | `String` | User ID of the sender (can be user or agent's user) | | `content` | `String` | | | `messageType` | `MessageType` | enum, default `TEXT` | | `fileUrl` | `String?` | | | `fileName` | `String?` | | | `fileSize` | `Int?` | File size in bytes | | `mimeType` | `String?` | | | `status` | `MessageStatus` | enum, default `SENT` | | `deliveredAt` | `DateTime?` | | | `readAt` | `DateTime?` | | | `createdAt` | `DateTime` | default `now(` | | `updatedAt` | `DateTime` | | | `conversation` | `Conversation` | FK, relation | | `sender` | `User` | FK, relation | ### Notification | Field | Type | Notes | |---|---|---| | `id` | `String` | PK, default `uuid(` | | `userId` | `String` | | | `type` | `String` | 'connection', 'message', 'system', 'update', 'request' | | `title` | `String` | | | `description` | `String` | | | `read` | `Boolean` | default `false` | | `actionUrl` | `String?` | | | `data` | `Json?` | Extra metadata (conversationId, connectionRequestId, etc.) | | `createdAt` | `DateTime` | default `now(` | | `user` | `User` | FK, relation | ### Payment | Field | Type | Notes | |---|---|---| | `id` | `String` | PK, default `uuid(` | | `subscriptionId` | `String?` | | | `userId` | `String` | | | `stripePaymentIntentId` | `String?` | unique, pi_xxx | | `stripeInvoiceId` | `String?` | unique, in_xxx | | `amount` | `Int` | cents | | `currency` | `String` | default `"usd"` | | `status` | `String` | "succeeded", "failed", "pending" | | `receiptUrl` | `String?` | | | `createdAt` | `DateTime` | default `now(` | | `subscription` | `AgentSubscription?` | FK, relation | | `user` | `User` | FK, relation | ### ProfileField | Field | Type | Notes | |---|---|---| | `id` | `String` | PK, default `uuid(` | | `sectionId` | `String` | | | `name` | `String` | e.g., "State", "Years in Business" | | `slug` | `String` | Unique within section: e.g., "state", "years_in_business" | | `fieldType` | `FieldType` | enum | | `description` | `String?` | Help text shown to user | | `placeholder` | `String?` | Placeholder text for input | | `defaultValue` | `String?` | Default value (JSON for complex types) | | `sortOrder` | `Int` | default `0` | | `isActive` | `Boolean` | default `true` | | `isRequired` | `Boolean` | default `false` | | `isSearchableOnly` | `Boolean` | default `false`, If true, shown in edit form & search, but hidden on public profile | | `validation` | `Json?` | { min, max, minLength, maxLength, pattern, etc. } | | `options` | `Json?` | [{ value: "...", label: "...", sortOrder: 0 }] | | `rangeConfig` | `Json?` | { min: 0, max: 100, step: 1 } | | `uiConfig` | `Json?` | { columns: 2, showInPreview: true, etc. } | | `createdAt` | `DateTime` | default `now(` | | `updatedAt` | `DateTime` | | | `section` | `ProfileSection` | FK, relation | | `fieldValues` | `AgentProfileFieldValue[]` | relation | ### ProfileSection | Field | Type | Notes | |---|---|---| | `id` | `String` | PK, default `uuid(` | | `name` | `String` | e.g., "Location", "Experience", "Specialization" | | `slug` | `String` | unique, URL-friendly identifier | | `description` | `String?` | | | `icon` | `String?` | Icon name or URL | | `sortOrder` | `Int` | default `0` | | `isActive` | `Boolean` | default `true` | | `isGlobal` | `Boolean` | default `false`, If true, applies to ALL agent types | | `isSystem` | `Boolean` | default `false`, If true, section cannot be deleted (system default) | | `isRepeatable` | `Boolean` | default `false`, If true, user can add multiple entries (e.g., certifications) | | `createdAt` | `DateTime` | default `now(` | | `updatedAt` | `DateTime` | | | `fields` | `ProfileField[]` | relation | | `agentTypeSections` | `AgentTypeSection[]` | relation | ### Session | Field | Type | Notes | |---|---|---| | `id` | `String` | PK, default `uuid(` | | `userId` | `String` | | | `token` | `String` | unique | | `refreshToken` | `String?` | unique | | `userAgent` | `String?` | | | `ipAddress` | `String?` | | | `expiresAt` | `DateTime` | | | `createdAt` | `DateTime` | default `now(` | | `user` | `User` | FK, relation | ### SubscriptionPlan | Field | Type | Notes | |---|---|---| | `id` | `String` | PK, default `uuid(` | | `name` | `String` | "Professional Annual" | | `description` | `String?` | | | `stripePriceId` | `String` | unique, Stripe Price ID (price_xxx) | | `amount` | `Int` | Amount in cents (49900) | | `currency` | `String` | default `"usd"` | | `interval` | `String` | default `"year"`, "month" | "year" | | `features` | `Json?` | ["Feature 1", "Feature 2"] | | `isActive` | `Boolean` | default `true` | | `sortOrder` | `Int` | default `0` | | `createdAt` | `DateTime` | default `now(` | | `updatedAt` | `DateTime` | | | `subscriptions` | `AgentSubscription[]` | relation | ### SupportChat | Field | Type | Notes | |---|---|---| | `id` | `String` | PK, default `uuid(` | | `userId` | `String` | | | `status` | `SupportChatStatus` | enum, default `OPEN` | | `lastMessageAt` | `DateTime?` | | | `lastMessageText` | `String?` | | | `userUnreadCount` | `Int` | default `0` | | `adminUnreadCount` | `Int` | default `0` | | `createdAt` | `DateTime` | default `now(` | | `updatedAt` | `DateTime` | | | `user` | `User` | FK, relation | | `messages` | `SupportMessage[]` | relation | ### SupportMessage | Field | Type | Notes | |---|---|---| | `id` | `String` | PK, default `uuid(` | | `chatId` | `String` | | | `senderId` | `String` | | | `senderRole` | `String` | "USER" or "ADMIN" | | `content` | `String` | | | `createdAt` | `DateTime` | default `now(` | | `chat` | `SupportChat` | FK, relation | ### Testimonial | Field | Type | Notes | |---|---|---| | `id` | `String` | PK, default `uuid(` | | `agentProfileId` | `String` | | | `rating` | `Int` | 1-5 | | `text` | `String` | | | `authorName` | `String` | | | `authorRole` | `String` | "Home Buyer", "Investor", etc. | | `isPublished` | `Boolean` | default `true` | | `createdAt` | `DateTime` | default `now(` | | `updatedAt` | `DateTime` | | | `agentProfile` | `AgentProfile` | FK, relation | ### User | Field | Type | Notes | |---|---|---| | `id` | `String` | PK, default `uuid(` | | `email` | `String` | unique | | `password` | `String?` | Null for social login users | | `role` | `UserRole` | enum, default `USER` | | `status` | `UserStatus` | enum, default `ACTIVE` | | `emailVerified` | `Boolean` | default `false` | | `emailVerifiedAt` | `DateTime?` | | | `avatar` | `String?` | Profile picture URL | | `authProvider` | `AuthProvider` | enum, default `LOCAL` | | `googleId` | `String?` | unique | | `facebookId` | `String?` | unique | | `twitterId` | `String?` | unique | | `appleId` | `String?` | unique | | `twoFactorEnabled` | `Boolean` | default `false` | | `twoFactorSecret` | `String?` | Encrypted TOTP secret | | `twoFactorBackupCodes` | `String?` | JSON array of hashed backup codes | | `twoFactorVerifiedAt` | `DateTime?` | When 2FA was enabled | | `notificationPreferences` | `Json?` | { email: {...}, push: {...} } | | `privacyPreferences` | `Json?` | { privacySettings: {...}, dataSettings: {...} } | | `fcmTokens` | `Json?` | [{ token, device, createdAt }] | | `createdAt` | `DateTime` | default `now(` | | `updatedAt` | `DateTime` | | | `lastLoginAt` | `DateTime?` | | | `isOnline` | `Boolean` | default `false` | | `lastSeenAt` | `DateTime?` | | | `userProfile` | `UserProfile?` | relation | | `agentProfile` | `AgentProfile?` | relation | | `sessions` | `Session[]` | relation | | `connectionRequests` | `ConnectionRequest[]` | relation | | `conversations` | `Conversation[]` | relation | | `messages` | `Message[]` | relation | | `notifications` | `Notification[]` | relation | | `supportChats` | `SupportChat[]` | relation | | `subscriptions` | `AgentSubscription[]` | relation | | `payments` | `Payment[]` | relation | | `reportsSubmitted` | `UserReport[]` | relation | | `reportsReceived` | `UserReport[]` | relation | | `verificationActions` | `VerificationHistory[]` | relation | | `auditLogs` | `AuditLog[]` | relation | ### UserProfile | Field | Type | Notes | |---|---|---| | `id` | `String` | PK, default `uuid(` | | `userId` | `String` | unique | | `firstName` | `String?` | | | `lastName` | `String?` | | | `phone` | `String?` | | | `avatar` | `String?` | | | `city` | `String?` | | | `state` | `String?` | | | `country` | `String?` | | | `createdAt` | `DateTime` | default `now(` | | `updatedAt` | `DateTime` | | | `user` | `User` | FK, relation | ### UserReport | Field | Type | Notes | |---|---|---| | `id` | `String` | PK, default `uuid(` | | `reporterId` | `String` | | | `reportedUserId` | `String` | | | `conversationId` | `String?` | | | `reason` | `String` | | | `description` | `String?` | | | `status` | `ReportStatus` | enum, default `PENDING` | | `adminNotes` | `String?` | | | `createdAt` | `DateTime` | default `now(` | | `updatedAt` | `DateTime` | | | `reporter` | `User` | FK, relation | | `reportedUser` | `User` | FK, relation | ### VerificationHistory | Field | Type | Notes | |---|---|---| | `id` | `String` | PK, default `uuid(` | | `agentProfileId` | `String` | | | `status` | `VerificationStatus` | enum | | `note` | `String?` | | | `adminId` | `String?` | | | `submittedData` | `Json?` | Snapshot of agent profile + documents at submission time | | `createdAt` | `DateTime` | default `now(` | | `agentProfile` | `AgentProfile` | FK, relation | | `admin` | `User?` | FK, relation | ## Enums - **AuthProvider**: `LOCAL`, `GOOGLE`, `FACEBOOK`, `TWITTER`, `APPLE` - **ConnectionStatus**: `PENDING`, `ACCEPTED`, `REJECTED` - **FieldType**: `TEXT // Single line text input`, `TEXTAREA // Multi-line text area`, `CHECKBOX // Single checkbox (Yes/No)`, `CHECKBOX_GROUP // Multiple checkboxes in grid (select multiple)`, `RADIO // Radio buttons (select one)`, `SELECT // Dropdown (select one)`, `MULTI_SELECT // Dropdown with multi-select`, `RANGE // Slider with min/max`, `NUMBER // Number input`, `DATE // Date picker`, `TAG_INPUT // Tag input (add custom tags)`, `FILE // File upload (documents, images)`, `REPEATER // Repeatable group of fields (e.g., certification + years)` - **MessageStatus**: `SENT`, `DELIVERED`, `READ` - **MessageType**: `TEXT`, `FILE`, `IMAGE`, `SYSTEM // For system messages like "connection accepted"` - **ReportStatus**: `PENDING`, `REVIEWED`, `RESOLVED`, `DISMISSED` - **SubscriptionStatus**: `ACTIVE`, `PAST_DUE`, `CANCELED`, `UNPAID`, `TRIALING`, `INCOMPLETE` - **SupportChatStatus**: `OPEN`, `CLOSED` - **UserRole**: `USER`, `AGENT`, `ADMIN`, `SUPER_ADMIN` - **UserStatus**: `ACTIVE`, `INACTIVE`, `SUSPENDED`, `PENDING_VERIFICATION` - **VerificationStatus**: `NONE // Agent hasn't uploaded documents`, `PENDING_REVIEW // Documents uploaded, awaiting admin review`, `APPROVED // Admin approved verification`, `REJECTED // Admin rejected verification`