generator client { provider = "prisma-client-js" } datasource db { provider = "mysql" url = env("DATABASE_URL") } // ============================================ // USERS // ============================================ model User { id String @id @default(uuid()) email String @unique @db.VarChar(255) password String @db.VarChar(255) name String @db.VarChar(100) role UserRole @default(USER_FREE) isEmailVerified Boolean @default(false) @map("is_email_verified") emailVerifyToken String? @map("email_verify_token") @db.VarChar(255) refreshToken String? @map("refresh_token") @db.Text status UserStatus @default(ACTIVE) lastLoginAt DateTime? @map("last_login_at") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") licenses License[] subscriptions Subscription[] scans Scan[] payments Payment[] usageLogs UsageLog[] @@index([email]) @@index([role]) @@map("users") } enum UserRole { USER_FREE USER_PRO ADMIN } enum UserStatus { ACTIVE SUSPENDED DELETED } // ============================================ // LICENSES // ============================================ model License { id String @id @default(uuid()) key String @unique @db.VarChar(255) userId String @map("user_id") plan LicensePlan @default(FREE) maxScan Int @default(50) @map("max_scan") expiredAt DateTime? @map("expired_at") status LicenseStatus @default(ACTIVE) lastCheckAt DateTime? @map("last_check_at") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([key]) @@index([userId]) @@index([status]) @@map("licenses") } enum LicensePlan { FREE PRO_MONTHLY PRO_YEARLY } enum LicenseStatus { ACTIVE EXPIRED REVOKED } // ============================================ // SUBSCRIPTIONS // ============================================ model Subscription { id String @id @default(uuid()) userId String @map("user_id") plan LicensePlan status SubscriptionStatus @default(PENDING) startDate DateTime? @map("start_date") endDate DateTime? @map("end_date") autoRenew Boolean @default(true) @map("auto_renew") dokuInvoiceNo String? @map("doku_invoice_no") @db.VarChar(255) createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") user User @relation(fields: [userId], references: [id], onDelete: Cascade) payments Payment[] @@index([userId]) @@index([status]) @@map("subscriptions") } enum SubscriptionStatus { PENDING ACTIVE CANCELLED EXPIRED } // ============================================ // SCANS // ============================================ model Scan { id String @id @default(uuid()) userId String @map("user_id") marketplace Marketplace keyword String? @db.VarChar(255) category String? @db.VarChar(255) totalProducts Int @default(0) @map("total_products") minPrice Float? @map("min_price") maxPrice Float? @map("max_price") avgPrice Float? @map("avg_price") medianPrice Float? @map("median_price") stdDeviation Float? @map("std_deviation") status ScanStatus @default(PROCESSING) aiRecommendation Json? @map("ai_recommendation") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") user User @relation(fields: [userId], references: [id], onDelete: Cascade) items ScanItem[] @@index([userId]) @@index([marketplace]) @@index([createdAt]) @@map("scans") } enum Marketplace { SHOPEE TOKOPEDIA TIKTOK_SHOP } enum ScanStatus { PROCESSING COMPLETED FAILED } // ============================================ // SCAN ITEMS // ============================================ model ScanItem { id String @id @default(uuid()) scanId String @map("scan_id") productName String @map("product_name") @db.VarChar(500) priceRaw String? @map("price_raw") @db.VarChar(100) priceNumeric Float @map("price_numeric") rating Float? soldCount Int? @map("sold_count") storeName String? @map("store_name") @db.VarChar(255) productUrl String? @map("product_url") @db.Text isOutlier Boolean @default(false) @map("is_outlier") cluster Int? createdAt DateTime @default(now()) @map("created_at") scan Scan @relation(fields: [scanId], references: [id], onDelete: Cascade) @@index([scanId]) @@index([priceNumeric]) @@map("scan_items") } // ============================================ // PAYMENTS // ============================================ model Payment { id String @id @default(uuid()) userId String @map("user_id") subscriptionId String? @map("subscription_id") invoiceNumber String @unique @map("invoice_number") @db.VarChar(100) amount Float currency String @default("IDR") @db.VarChar(3) status PaymentStatus @default(PENDING) paymentMethod String? @map("payment_method") @db.VarChar(50) dokuPaymentUrl String? @map("doku_payment_url") @db.Text dokuTokenId String? @map("doku_token_id") @db.VarChar(255) paidAt DateTime? @map("paid_at") expiredAt DateTime? @map("expired_at") rawResponse Json? @map("raw_response") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") user User @relation(fields: [userId], references: [id], onDelete: Cascade) subscription Subscription? @relation(fields: [subscriptionId], references: [id], onDelete: SetNull) @@index([userId]) @@index([invoiceNumber]) @@index([status]) @@map("payments") } enum PaymentStatus { PENDING PAID FAILED EXPIRED REFUNDED } // ============================================ // USAGE LOGS // ============================================ model UsageLog { id String @id @default(uuid()) userId String @map("user_id") action String @db.VarChar(100) details Json? ipAddress String? @map("ip_address") @db.VarChar(45) userAgent String? @map("user_agent") @db.Text createdAt DateTime @default(now()) @map("created_at") user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@index([action]) @@index([createdAt]) @@map("usage_logs") } // ============================================ // API LOGS // ============================================ model ApiLog { id String @id @default(uuid()) method String @db.VarChar(10) path String @db.VarChar(500) statusCode Int @map("status_code") responseTime Int @map("response_time") ipAddress String? @map("ip_address") @db.VarChar(45) userAgent String? @map("user_agent") @db.Text userId String? @map("user_id") errorMessage String? @map("error_message") @db.Text createdAt DateTime @default(now()) @map("created_at") @@index([path]) @@index([statusCode]) @@index([createdAt]) @@map("api_logs") }