Initial WinningHunter MVP

This commit is contained in:
2026-07-07 20:40:43 +03:00
commit 8dd43c57ca
57 changed files with 5295 additions and 0 deletions
+563
View File
@@ -0,0 +1,563 @@
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 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?
name String?
avatarUrl String?
role UserRole @default(USER)
locale String @default("tr")
currency String @default("EUR")
theme String @default("system")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
lastLoginAt DateTime?
sessions AuthSession[]
subscription Subscription?
usageCounters UsageCounter[]
savedFolders SavedFolder[]
savedAds SavedAd[]
filterPresets FilterPreset[]
trackedStores TrackedStore[]
exportJobs ExportJob[]
apiKeys ApiKey[]
@@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[]
@@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 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])
}