// 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) notificationCount Int @default(0) mutedUntil DateTime? suspendedUntil DateTime? disabledAt DateTime? 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[] dealEvents DealEvent[] notifications Notification[] dealSaves DealSave[] dealReports DealReport[] userBadges UserBadge[] auditEvents AuditEvent[] userNotes UserNote[] @relation("UserNotes") notesAuthored UserNote[] @relation("UserNotesAuthor") interestProfile UserInterestProfile? } model UserNote { id Int @id @default(autoincrement()) userId Int createdById Int note String createdAt DateTime @default(now()) user User @relation("UserNotes", fields: [userId], references: [id], onDelete: Cascade) createdBy User @relation("UserNotesAuthor", fields: [createdById], references: [id], onDelete: Cascade) @@index([userId, createdAt]) @@index([createdById]) } model Badge { id Int @id @default(autoincrement()) name String @unique iconUrl String? @db.VarChar(512) description String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt userBadges UserBadge[] } model UserBadge { userId Int badgeId Int earnedAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) badge Badge @relation(fields: [badgeId], references: [id], onDelete: Cascade) @@id([userId, badgeId]) @@index([userId, earnedAt]) @@index([badgeId]) } 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 } enum DiscountType { PERCENT AMOUNT } 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? isActive Boolean @default(true) 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 usageCount Int @default(0) createdAt DateTime @default(now()) 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? couponCode String? location String? discountType DiscountType? @default(AMOUNT) discountValue Float? maxNotifiedMilestone Int @default(0) barcodeId String? 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? analyticsTotal DealAnalyticsTotal? events DealEvent[] savedBy DealSave[] reports DealReport[] @@index([categoryId, createdAt]) @@index([userId, createdAt]) } model DealSave { userId Int dealId Int createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) deal Deal @relation(fields: [dealId], references: [id], onDelete: Cascade) @@id([userId, dealId]) @@index([userId, createdAt]) @@index([dealId]) } model DealReport { id Int @id @default(autoincrement()) dealId Int userId Int reason DealReportReason note String? status DealReportStatus @default(OPEN) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deal Deal @relation(fields: [dealId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([dealId, userId]) @@index([dealId, createdAt]) @@index([userId, createdAt]) @@index([status, 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([userId, 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 } enum DealEventType { IMPRESSION VIEW CLICK } enum DealReportReason { EXPIRED WRONG_PRICE MISLEADING SPAM OTHER } enum DealReportStatus { OPEN REVIEWED CLOSED } model DealAiReview { id Int @id @default(autoincrement()) dealId Int @unique deal Deal @relation(fields: [dealId], references: [id], onDelete: Cascade) bestCategoryId Int tags String[] @default([]) 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]) } model AuditEvent { id Int @id @default(autoincrement()) userId Int? action String ip String? userAgent String? meta Json? createdAt DateTime @default(now()) user User? @relation(fields: [userId], references: [id], onDelete: SetNull) @@index([userId, createdAt]) @@index([action, createdAt]) } model UserInterestProfile { userId Int @id categoryScores Json @default("{}") totalScore Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([updatedAt]) } model DealAnalyticsTotal { dealId Int @id impressions Int @default(0) views Int @default(0) clicks Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deal Deal @relation(fields: [dealId], references: [id], onDelete: Cascade) @@index([updatedAt]) } model DealEvent { id Int @id @default(autoincrement()) dealId Int type DealEventType userId Int? ip String? createdAt DateTime @default(now()) deal Deal @relation(fields: [dealId], references: [id], onDelete: Cascade) user User? @relation(fields: [userId], references: [id], onDelete: SetNull) @@index([dealId, type, createdAt]) @@index([userId, createdAt]) } model Notification { id Int @id @default(autoincrement()) userId Int message String type String @default("INFO") extras Json? createdAt DateTime @default(now()) readAt DateTime? user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId, createdAt]) }