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
@@ -0,0 +1,76 @@
-- Expand page-view kinds, rename events table, add lifetime counters.
-- Events retained max 6 months (purged by Nest cron). Counters kept forever.
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'website_page_kind') THEN
CREATE TYPE website_page_kind AS ENUM (
'home',
'portfolio_list',
'portfolio_detail',
'product_list',
'product_detail',
'store_item_list',
'store_item_detail',
'blog_list',
'blog_detail',
'video_list',
'video_detail'
);
END IF;
END $$;
-- Recreate events table with new kind enum (table is empty / low volume today).
CREATE TABLE IF NOT EXISTS website_page_views (
id BIGSERIAL PRIMARY KEY,
business_id BIGINT NOT NULL REFERENCES businesses (id) ON DELETE CASCADE,
page_kind website_page_kind NOT NULL,
entity_id BIGINT NOT NULL DEFAULT 0,
page_path TEXT,
viewed_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_website_page_views_business_kind_time
ON website_page_views (business_id, page_kind, viewed_at DESC);
-- Copy any legacy rows from website_content_views
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_schema = 'public' AND table_name = 'website_content_views'
) THEN
INSERT INTO website_page_views (business_id, page_kind, entity_id, page_path, viewed_at)
SELECT
business_id,
CASE view_kind::text
WHEN 'website' THEN 'home'::website_page_kind
WHEN 'product' THEN 'product_detail'::website_page_kind
WHEN 'portfolio' THEN 'portfolio_detail'::website_page_kind
WHEN 'blog' THEN 'blog_detail'::website_page_kind
ELSE 'home'::website_page_kind
END,
COALESCE(entity_id, 0),
page_path,
viewed_at
FROM website_content_views;
DROP TABLE website_content_views;
END IF;
END $$;
DROP TYPE IF EXISTS website_view_kind;
CREATE TABLE IF NOT EXISTS website_page_view_counters (
id BIGSERIAL PRIMARY KEY,
business_id BIGINT NOT NULL REFERENCES businesses (id) ON DELETE CASCADE,
page_kind website_page_kind NOT NULL,
entity_id BIGINT NOT NULL DEFAULT 0,
total_count BIGINT NOT NULL DEFAULT 0,
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT uq_website_page_view_counters_biz_kind_entity
UNIQUE (business_id, page_kind, entity_id)
);
CREATE INDEX IF NOT EXISTS idx_website_page_view_counters_business_kind
ON website_page_view_counters (business_id, page_kind);
@@ -0,0 +1,5 @@
-- Lifetime product analysis counters (visits, cart adds, units sold).
ALTER TABLE products
ADD COLUMN IF NOT EXISTS visit_count BIGINT NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS cart_add_count BIGINT NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS sale_count BIGINT NOT NULL DEFAULT 0;
@@ -0,0 +1,8 @@
-- Store Let's Encrypt / TLS notAfter after a successful SSL issue.
-- List Valid/Invalid prefers this date; live probes run at most once per day.
ALTER TABLE domains
ADD COLUMN IF NOT EXISTS ssl_expires_at TIMESTAMPTZ;
CREATE INDEX IF NOT EXISTS idx_domains_ssl_expires_at
ON domains (ssl_expires_at)
WHERE ssl_expires_at IS NOT NULL;
@@ -0,0 +1,32 @@
-- Business annual renewal date + user honorific titles.
CREATE TABLE IF NOT EXISTS user_titles (
id BIGSERIAL PRIMARY KEY,
slug VARCHAR(32) NOT NULL,
name_en VARCHAR(64) NOT NULL,
name_fa VARCHAR(64) NOT NULL,
sort_order INT NOT NULL DEFAULT 0,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT user_titles_slug_unique UNIQUE (slug)
);
CREATE INDEX IF NOT EXISTS idx_user_titles_sort
ON user_titles (sort_order, id);
INSERT INTO user_titles (slug, name_en, name_fa, sort_order)
VALUES
('mr', 'Mr', 'آقای', 10),
('mrs', 'Mrs', 'خانم', 20),
('ms', 'Ms', 'خانم', 30),
('dr', 'Dr', 'دکتر', 40),
('eng', 'Eng.', 'مهندس', 50)
ON CONFLICT (slug) DO NOTHING;
ALTER TABLE users
ADD COLUMN IF NOT EXISTS title_id BIGINT REFERENCES user_titles (id) ON DELETE SET NULL;
CREATE INDEX IF NOT EXISTS idx_users_title_id ON users (title_id);
ALTER TABLE businesses
ADD COLUMN IF NOT EXISTS annual_renewal_at TIMESTAMPTZ;