669 lines
16 KiB
Plaintext
669 lines
16 KiB
Plaintext
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "postgresql"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
enum UserRole {
|
|
USER
|
|
ADMIN
|
|
}
|
|
|
|
enum PlanCode {
|
|
FREE
|
|
BASIC
|
|
STANDARD
|
|
PREMIUM
|
|
}
|
|
|
|
enum SubscriptionStatus {
|
|
ACTIVE
|
|
TRIALING
|
|
PAST_DUE
|
|
CANCELED
|
|
EXPIRED
|
|
}
|
|
|
|
enum UsagePeriod {
|
|
DAILY
|
|
MONTHLY
|
|
LIFETIME
|
|
}
|
|
|
|
enum CreditTransactionType {
|
|
ADMIN_CREDIT
|
|
ADMIN_DEBIT
|
|
PAYMENT
|
|
REFUND
|
|
USAGE
|
|
ADJUSTMENT
|
|
}
|
|
|
|
enum PaymentStatus {
|
|
PENDING
|
|
PAID
|
|
FAILED
|
|
REFUNDED
|
|
CANCELED
|
|
}
|
|
|
|
enum AdSource {
|
|
META
|
|
ADSPY
|
|
PINTEREST
|
|
TIKTOK
|
|
}
|
|
|
|
enum AdStatus {
|
|
ACTIVE
|
|
INACTIVE
|
|
UNKNOWN
|
|
}
|
|
|
|
enum MediaType {
|
|
VIDEO
|
|
IMAGE
|
|
CAROUSEL
|
|
DCT
|
|
UNKNOWN
|
|
}
|
|
|
|
enum AdScore {
|
|
ESTABLISHED
|
|
HAS_POTENTIAL
|
|
UNESTABLISHED
|
|
UNKNOWN
|
|
}
|
|
|
|
enum StorePlatform {
|
|
SHOPIFY
|
|
WOOCOMMERCE
|
|
CUSTOM
|
|
UNKNOWN
|
|
}
|
|
|
|
enum ExportStatus {
|
|
QUEUED
|
|
PROCESSING
|
|
COMPLETED
|
|
FAILED
|
|
}
|
|
|
|
enum IngestStatus {
|
|
QUEUED
|
|
RUNNING
|
|
COMPLETED
|
|
FAILED
|
|
}
|
|
|
|
model User {
|
|
id String @id @default(cuid())
|
|
email String @unique
|
|
passwordHash String?
|
|
googleId String? @unique
|
|
name String?
|
|
avatarUrl String?
|
|
role UserRole @default(USER)
|
|
locale String @default("tr")
|
|
currency String @default("EUR")
|
|
theme String @default("system")
|
|
creditBalance Int @default(0)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
lastLoginAt DateTime?
|
|
|
|
sessions AuthSession[]
|
|
subscription Subscription?
|
|
usageCounters UsageCounter[]
|
|
savedFolders SavedFolder[]
|
|
savedAds SavedAd[]
|
|
filterPresets FilterPreset[]
|
|
trackedStores TrackedStore[]
|
|
trackedBrands TrackedBrand[]
|
|
exportJobs ExportJob[]
|
|
apiKeys ApiKey[]
|
|
creditTransactions CreditTransaction[] @relation("CreditOwner")
|
|
creditActions CreditTransaction[] @relation("CreditActor")
|
|
payments Payment[] @relation("PaymentOwner")
|
|
manualPayments Payment[] @relation("PaymentActor")
|
|
adminAuditLogs AdminAuditLog[] @relation("AuditActor")
|
|
|
|
@@index([email])
|
|
}
|
|
|
|
model AuthSession {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
tokenHash String @unique
|
|
expiresAt DateTime
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId])
|
|
@@index([expiresAt])
|
|
}
|
|
|
|
model Plan {
|
|
id String @id @default(cuid())
|
|
code PlanCode @unique
|
|
name String
|
|
monthlyPriceEur Int
|
|
quarterlyPriceEur Int?
|
|
yearlyPriceEur Int?
|
|
description String?
|
|
features Json
|
|
isActive Boolean @default(true)
|
|
sortOrder Int @default(0)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
subscriptions Subscription[]
|
|
}
|
|
|
|
model Subscription {
|
|
id String @id @default(cuid())
|
|
userId String @unique
|
|
planId String
|
|
status SubscriptionStatus @default(ACTIVE)
|
|
billingInterval String @default("monthly")
|
|
currentPeriodStart DateTime?
|
|
currentPeriodEnd DateTime?
|
|
cancelAtPeriodEnd Boolean @default(false)
|
|
provider String?
|
|
providerCustomerId String?
|
|
providerSubscriptionId String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
plan Plan @relation(fields: [planId], references: [id])
|
|
|
|
@@index([planId])
|
|
@@index([status])
|
|
}
|
|
|
|
model UsageCounter {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
metric String
|
|
period UsagePeriod
|
|
periodKey String
|
|
used Int @default(0)
|
|
limit Int?
|
|
resetAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([userId, metric, periodKey])
|
|
@@index([userId, metric])
|
|
@@index([periodKey])
|
|
}
|
|
|
|
model BrandPage {
|
|
id String @id @default(cuid())
|
|
source AdSource @default(META)
|
|
externalPageId String?
|
|
name String
|
|
handle String?
|
|
logoUrl String?
|
|
pageUrl String?
|
|
websiteDomain String?
|
|
fbLikes Int?
|
|
igFollowers Int?
|
|
country String?
|
|
niche String?
|
|
storeId String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
store Store? @relation(fields: [storeId], references: [id], onDelete: SetNull)
|
|
ads Ad[]
|
|
trackedBy TrackedBrand[]
|
|
|
|
@@unique([source, externalPageId])
|
|
@@index([websiteDomain])
|
|
@@index([niche])
|
|
@@index([country])
|
|
}
|
|
|
|
model Ad {
|
|
id String @id @default(cuid())
|
|
source AdSource @default(META)
|
|
externalAdId String?
|
|
brandPageId String?
|
|
status AdStatus @default(UNKNOWN)
|
|
mediaType MediaType @default(UNKNOWN)
|
|
adScore AdScore @default(UNKNOWN)
|
|
primaryText String?
|
|
headline String?
|
|
description String?
|
|
ctaText String?
|
|
landingUrl String?
|
|
productUrl String?
|
|
language String?
|
|
countries String[] @default([])
|
|
niche String?
|
|
firstSeenAt DateTime?
|
|
lastSeenAt DateTime?
|
|
createdAtSource DateTime?
|
|
daysRunning Int?
|
|
estimatedReachMin Int?
|
|
estimatedReachMax Int?
|
|
estimatedSpendMin Int?
|
|
estimatedSpendMax Int?
|
|
estimatedCpm Float?
|
|
rank Float?
|
|
rankPercentile Float?
|
|
rankGrowth String?
|
|
raw Json?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
brandPage BrandPage? @relation(fields: [brandPageId], references: [id], onDelete: SetNull)
|
|
creatives AdCreative[]
|
|
metricsDaily AdMetricDaily[]
|
|
countryStats AdCountryStat[]
|
|
aiTags AITag[]
|
|
savedBy SavedAd[]
|
|
|
|
@@unique([source, externalAdId])
|
|
@@index([source, status])
|
|
@@index([mediaType])
|
|
@@index([niche])
|
|
@@index([language])
|
|
@@index([firstSeenAt])
|
|
@@index([lastSeenAt])
|
|
@@index([daysRunning])
|
|
@@index([rankPercentile])
|
|
}
|
|
|
|
model AdCreative {
|
|
id String @id @default(cuid())
|
|
adId String
|
|
type MediaType
|
|
url String
|
|
thumbnailUrl String?
|
|
width Int?
|
|
height Int?
|
|
durationSeconds Int?
|
|
position Int @default(0)
|
|
createdAt DateTime @default(now())
|
|
|
|
ad Ad @relation(fields: [adId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([adId])
|
|
@@index([type])
|
|
}
|
|
|
|
model AdMetricDaily {
|
|
id String @id @default(cuid())
|
|
adId String
|
|
date DateTime
|
|
reachMin Int?
|
|
reachMax Int?
|
|
spendMin Int?
|
|
spendMax Int?
|
|
cpm Float?
|
|
rank Float?
|
|
rankPercentile Float?
|
|
activeCountries Int?
|
|
createdAt DateTime @default(now())
|
|
|
|
ad Ad @relation(fields: [adId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([adId, date])
|
|
@@index([date])
|
|
}
|
|
|
|
model AdCountryStat {
|
|
id String @id @default(cuid())
|
|
adId String
|
|
countryCode String
|
|
reachMin Int?
|
|
reachMax Int?
|
|
spendMin Int?
|
|
spendMax Int?
|
|
sharePercent Float?
|
|
|
|
ad Ad @relation(fields: [adId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([adId, countryCode])
|
|
@@index([countryCode])
|
|
}
|
|
|
|
model AITag {
|
|
id String @id @default(cuid())
|
|
adId String
|
|
type String
|
|
value String
|
|
confidence Float?
|
|
explanation String?
|
|
model String?
|
|
promptVersion String?
|
|
createdAt DateTime @default(now())
|
|
|
|
ad Ad @relation(fields: [adId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([adId])
|
|
@@index([type, value])
|
|
}
|
|
|
|
model Store {
|
|
id String @id @default(cuid())
|
|
domain String @unique
|
|
name String
|
|
logoUrl String?
|
|
platform StorePlatform @default(UNKNOWN)
|
|
country String?
|
|
currency String?
|
|
language String?
|
|
niche String?
|
|
categoryPath String[] @default([])
|
|
shopUrl String?
|
|
createdAtSource DateTime?
|
|
dataSince DateTime?
|
|
shopifyTheme String?
|
|
productCount Int?
|
|
collectionCount Int?
|
|
trustpilotScore Float?
|
|
trustpilotReviewCount Int?
|
|
monthlyVisits Int?
|
|
monthlyVisitGrowth Float?
|
|
estRevenue30dMin Int?
|
|
estRevenue30dMax Int?
|
|
estRevenue24hMin Int?
|
|
estRevenue24hMax Int?
|
|
trafficCountries Json?
|
|
socials Json?
|
|
raw Json?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
brandPages BrandPage[]
|
|
products StoreProduct[]
|
|
snapshots StoreSnapshot[]
|
|
pixels StorePixel[]
|
|
apps StoreApp[]
|
|
trackedBy TrackedStore[]
|
|
|
|
@@index([niche])
|
|
@@index([country])
|
|
@@index([currency])
|
|
@@index([monthlyVisits])
|
|
@@index([monthlyVisitGrowth])
|
|
@@index([estRevenue30dMax])
|
|
}
|
|
|
|
model StoreSnapshot {
|
|
id String @id @default(cuid())
|
|
storeId String
|
|
date DateTime
|
|
monthlyVisits Int?
|
|
estRevenueMin Int?
|
|
estRevenueMax Int?
|
|
activeAdsCount Int?
|
|
productCount Int?
|
|
trafficCountries Json?
|
|
createdAt DateTime @default(now())
|
|
|
|
store Store @relation(fields: [storeId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([storeId, date])
|
|
@@index([date])
|
|
}
|
|
|
|
model StoreProduct {
|
|
id String @id @default(cuid())
|
|
storeId String
|
|
externalProductId String?
|
|
title String
|
|
handle String?
|
|
imageUrl String?
|
|
productUrl String?
|
|
price Float?
|
|
compareAtPrice Float?
|
|
currency String?
|
|
variantCount Int?
|
|
isBestSeller Boolean @default(false)
|
|
firstSeenAt DateTime?
|
|
raw Json?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
store Store @relation(fields: [storeId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([storeId])
|
|
@@index([isBestSeller])
|
|
@@index([price])
|
|
}
|
|
|
|
model StorePixel {
|
|
id String @id @default(cuid())
|
|
storeId String
|
|
type String
|
|
value String?
|
|
|
|
store Store @relation(fields: [storeId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([storeId, type, value])
|
|
@@index([type])
|
|
}
|
|
|
|
model StoreApp {
|
|
id String @id @default(cuid())
|
|
storeId String
|
|
name String
|
|
category String?
|
|
detectedAt DateTime @default(now())
|
|
|
|
store Store @relation(fields: [storeId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([storeId, name])
|
|
@@index([name])
|
|
}
|
|
|
|
model SavedFolder {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
name String
|
|
color String?
|
|
sortOrder Int @default(0)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
savedAds SavedAd[]
|
|
|
|
@@unique([userId, name])
|
|
@@index([userId])
|
|
}
|
|
|
|
model SavedAd {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
adId String
|
|
folderId String?
|
|
note String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
ad Ad @relation(fields: [adId], references: [id], onDelete: Cascade)
|
|
folder SavedFolder? @relation(fields: [folderId], references: [id], onDelete: SetNull)
|
|
|
|
@@unique([userId, adId])
|
|
@@index([userId, folderId])
|
|
@@index([adId])
|
|
}
|
|
|
|
model TrackedStore {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
storeId String
|
|
notes String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
store Store @relation(fields: [storeId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([userId, storeId])
|
|
@@index([userId])
|
|
@@index([storeId])
|
|
}
|
|
|
|
model TrackedBrand {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
brandPageId String
|
|
notes String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
brandPage BrandPage @relation(fields: [brandPageId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([userId, brandPageId])
|
|
@@index([userId])
|
|
@@index([brandPageId])
|
|
}
|
|
|
|
model FilterPreset {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
module String
|
|
name String
|
|
filters Json
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([userId, module, name])
|
|
@@index([userId, module])
|
|
}
|
|
|
|
model ExportJob {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
module String
|
|
filters Json?
|
|
status ExportStatus @default(QUEUED)
|
|
fileUrl String?
|
|
errorMessage String?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId, status])
|
|
@@index([module])
|
|
}
|
|
|
|
model IngestJob {
|
|
id String @id @default(cuid())
|
|
source String
|
|
type String
|
|
status IngestStatus @default(QUEUED)
|
|
startedAt DateTime?
|
|
finishedAt DateTime?
|
|
recordsImported Int @default(0)
|
|
recordsFailed Int @default(0)
|
|
errorMessage String?
|
|
metadata Json?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([source, status])
|
|
@@index([createdAt])
|
|
}
|
|
|
|
model ApiKey {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
name String
|
|
keyHash String @unique
|
|
prefix String
|
|
lastUsedAt DateTime?
|
|
revokedAt DateTime?
|
|
monthlyCreditLimit Int?
|
|
usedCreditsThisMonth Int @default(0)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId])
|
|
@@index([prefix])
|
|
}
|
|
|
|
model CreditTransaction {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
actorId String?
|
|
type CreditTransactionType
|
|
amount Int
|
|
balanceAfter Int
|
|
reason String
|
|
metadata Json?
|
|
createdAt DateTime @default(now())
|
|
|
|
user User @relation("CreditOwner", fields: [userId], references: [id], onDelete: Cascade)
|
|
actor User? @relation("CreditActor", fields: [actorId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([userId, createdAt])
|
|
@@index([actorId, createdAt])
|
|
@@index([type, createdAt])
|
|
}
|
|
|
|
model Payment {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
actorId String?
|
|
amountCents Int
|
|
currency String @default("EUR")
|
|
status PaymentStatus @default(PAID)
|
|
provider String @default("MANUAL")
|
|
externalId String? @unique
|
|
description String?
|
|
creditGranted Int @default(0)
|
|
paidAt DateTime?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
user User @relation("PaymentOwner", fields: [userId], references: [id], onDelete: Cascade)
|
|
actor User? @relation("PaymentActor", fields: [actorId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([userId, createdAt])
|
|
@@index([status, createdAt])
|
|
@@index([provider, createdAt])
|
|
}
|
|
|
|
model AdminAuditLog {
|
|
id String @id @default(cuid())
|
|
actorId String?
|
|
actorEmail String
|
|
action String
|
|
targetType String
|
|
targetId String?
|
|
summary String
|
|
details Json?
|
|
ipAddress String?
|
|
userAgent String?
|
|
createdAt DateTime @default(now())
|
|
|
|
actor User? @relation("AuditActor", fields: [actorId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([actorId, createdAt])
|
|
@@index([action, createdAt])
|
|
@@index([targetType, targetId])
|
|
@@index([createdAt])
|
|
}
|