generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" } enum UserRole { VENDOR SUPPLIER ADMIN } model User { id String @id @default(cuid()) name String? email String? @unique emailVerified DateTime? image String? password String? role UserRole @default(VENDOR) activeStoreId String? // Track which store is currently active // MFA (Multi-Factor Authentication) mfaEnabled Boolean @default(false) mfaSecret String? // Encrypted TOTP secret chatPublicKey String? // Relations sentMessages ChatMessage[] @relation("SentMessages") receivedMessages ChatMessage[] @relation("ReceivedMessages") accounts Account[] sessions Session[] stores Store[] // Changed from singular to support multiple stores supplierProfile SupplierProfile? notifications Notification[] gmailIntegration GmailIntegration? emailLogs EmailLog[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } // Auth.js / NextAuth Prisma adapter models (required for OAuth e.g. Google) model Account { id String @id @default(cuid()) userId String type String provider String providerAccountId String refresh_token String? @db.Text access_token String? @db.Text expires_at Int? token_type String? scope String? id_token String? @db.Text session_state String? user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([provider, providerAccountId]) } model Session { id String @id @default(cuid()) sessionToken String @unique userId String expires DateTime user User @relation(fields: [userId], references: [id], onDelete: Cascade) } model VerificationToken { identifier String token String expires DateTime @@unique([identifier, token]) } model Store { id String @id @default(cuid()) userId String // Removed @unique to allow multiple stores per user user User @relation(fields: [userId], references: [id]) businessName String email String timezone String country String // Shopify Credentials shopifyStoreName String shopifyDomain String shopifyApiKey String shopifyApiSecret String shopifyAccessToken String onboardingComplete Boolean @default(false) isActive Boolean @default(true) // Track if this is the active store // Relations connections Connection[] cachedProducts CachedProduct[] cachedOrders CachedOrder[] cachedCustomers CachedCustomer[] syncMetadata SyncMetadata[] invitations Invitation[] productSyncs ProductSync[] knowledgeDocuments KnowledgeDocument[] faqItems FaqItem[] knowledgeChunks KnowledgeChunk[] emailLogs EmailLog[] flaggedEmails FlaggedEmail[] templates Template[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([userId]) // Add index for performance } model CachedProduct { id String @id @default(cuid()) shopifyProductId String storeId String title String vendor String? productType String? status String? variants Json // Store variants as JSON images Json // Store images as JSON createdAt DateTime @default(now()) updatedAt DateTime @updatedAt lastSyncedAt DateTime @default(now()) store Store @relation(fields: [storeId], references: [id], onDelete: Cascade) @@unique([shopifyProductId, storeId]) @@index([storeId]) } model CachedOrder { id String @id @default(cuid()) shopifyOrderId String storeId String orderNumber String email String? totalPrice String currency String? financialStatus String? fulfillmentStatus String? lineItems Json // Store line items as JSON customer Json? // Store customer info as JSON createdAt DateTime @default(now()) updatedAt DateTime @updatedAt lastSyncedAt DateTime @default(now()) shopifyCreatedAt DateTime? store Store @relation(fields: [storeId], references: [id], onDelete: Cascade) @@unique([shopifyOrderId, storeId]) @@index([storeId]) @@index([shopifyCreatedAt]) } model CachedCustomer { id String @id @default(cuid()) shopifyCustomerId String storeId String email String? firstName String? lastName String? ordersCount Int @default(0) totalSpent String? phone String? tags String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt lastSyncedAt DateTime @default(now()) shopifyCreatedAt DateTime? store Store @relation(fields: [storeId], references: [id], onDelete: Cascade) @@unique([shopifyCustomerId, storeId]) @@index([storeId]) } model SyncMetadata { id String @id @default(cuid()) storeId String resourceType String // "products", "orders", "customers" lastSyncAt DateTime @default(now()) syncStatus String @default("success") // "success", "failed", "in_progress" recordCount Int @default(0) errorMessage String? store Store @relation(fields: [storeId], references: [id], onDelete: Cascade) @@unique([storeId, resourceType]) @@index([storeId]) } model SupplierProfile { id String @id @default(cuid()) userId String @unique user User @relation(fields: [userId], references: [id]) companyName String description String? // Relations connections Connection[] products Product[] invitations Invitation[] productSyncs ProductSync[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Connection { id String @id @default(cuid()) storeId String store Store @relation(fields: [storeId], references: [id]) supplierId String supplier SupplierProfile @relation(fields: [supplierId], references: [id]) status ConnectionStatus @default(PENDING) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([storeId, supplierId]) } model Invitation { id String @id @default(cuid()) token String @unique storeId String store Store @relation(fields: [storeId], references: [id]) supplierId String supplier SupplierProfile @relation(fields: [supplierId], references: [id]) email String // Supplier email to send invitation to expiresAt DateTime used Boolean @default(false) createdAt DateTime @default(now()) usedAt DateTime? @@index([token]) @@index([supplierId]) @@index([storeId]) } enum ConnectionStatus { PENDING CONNECTED REJECTED } model Product { id String @id @default(cuid()) supplierId String supplier SupplierProfile @relation(fields: [supplierId], references: [id]) title String description String? price Float sku String? imageUrl String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } enum ProductSyncStatus { PENDING ACCEPTED REJECTED } model ProductSync { id String @id @default(cuid()) storeId String store Store @relation(fields: [storeId], references: [id]) supplierId String supplier SupplierProfile @relation(fields: [supplierId], references: [id]) shopifyProductId String // Shopify product ID from vendor's store shopifyProductTitle String shopifyProductData Json? // Store full product data for reference status ProductSyncStatus @default(PENDING) // Metadata requestedBy String // userId of vendor who requested acceptedAt DateTime? rejectedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([storeId, supplierId, shopifyProductId]) @@index([storeId]) @@index([supplierId]) @@index([status]) @@index([shopifyProductId]) } model GmailIntegration { id String @id @default(cuid()) userId String @unique user User @relation(fields: [userId], references: [id], onDelete: Cascade) accessToken String @db.Text refreshToken String? @db.Text expiresAt DateTime email String // Gmail email address scope String // OAuth scopes granted createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([userId]) } enum NotificationType { INVITATION_ACCEPTED INVITATION_RECEIVED CONNECTION_STATUS_CHANGED ORDER_RECEIVED PRODUCT_UPDATED PRODUCT_SYNC_REQUESTED PRODUCT_SYNC_ACCEPTED PRODUCT_SYNC_REJECTED } model Notification { id String @id @default(cuid()) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) type NotificationType title String message String read Boolean @default(false) readAt DateTime? // Optional metadata for linking to related entities metadata Json? // Store related IDs (connectionId, invitationId, etc.) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([userId]) @@index([userId, read]) @@index([createdAt]) } model EmailLog { id String @id @default(cuid()) userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) storeId String? store Store? @relation(fields: [storeId], references: [id]) to String subject String bodyPreview String? // Whether this email was triggered automatically by the system isAutomated Boolean @default(false) trigger String? // e.g. "GMAIL_AUTO_REPLY", "TICKET_CREATED" status String // "SENT" | "FAILED" providerMessageId String? errorMessage String? sentAt DateTime @default(now()) createdAt DateTime @default(now()) @@index([storeId, sentAt]) @@index([userId, sentAt]) } enum FlaggedEmailPriority { LOW MEDIUM HIGH } model FlaggedEmail { id String @id @default(cuid()) storeId String store Store @relation(fields: [storeId], references: [id], onDelete: Cascade) subject String @db.Text from String // Sender email reason String @db.Text priority FlaggedEmailPriority @default(MEDIUM) messageId String? // Gmail message id for future sync flaggedAt DateTime @default(now()) createdAt DateTime @default(now()) @@index([storeId]) @@index([storeId, flaggedAt]) } enum KnowledgeDocumentStatus { UPLOADED PROCESSING READY FAILED } enum KnowledgeChunkSourceType { DOCUMENT FAQ } model KnowledgeDocument { id String @id @default(cuid()) storeId String store Store @relation(fields: [storeId], references: [id], onDelete: Cascade) title String fileName String mimeType String sizeBytes Int status KnowledgeDocumentStatus @default(UPLOADED) errorMessage String? // Where the raw file is stored (local path or object storage key) storagePath String // Relations chunks KnowledgeChunk[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([storeId]) } model FaqItem { id String @id @default(cuid()) storeId String store Store @relation(fields: [storeId], references: [id], onDelete: Cascade) question String answer String tags Json? // Relations chunks KnowledgeChunk[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([storeId]) } model KnowledgeChunk { id String @id @default(cuid()) storeId String store Store @relation(fields: [storeId], references: [id], onDelete: Cascade) documentId String? document KnowledgeDocument? @relation(fields: [documentId], references: [id]) faqItemId String? faqItem FaqItem? @relation(fields: [faqItemId], references: [id]) sourceType KnowledgeChunkSourceType content String metadata Json? // pgvector-backed column; accessed via raw SQL for similarity search embedding Unsupported("vector") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([storeId]) @@index([documentId]) @@index([faqItemId]) } model Template { id String @id @default(cuid()) storeId String store Store @relation(fields: [storeId], references: [id], onDelete: Cascade) name String intent String mood String description String? body String // Template body (HTML or plain text with {{variables}}) variables Json? // Optional metadata describing expected variables isHtml Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([storeId]) } model ChatMessage { id String @id @default(cuid()) senderId String receiverId String encryptedContent String @db.Text encryptedKeyForSender String @db.Text encryptedKeyForReceiver String @db.Text iv String sender User @relation("SentMessages", fields: [senderId], references: [id]) receiver User @relation("ReceivedMessages", fields: [receiverId], references: [id]) createdAt DateTime @default(now()) @@index([senderId, receiverId]) @@index([receiverId, senderId]) }