fix(security): resolve audit findings — secrets, env contract, migrations
Removes hardcoded fallback secrets and makes a misconfigured deploy fail loudly instead of silently falling back to development defaults. - Remove insecure JWT fallback secrets (messages.module, configuration) - Remove the 'default-secret' fallback for the 2FA TOTP encryption key and allow a dedicated TWO_FACTOR_ENCRYPTION_KEY so rotating JWT_SECRET no longer locks out every 2FA user (see docs/2fa-key-rotation.md) - Require EMAIL_API_URL; drop the hardcoded vendor email endpoint - Drive WebSocket CORS from CORS_ORIGINS instead of origin:'*' - Load .env before any Nest module is imported (src/load-env.ts). Decorator arguments evaluate at import time, so the gateway previously froze its CORS config to the localhost fallback even when CORS_ORIGINS was set - Add boot-time env validation: missing required vars, weak JWT_SECRET, and inverted access/refresh token lifetimes now abort startup - Enable Redis TLS certificate verification - Require ADMIN_EMAIL/ADMIN_PASSWORD for the seed; remove the published default super-admin credentials and stop printing them - Add the initial Prisma migration and stop gitignoring prisma/migrations - Make .env.example an accurate configuration contract (admin bootstrap, REDIS_TLS, S3_ENDPOINT, 2FA key, Firebase path; drop the dead SMTP block) - Add handover documentation: architecture, ER model, sequence and data-flow diagrams, 2FA key rotation runbook Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
516
docs/data-model.md
Normal file
516
docs/data-model.md
Normal file
@@ -0,0 +1,516 @@
|
||||
# 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`
|
||||
Reference in New Issue
Block a user