HotTRDealsBackend/prisma/schema.prisma
2026-02-09 21:47:55 +00:00

523 lines
13 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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])
}