// This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } enum UserRole { USER MOD ADMIN } model User { id Int @id @default(autoincrement()) username String @unique email String @unique passwordHash String avatarUrl String? @db.VarChar(512) role UserRole @default(USER) createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt Deal Deal[] votes DealVote[] comments Comment[] companies Seller[] domains SellerDomain[] dealVoteHistory DealVoteHistory[] dealNotices DealNotice[] @relation("UserDealNotices") refreshTokens RefreshToken[] // <-- bunu ekle commentLikes CommentLike[] } model RefreshToken { id String @id @default(cuid()) // token kaydı id userId Int user User @relation(fields: [userId], references: [id], onDelete: Cascade) tokenHash String @unique // refresh token hash (örn sha256) familyId String // rotation zinciri için aynı aile jti String // token id (JWT jti / random) expiresAt DateTime createdAt DateTime @default(now()) revokedAt DateTime? replacedById String? @unique replacedBy RefreshToken? @relation("TokenRotation", fields: [replacedById], references: [id]) replaces RefreshToken? @relation("TokenRotation") createdByIp String? userAgent String? @@index([userId]) @@index([familyId]) @@index([expiresAt]) } enum DealStatus { PENDING ACTIVE EXPIRED REJECTED } enum SaleType { ONLINE OFFLINE CODE } enum AffiliateType { AFFILIATE NON_AFFILIATE USER_AFFILIATE } model SellerDomain { id Int @id @default(autoincrement()) domain String @unique sellerId Int seller Seller @relation(fields: [sellerId], references: [id]) createdAt DateTime @default(now()) createdById Int createdBy User @relation(fields: [createdById], references: [id]) } model Seller { id Int @id @default(autoincrement()) name String @unique url String @default("") sellerLogo String @default("") isActive Boolean @default(true) createdAt DateTime @default(now()) createdById Int deals Deal[] createdBy User @relation(fields: [createdById], references: [id]) domains SellerDomain[] } /** * NEW: Category (self-parent tree) * NOTE: You want Deal.categoryId default 0 -> you MUST create a Category row with id=0 ("Undefined") */ model Category { id Int @id @default(autoincrement()) name String slug String @unique description String @default("") parentId Int? parent Category? @relation("CategoryParent", fields: [parentId], references: [id]) children Category[] @relation("CategoryParent") deals Deal[] @@index([parentId]) } /** * NEW: Tag (canonical) */ model Tag { id Int @id @default(autoincrement()) slug String @unique name String dealTags DealTag[] } /** * NEW: Join table Deal <-> Tag (many-to-many) */ model DealTag { dealId Int tagId Int deal Deal @relation(fields: [dealId], references: [id], onDelete: Cascade) tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) @@id([dealId, tagId]) @@index([tagId, dealId]) // tag -> deals hızlı @@index([dealId]) // deal -> tags hızlı } model Deal { id Int @id @default(autoincrement()) title String description String? url String? price Float? originalPrice Float? shippingPrice Float? percentOff Float? userId Int score Int @default(0) commentCount Int @default(0) status DealStatus @default(PENDING) saletype SaleType @default(ONLINE) affiliateType AffiliateType @default(NON_AFFILIATE) sellerId Int? customSeller String? seller Seller? @relation(fields: [sellerId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id]) votes DealVote[] voteHistory DealVoteHistory[] notices DealNotice[] @relation("DealNotices") comments Comment[] images DealImage[] // NEW: category (single) categoryId Int @default(0) category Category @relation(fields: [categoryId], references: [id]) // NEW: tags (multiple, optional) dealTags DealTag[] aiReview DealAiReview? @@index([categoryId, createdAt]) @@index([userId, createdAt]) } enum DealNoticeSeverity { INFO WARNING DANGER SUCCESS } model DealNotice { id Int @id @default(autoincrement()) dealId Int title String body String? severity DealNoticeSeverity @default(INFO) isActive Boolean @default(true) createdBy Int createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deal Deal @relation("DealNotices", fields: [dealId], references: [id], onDelete: Cascade) creator User @relation("UserDealNotices", fields: [createdBy], references: [id]) @@index([dealId, isActive, createdAt]) @@index([createdBy]) } model DealImage { id Int @id @default(autoincrement()) imageUrl String @db.VarChar(512) order Int @default(0) createdAt DateTime @default(now()) dealId Int deal Deal @relation(fields: [dealId], references: [id], onDelete: Cascade) } model DealVote { id Int @id @default(autoincrement()) dealId Int userId Int voteType Int @default(0) // -1,0,1 createdAt DateTime @default(now()) lastVotedAt DateTime @default(now()) // her vote değişiminde set edeceğiz deal Deal @relation(fields: [dealId], references: [id]) user User @relation(fields: [userId], references: [id]) @@unique([dealId, userId]) @@index([dealId]) } model DealVoteHistory { id Int @id @default(autoincrement()) dealId Int userId Int voteType Int createdAt DateTime @default(now()) deal Deal @relation(fields: [dealId], references: [id]) user User @relation(fields: [userId], references: [id]) @@index([dealId]) @@index([userId]) @@index([createdAt]) } model Comment { id Int @id @default(autoincrement()) text String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt userId Int dealId Int parentId Int? likeCount Int @default(0) deletedAt DateTime? user User @relation(fields: [userId], references: [id]) deal Deal @relation(fields: [dealId], references: [id]) parent Comment? @relation("CommentReplies", fields: [parentId], references: [id], onDelete: SetNull) replies Comment[] @relation("CommentReplies") likes CommentLike[] @@index([dealId, createdAt]) @@index([parentId, createdAt]) @@index([dealId, parentId, createdAt]) @@index([deletedAt]) } model CommentLike { id Int @id @default(autoincrement()) commentId Int userId Int createdAt DateTime @default(now()) comment Comment @relation(fields: [commentId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([commentId, userId]) @@index([commentId]) @@index([userId]) } enum DealAiIssueType { NONE PROFANITY PHONE_NUMBER PERSONAL_DATA SPAM OTHER } model DealAiReview { id Int @id @default(autoincrement()) dealId Int @unique deal Deal @relation(fields: [dealId], references: [id], onDelete: Cascade) bestCategoryId Int needsReview Boolean @default(false) hasIssue Boolean @default(false) issueType DealAiIssueType @default(NONE) issueReason String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([needsReview, hasIssue, updatedAt]) }