feat: PL24 integration + EMEX on-demand parts loading
## Major Changes ### PL24 Integration (PartsLink24 API) - Add PL24 auth service with JWT token management - Add PL24 service for VIN decode and parts catalog - Support for VAG, Mercedes, BMW, Ford, Renault and more - Schema image download with base64 decoding - Image deduplication via SchemaPic table ### EMEX On-Demand Loading - Remove automatic parts scraping during VIN decode - Add fetchCategoryParts() for on-demand loading - Store category URLs in rawResponse for later fetching - Fix Chrome executable path for Puppeteer ### VIN Decode Flow - PL24 first, fallback to EMEX if not available - Both sources now use on-demand parts loading - Faster VIN lookup (no parts scraped upfront) ### Database Schema - Add SchemaPic model for image deduplication - Add schemaPicId to VehicleCategory - Add EMEX models for parallel scraping ### Frontend Improvements - Dark theme with Tailwind CSS - Improved UI components (card, button, input) - Better category and parts display - Schema image viewer Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,31 +7,482 @@ datasource db {
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
// ==================== USERS ====================
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
email String @unique
|
||||
name String?
|
||||
avatar String?
|
||||
role Role @default(USER)
|
||||
provider AuthProvider @default(EMAIL)
|
||||
providerId String?
|
||||
passwordHash String?
|
||||
isActive Boolean @default(true)
|
||||
|
||||
subscription UserSubscription?
|
||||
selectedBrands UserBrand[]
|
||||
vehicles Vehicle[] @relation("QueriedBy")
|
||||
id String @id @default(cuid())
|
||||
email String @unique
|
||||
name String?
|
||||
avatar String?
|
||||
provider AuthProvider @default(EMAIL)
|
||||
providerId String?
|
||||
passwordHash String?
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
role Role @default(USER)
|
||||
queryLogs QueryLog[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
selectedBrands UserBrand[]
|
||||
subscription UserSubscription?
|
||||
vehicles Vehicle[] @relation("QueriedBy")
|
||||
|
||||
@@index([email])
|
||||
@@index([role])
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
model Brand {
|
||||
id String @id @default(cuid())
|
||||
code String @unique
|
||||
name String
|
||||
logo String?
|
||||
isActive Boolean @default(true)
|
||||
sortOrder Int @default(0)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
userBrands UserBrand[]
|
||||
vehicles Vehicle[]
|
||||
|
||||
@@map("brands")
|
||||
}
|
||||
|
||||
model Plan {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
slug String @unique
|
||||
description String? @db.Text
|
||||
price Decimal @db.Decimal(10, 2)
|
||||
currency String @default("TRY")
|
||||
brandLimit Int
|
||||
hasFullAccess Boolean @default(false)
|
||||
features Json
|
||||
durationDays Int @default(30)
|
||||
sortOrder Int @default(0)
|
||||
isActive Boolean @default(true)
|
||||
isPopular Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
subscriptions UserSubscription[]
|
||||
|
||||
@@map("plans")
|
||||
}
|
||||
|
||||
model UserSubscription {
|
||||
id String @id @default(cuid())
|
||||
userId String @unique
|
||||
planId String
|
||||
status SubscriptionStatus @default(ACTIVE)
|
||||
currentPeriodStart DateTime @default(now())
|
||||
currentPeriodEnd DateTime
|
||||
cancelAtPeriodEnd Boolean @default(false)
|
||||
cancelledAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
payments Payment[]
|
||||
plan Plan @relation(fields: [planId], references: [id])
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([status])
|
||||
@@index([currentPeriodEnd])
|
||||
@@index([planId], map: "user_subscriptions_planId_fkey")
|
||||
@@map("user_subscriptions")
|
||||
}
|
||||
|
||||
model UserBrand {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
brandId String
|
||||
createdAt DateTime @default(now())
|
||||
brand Brand @relation(fields: [brandId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([userId, brandId])
|
||||
@@index([userId])
|
||||
@@index([brandId], map: "user_brands_brandId_fkey")
|
||||
@@map("user_brands")
|
||||
}
|
||||
|
||||
model Vehicle {
|
||||
id String @id @default(cuid())
|
||||
vin String @unique @db.VarChar(17)
|
||||
brandId String
|
||||
model String
|
||||
year Int
|
||||
series String?
|
||||
bodyType String?
|
||||
engineCode String?
|
||||
engineType String?
|
||||
engineVolume String?
|
||||
transmission String?
|
||||
driveType String?
|
||||
colorCode String?
|
||||
rawResponse Json
|
||||
queriedById String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
parts Part[]
|
||||
queryLogs QueryLog[]
|
||||
categories VehicleCategory[]
|
||||
brand Brand @relation(fields: [brandId], references: [id])
|
||||
queriedBy User? @relation("QueriedBy", fields: [queriedById], references: [id])
|
||||
|
||||
@@index([vin])
|
||||
@@index([brandId])
|
||||
@@index([queriedById], map: "vehicles_queriedById_fkey")
|
||||
@@map("vehicles")
|
||||
}
|
||||
|
||||
model Category {
|
||||
id String @id @default(cuid())
|
||||
code String @unique
|
||||
nameEn String
|
||||
nameTr String
|
||||
slug String @unique
|
||||
description String? @db.Text
|
||||
parentId String?
|
||||
iconName String?
|
||||
schemaImageUrl String?
|
||||
sortOrder Int @default(0)
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
parent Category? @relation("CategoryTree", fields: [parentId], references: [id])
|
||||
children Category[] @relation("CategoryTree")
|
||||
parts Part[]
|
||||
vehicleCategories VehicleCategory[]
|
||||
|
||||
@@index([parentId])
|
||||
@@map("categories")
|
||||
}
|
||||
|
||||
model VehicleCategory {
|
||||
id String @id @default(cuid())
|
||||
vehicleId String
|
||||
categoryId String
|
||||
partCount Int @default(0)
|
||||
schemaImageUrl String? @db.Text // Original URL from PL24
|
||||
schemaPicId String? // Reference to deduplicated schema image
|
||||
createdAt DateTime @default(now())
|
||||
category Category @relation(fields: [categoryId], references: [id], onDelete: Cascade)
|
||||
vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
|
||||
schemaPic SchemaPic? @relation(fields: [schemaPicId], references: [id])
|
||||
|
||||
@@unique([vehicleId, categoryId])
|
||||
@@index([vehicleId])
|
||||
@@index([categoryId])
|
||||
@@index([schemaPicId])
|
||||
@@map("vehicle_categories")
|
||||
}
|
||||
|
||||
model SchemaPic {
|
||||
id String @id @default(cuid())
|
||||
imageId String @unique // PL24 image ID (e.g., "194500200") for deduplication
|
||||
localPath String // Local path: /images/schemas/194500200.png
|
||||
originalUrl String? @db.Text // Original PL24 URL for reference
|
||||
fileSize Int? // File size in bytes
|
||||
width Int? // Image width
|
||||
height Int? // Image height
|
||||
createdAt DateTime @default(now())
|
||||
vehicleCategories VehicleCategory[]
|
||||
|
||||
@@index([imageId])
|
||||
@@map("schema_pics")
|
||||
}
|
||||
|
||||
model Part {
|
||||
id String @id @default(cuid())
|
||||
vehicleId String
|
||||
categoryId String
|
||||
oemCode String
|
||||
oemCodes Json?
|
||||
nameEn String
|
||||
nameTr String
|
||||
description String? @db.Text
|
||||
positionCode String?
|
||||
positionX Float?
|
||||
positionY Float?
|
||||
brandPrices Json
|
||||
imageUrl String?
|
||||
notes String? @db.Text
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
category Category @relation(fields: [categoryId], references: [id], onDelete: Cascade)
|
||||
vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([oemCode])
|
||||
@@index([vehicleId])
|
||||
@@index([categoryId])
|
||||
@@index([vehicleId, categoryId])
|
||||
@@map("parts")
|
||||
}
|
||||
|
||||
model Payment {
|
||||
id String @id @default(cuid())
|
||||
subscriptionId String
|
||||
amount Decimal @db.Decimal(10, 2)
|
||||
currency String @default("TRY")
|
||||
status PaymentStatus @default(PENDING)
|
||||
provider String @default("iyzico")
|
||||
providerTxId String? @unique
|
||||
providerData Json?
|
||||
invoiceNumber String?
|
||||
invoiceUrl String?
|
||||
failureReason String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
subscription UserSubscription @relation(fields: [subscriptionId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([status])
|
||||
@@index([providerTxId])
|
||||
@@index([subscriptionId], map: "payments_subscriptionId_fkey")
|
||||
@@map("payments")
|
||||
}
|
||||
|
||||
model QueryLog {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
vehicleId String?
|
||||
vin String @db.VarChar(17)
|
||||
responseTime Int?
|
||||
createdAt DateTime @default(now())
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
vehicle Vehicle? @relation(fields: [vehicleId], references: [id])
|
||||
|
||||
@@index([userId])
|
||||
@@index([vin])
|
||||
@@index([createdAt])
|
||||
@@index([userId, createdAt])
|
||||
@@index([vehicleId], map: "query_logs_vehicleId_fkey")
|
||||
@@map("query_logs")
|
||||
}
|
||||
|
||||
model EmexCatalog {
|
||||
id String @id @default(cuid())
|
||||
code String @unique
|
||||
name String
|
||||
brandCode String
|
||||
icon String?
|
||||
supportVinSearch Boolean @default(false)
|
||||
supportParameterIdentification Boolean @default(false)
|
||||
supportQuickGroups Boolean @default(false)
|
||||
isActive Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
categories EmexCategory[]
|
||||
parts EmexPart[]
|
||||
vehicles EmexVehicle[]
|
||||
|
||||
@@index([brandCode])
|
||||
@@map("emex_catalogs")
|
||||
}
|
||||
|
||||
model EmexVehicle {
|
||||
id String @id @default(cuid())
|
||||
catalogId String
|
||||
name String
|
||||
engine String?
|
||||
engineCode String?
|
||||
options Json?
|
||||
ssd String @db.VarChar(500)
|
||||
pathData String? @db.Text
|
||||
sourceUrl String? @db.Text
|
||||
uniqueKey String @db.VarChar(255)
|
||||
scrapeComplete Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
groupLinks EmexVehicleCategory[]
|
||||
partLinks EmexVehiclePart[]
|
||||
catalog EmexCatalog @relation(fields: [catalogId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([catalogId, uniqueKey])
|
||||
@@index([catalogId])
|
||||
@@index([ssd])
|
||||
@@map("emex_vehicles")
|
||||
}
|
||||
|
||||
model EmexCategory {
|
||||
id String @id @default(cuid())
|
||||
catalogId String
|
||||
groupId String
|
||||
parentId String?
|
||||
name String
|
||||
nameTr String
|
||||
level Int @default(1)
|
||||
sortOrder Int @default(0)
|
||||
hasParts Boolean @default(true)
|
||||
hasChildren Boolean @default(false)
|
||||
schemaImageUrl String? @db.Text
|
||||
localImagePath String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
catalog EmexCatalog @relation(fields: [catalogId], references: [id], onDelete: Cascade)
|
||||
parent EmexCategory? @relation("CategoryTree", fields: [parentId], references: [id])
|
||||
children EmexCategory[] @relation("CategoryTree")
|
||||
images EmexPartImage[]
|
||||
parts EmexPart[]
|
||||
vehicleLinks EmexVehicleCategory[]
|
||||
vehiclePartLinks EmexVehiclePart[]
|
||||
|
||||
@@unique([catalogId, groupId])
|
||||
@@index([catalogId])
|
||||
@@index([parentId])
|
||||
@@map("emex_categories")
|
||||
}
|
||||
|
||||
model EmexPart {
|
||||
id String @id @default(cuid())
|
||||
catalogId String
|
||||
partNumber String @db.VarChar(50)
|
||||
name String
|
||||
nameTr String?
|
||||
description String? @db.Text
|
||||
brand String?
|
||||
oemNumber String?
|
||||
unit String @default("PC")
|
||||
notes String? @db.Text
|
||||
attributes Json?
|
||||
categoryId String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
partNumbers EmexPartNumber[]
|
||||
catalog EmexCatalog @relation(fields: [catalogId], references: [id], onDelete: Cascade)
|
||||
category EmexCategory? @relation(fields: [categoryId], references: [id])
|
||||
vehicleLinks EmexVehiclePart[]
|
||||
|
||||
@@unique([catalogId, partNumber])
|
||||
@@index([catalogId])
|
||||
@@index([partNumber])
|
||||
@@index([categoryId])
|
||||
@@map("emex_parts")
|
||||
}
|
||||
|
||||
model EmexVehiclePart {
|
||||
id String @id @default(cuid())
|
||||
vehicleId String
|
||||
partId String
|
||||
categoryId String?
|
||||
quantity Float @default(1)
|
||||
position String?
|
||||
createdAt DateTime @default(now())
|
||||
category EmexCategory? @relation(fields: [categoryId], references: [id])
|
||||
part EmexPart @relation(fields: [partId], references: [id], onDelete: Cascade)
|
||||
vehicle EmexVehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([vehicleId, partId, categoryId])
|
||||
@@index([vehicleId])
|
||||
@@index([partId])
|
||||
@@index([categoryId])
|
||||
@@map("emex_vehicle_parts")
|
||||
}
|
||||
|
||||
model EmexVehicleCategory {
|
||||
id String @id @default(cuid())
|
||||
vehicleId String
|
||||
categoryId String
|
||||
isScraped Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
category EmexCategory @relation(fields: [categoryId], references: [id], onDelete: Cascade)
|
||||
vehicle EmexVehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([vehicleId, categoryId])
|
||||
@@index([vehicleId])
|
||||
@@index([categoryId])
|
||||
@@map("emex_vehicle_categories")
|
||||
}
|
||||
|
||||
model EmexPartNumber {
|
||||
id String @id @default(cuid())
|
||||
partId String
|
||||
number String @db.VarChar(50)
|
||||
numberType String @default("ALTERNATE")
|
||||
brand String?
|
||||
priority Int @default(0)
|
||||
createdAt DateTime @default(now())
|
||||
part EmexPart @relation(fields: [partId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([partId, number])
|
||||
@@index([partId])
|
||||
@@index([number])
|
||||
@@map("emex_part_numbers")
|
||||
}
|
||||
|
||||
model EmexPartImage {
|
||||
id String @id @default(cuid())
|
||||
categoryId String?
|
||||
imageType String @default("DIAGRAM")
|
||||
originalUrl String @db.Text
|
||||
localPath String?
|
||||
isPrimary Boolean @default(false)
|
||||
sortOrder Int @default(0)
|
||||
downloadedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
category EmexCategory? @relation(fields: [categoryId], references: [id])
|
||||
|
||||
@@index([categoryId])
|
||||
@@index([originalUrl(length: 255)])
|
||||
@@map("emex_part_images")
|
||||
}
|
||||
|
||||
model EmexScrapeQueue {
|
||||
id String @id @default(cuid())
|
||||
catalogId String
|
||||
taskType String
|
||||
vehicleId String?
|
||||
vehicleSsd String? @db.VarChar(500)
|
||||
vehicleName String?
|
||||
categoryId String?
|
||||
groupId String?
|
||||
status String @default("PENDING")
|
||||
workerId String?
|
||||
retryCount Int @default(0)
|
||||
maxRetries Int @default(3)
|
||||
errorMessage String? @db.Text
|
||||
priority Int @default(5)
|
||||
startedAt DateTime?
|
||||
completedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([status])
|
||||
@@index([catalogId])
|
||||
@@index([vehicleId])
|
||||
@@index([priority])
|
||||
@@map("emex_scrape_queue")
|
||||
}
|
||||
|
||||
model EmexScrapeSession {
|
||||
id String @id @default(cuid())
|
||||
catalogId String?
|
||||
brandCode String
|
||||
modelName String?
|
||||
status String @default("PENDING")
|
||||
totalItems Int @default(0)
|
||||
processedItems Int @default(0)
|
||||
failedItems Int @default(0)
|
||||
lastSsd String? @db.VarChar(500)
|
||||
lastError String? @db.Text
|
||||
stats Json?
|
||||
startedAt DateTime?
|
||||
completedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([status])
|
||||
@@index([brandCode])
|
||||
@@map("emex_scrape_sessions")
|
||||
}
|
||||
|
||||
model EmexCategoryTranslation {
|
||||
id String @id @default(cuid())
|
||||
nameEn String @unique
|
||||
nameTr String
|
||||
synonyms Json?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@map("emex_category_translations")
|
||||
}
|
||||
|
||||
enum Role {
|
||||
USER
|
||||
MODERATOR
|
||||
@@ -46,85 +497,6 @@ enum AuthProvider {
|
||||
FACEBOOK
|
||||
}
|
||||
|
||||
// ==================== BRANDS ====================
|
||||
model Brand {
|
||||
id String @id @default(cuid())
|
||||
code String @unique
|
||||
name String
|
||||
logo String?
|
||||
isActive Boolean @default(true)
|
||||
sortOrder Int @default(0)
|
||||
|
||||
vehicles Vehicle[]
|
||||
userBrands UserBrand[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@map("brands")
|
||||
}
|
||||
|
||||
// ==================== SUBSCRIPTIONS ====================
|
||||
model Plan {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
slug String @unique
|
||||
description String? @db.Text
|
||||
price Decimal @db.Decimal(10, 2)
|
||||
currency String @default("TRY")
|
||||
brandLimit Int
|
||||
hasFullAccess Boolean @default(false)
|
||||
features Json
|
||||
durationDays Int @default(30)
|
||||
sortOrder Int @default(0)
|
||||
isActive Boolean @default(true)
|
||||
isPopular Boolean @default(false)
|
||||
|
||||
subscriptions UserSubscription[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@map("plans")
|
||||
}
|
||||
|
||||
model UserSubscription {
|
||||
id String @id @default(cuid())
|
||||
userId String @unique
|
||||
planId String
|
||||
status SubscriptionStatus @default(ACTIVE)
|
||||
currentPeriodStart DateTime @default(now())
|
||||
currentPeriodEnd DateTime
|
||||
cancelAtPeriodEnd Boolean @default(false)
|
||||
cancelledAt DateTime?
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
plan Plan @relation(fields: [planId], references: [id])
|
||||
payments Payment[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([status])
|
||||
@@index([currentPeriodEnd])
|
||||
@@map("user_subscriptions")
|
||||
}
|
||||
|
||||
model UserBrand {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
brandId String
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
brand Brand @relation(fields: [brandId], references: [id], onDelete: Cascade)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@unique([userId, brandId])
|
||||
@@index([userId])
|
||||
@@map("user_brands")
|
||||
}
|
||||
|
||||
enum SubscriptionStatus {
|
||||
ACTIVE
|
||||
PAST_DUE
|
||||
@@ -134,137 +506,6 @@ enum SubscriptionStatus {
|
||||
PENDING
|
||||
}
|
||||
|
||||
// ==================== VEHICLES ====================
|
||||
model Vehicle {
|
||||
id String @id @default(cuid())
|
||||
vin String @unique @db.VarChar(17)
|
||||
brandId String
|
||||
model String
|
||||
year Int
|
||||
series String?
|
||||
bodyType String?
|
||||
engineCode String?
|
||||
engineType String?
|
||||
engineVolume String?
|
||||
transmission String?
|
||||
driveType String?
|
||||
colorCode String?
|
||||
rawResponse Json
|
||||
|
||||
queriedById String?
|
||||
queriedBy User? @relation("QueriedBy", fields: [queriedById], references: [id], onDelete: SetNull)
|
||||
brand Brand @relation(fields: [brandId], references: [id])
|
||||
|
||||
categories VehicleCategory[]
|
||||
parts Part[]
|
||||
queryLogs QueryLog[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([vin])
|
||||
@@index([brandId])
|
||||
@@map("vehicles")
|
||||
}
|
||||
|
||||
// ==================== CATEGORIES ====================
|
||||
model Category {
|
||||
id String @id @default(cuid())
|
||||
code String @unique
|
||||
nameEn String
|
||||
nameTr String
|
||||
slug String @unique
|
||||
description String? @db.Text
|
||||
parentId String?
|
||||
iconName String?
|
||||
schemaImageUrl String?
|
||||
sortOrder Int @default(0)
|
||||
isActive Boolean @default(true)
|
||||
|
||||
parent Category? @relation("CategoryTree", fields: [parentId], references: [id], onDelete: SetNull)
|
||||
children Category[] @relation("CategoryTree")
|
||||
|
||||
vehicleCategories VehicleCategory[]
|
||||
parts Part[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([parentId])
|
||||
@@map("categories")
|
||||
}
|
||||
|
||||
model VehicleCategory {
|
||||
id String @id @default(cuid())
|
||||
vehicleId String
|
||||
categoryId String
|
||||
partCount Int @default(0)
|
||||
|
||||
vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
|
||||
category Category @relation(fields: [categoryId], references: [id], onDelete: Cascade)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@unique([vehicleId, categoryId])
|
||||
@@index([vehicleId])
|
||||
@@index([categoryId])
|
||||
@@map("vehicle_categories")
|
||||
}
|
||||
|
||||
// ==================== PARTS ====================
|
||||
model Part {
|
||||
id String @id @default(cuid())
|
||||
vehicleId String
|
||||
categoryId String
|
||||
oemCode String
|
||||
oemCodes Json?
|
||||
nameEn String
|
||||
nameTr String
|
||||
description String? @db.Text
|
||||
positionCode String?
|
||||
positionX Float?
|
||||
positionY Float?
|
||||
brandPrices Json @default("[]")
|
||||
imageUrl String?
|
||||
notes String? @db.Text
|
||||
|
||||
vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
|
||||
category Category @relation(fields: [categoryId], references: [id], onDelete: Cascade)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([oemCode])
|
||||
@@index([vehicleId])
|
||||
@@index([categoryId])
|
||||
@@index([vehicleId, categoryId])
|
||||
@@map("parts")
|
||||
}
|
||||
|
||||
// ==================== PAYMENTS ====================
|
||||
model Payment {
|
||||
id String @id @default(cuid())
|
||||
subscriptionId String
|
||||
amount Decimal @db.Decimal(10, 2)
|
||||
currency String @default("TRY")
|
||||
status PaymentStatus @default(PENDING)
|
||||
provider String @default("iyzico")
|
||||
providerTxId String? @unique
|
||||
providerData Json?
|
||||
invoiceNumber String?
|
||||
invoiceUrl String?
|
||||
failureReason String?
|
||||
|
||||
subscription UserSubscription @relation(fields: [subscriptionId], references: [id], onDelete: Cascade)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([status])
|
||||
@@index([providerTxId])
|
||||
@@map("payments")
|
||||
}
|
||||
|
||||
enum PaymentStatus {
|
||||
PENDING
|
||||
PROCESSING
|
||||
@@ -273,23 +514,3 @@ enum PaymentStatus {
|
||||
REFUNDED
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
// ==================== QUERY LOGS ====================
|
||||
model QueryLog {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
vehicleId String?
|
||||
vin String @db.VarChar(17)
|
||||
responseTime Int?
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
vehicle Vehicle? @relation(fields: [vehicleId], references: [id], onDelete: SetNull)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([userId])
|
||||
@@index([vin])
|
||||
@@index([createdAt])
|
||||
@@index([userId, createdAt])
|
||||
@@map("query_logs")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user