Add page-view analytics, product counters, SSL expiry, and business renewal.

Auto-count storefront page views with 6-month event retention and lifetime counters, track product visit/cart/sale stats, persist SSL cert expiry with daily probes, and support owner titles plus annual contract renewal.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-26 10:13:06 +03:30
co-authored by Cursor
parent 6cd0b009ef
commit 92601ae729
39 changed files with 1013 additions and 198 deletions
+57 -12
View File
@@ -23,6 +23,8 @@ model User {
firstNameEn String? @map("first_name_en") @db.VarChar(100)
lastNameEn String? @map("last_name_en") @db.VarChar(100)
oldId BigInt? @map("old_id")
titleId BigInt? @map("title_id")
title UserTitle? @relation(fields: [titleId], references: [id], onDelete: SetNull, onUpdate: NoAction)
addresses Address[]
authoredBlogs blogs[] @relation("BlogAuthor")
businessCustomers BusinessCustomer[]
@@ -53,6 +55,20 @@ model User {
@@map("users")
}
model UserTitle {
id BigInt @id @default(autoincrement())
slug String @unique(map: "user_titles_slug_unique") @db.VarChar(32)
nameEn String @map("name_en") @db.VarChar(64)
nameFa String @map("name_fa") @db.VarChar(64)
sortOrder Int @default(0) @map("sort_order")
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
users User[]
@@index([sortOrder, id], map: "idx_user_titles_sort")
@@map("user_titles")
}
model Business {
id BigInt @id @default(autoincrement())
name String @db.VarChar(255)
@@ -72,6 +88,7 @@ model Business {
logoDarkMediaId BigInt? @map("logo_dark_media_id")
faviconMediaId BigInt? @map("favicon_media_id")
oldBusinessId BigInt? @map("old_business_id")
annualRenewalAt DateTime? @map("annual_renewal_at") @db.Timestamptz(6)
addresses Address[]
blogs blogs[]
brands Brand[]
@@ -116,7 +133,8 @@ model Business {
website_category_groups website_category_groups[]
website_image_slots website_image_slots[]
website_sliders website_sliders[]
websiteContentViews WebsiteContentView[]
websitePageViews WebsitePageView[]
websitePageViewCounters WebsitePageViewCounter[]
@@map("businesses")
}
@@ -161,6 +179,7 @@ model Domain {
isVerified Boolean @default(false) @map("is_verified")
verifiedAt DateTime? @map("verified_at") @db.Timestamptz(6)
sslEnabled Boolean @default(false) @map("ssl_enabled")
sslExpiresAt DateTime? @map("ssl_expires_at") @db.Timestamptz(6)
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
expiresAt DateTime? @map("expires_at") @db.Timestamptz(6)
@@ -436,6 +455,9 @@ model Product {
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
brandId BigInt? @map("brand_id")
visitCount BigInt @default(0) @map("visit_count")
cartAddCount BigInt @default(0) @map("cart_add_count")
saleCount BigInt @default(0) @map("sale_count")
expertReviews ExpertReview[]
favorites Favorite[]
orderItems OrderItem[]
@@ -1638,24 +1660,47 @@ enum UserProductCondition {
@@map("user_product_condition")
}
enum WebsiteViewKind {
website
product
portfolio
blog
enum WebsitePageKind {
home
portfolio_list
portfolio_detail
product_list
product_detail
store_item_list
store_item_detail
blog_list
blog_detail
video_list
video_detail
@@map("website_view_kind")
@@map("website_page_kind")
}
model WebsiteContentView {
/// Raw page-view events (retained ~6 months; purged by cron).
model WebsitePageView {
id BigInt @id @default(autoincrement())
businessId BigInt @map("business_id")
viewKind WebsiteViewKind @map("view_kind")
entityId BigInt? @map("entity_id")
pageKind WebsitePageKind @map("page_kind")
entityId BigInt @default(0) @map("entity_id")
pagePath String? @map("page_path")
viewedAt DateTime @default(now()) @map("viewed_at") @db.Timestamptz(6)
business Business @relation(fields: [businessId], references: [id], onDelete: Cascade, onUpdate: NoAction)
@@index([businessId, viewKind, viewedAt(sort: Desc)], map: "idx_website_content_views_business_kind_time")
@@map("website_content_views")
@@index([businessId, pageKind, viewedAt(sort: Desc)], map: "idx_website_page_views_business_kind_time")
@@map("website_page_views")
}
/// Lifetime totals per page kind (+ entity for detail pages). Never purged.
model WebsitePageViewCounter {
id BigInt @id @default(autoincrement())
businessId BigInt @map("business_id")
pageKind WebsitePageKind @map("page_kind")
entityId BigInt @default(0) @map("entity_id")
totalCount BigInt @default(0) @map("total_count")
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
business Business @relation(fields: [businessId], references: [id], onDelete: Cascade, onUpdate: NoAction)
@@unique([businessId, pageKind, entityId], map: "uq_website_page_view_counters_biz_kind_entity")
@@index([businessId, pageKind], map: "idx_website_page_view_counters_business_kind")
@@map("website_page_view_counters")
}