Add public catalog APIs, website slots, featured slots, and int category/product IDs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-09-27 18:30:36 +03:30
co-authored by Cursor
parent 15dd3597f0
commit 835c8a0ac1
25 changed files with 1399 additions and 41 deletions
@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "Category" ADD COLUMN "technicalFields" JSONB NOT NULL DEFAULT '[]';
-- AlterTable
ALTER TABLE "Product" ADD COLUMN "technicalData" JSONB NOT NULL DEFAULT '{}';
@@ -0,0 +1,30 @@
-- CreateTable
CREATE TABLE "WebsiteSlotItem" (
"id" TEXT NOT NULL,
"storeId" TEXT NOT NULL,
"slotKey" TEXT NOT NULL,
"sortOrder" INTEGER NOT NULL DEFAULT 0,
"nameFa" TEXT NOT NULL DEFAULT '',
"nameEn" TEXT NOT NULL DEFAULT '',
"subtext" TEXT NOT NULL DEFAULT '',
"linkText" TEXT NOT NULL DEFAULT '',
"link" TEXT NOT NULL DEFAULT '',
"imageUrl" TEXT,
"imageKey" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "WebsiteSlotItem_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "WebsiteSlotItem_storeId_idx" ON "WebsiteSlotItem"("storeId");
-- CreateIndex
CREATE INDEX "WebsiteSlotItem_storeId_slotKey_idx" ON "WebsiteSlotItem"("storeId", "slotKey");
-- CreateIndex
CREATE INDEX "WebsiteSlotItem_storeId_slotKey_sortOrder_idx" ON "WebsiteSlotItem"("storeId", "slotKey", "sortOrder");
-- AddForeignKey
ALTER TABLE "WebsiteSlotItem" ADD CONSTRAINT "WebsiteSlotItem_storeId_fkey" FOREIGN KEY ("storeId") REFERENCES "Store"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "Product" ADD COLUMN "featuredSlot" TEXT;
-- CreateIndex
CREATE INDEX "Product_featuredSlot_idx" ON "Product"("featuredSlot");
@@ -0,0 +1,92 @@
-- Convert Category.id (cuid text) → serial integer; remap Product.categoryId + parentId.
ALTER TABLE "Product" DROP CONSTRAINT IF EXISTS "Product_categoryId_fkey";
ALTER TABLE "Category" DROP CONSTRAINT IF EXISTS "Category_parentId_fkey";
ALTER TABLE "Category" DROP CONSTRAINT IF EXISTS "Category_storeId_fkey";
DROP INDEX IF EXISTS "Category_storeId_idx";
DROP INDEX IF EXISTS "Category_parentId_idx";
DROP INDEX IF EXISTS "Category_sortOrder_idx";
DROP INDEX IF EXISTS "Product_categoryId_idx";
CREATE TABLE "_category_id_map" (
"old_id" TEXT PRIMARY KEY,
"new_id" INTEGER NOT NULL UNIQUE
);
INSERT INTO "_category_id_map" ("old_id", "new_id")
SELECT "id", ROW_NUMBER() OVER (ORDER BY "id")::INTEGER
FROM "Category";
CREATE TABLE "Category_new" (
"id" INTEGER NOT NULL,
"storeId" TEXT NOT NULL,
"parentId" INTEGER,
"nameFa" TEXT NOT NULL,
"nameEn" TEXT NOT NULL DEFAULT '',
"sortOrder" INTEGER NOT NULL DEFAULT 0,
"colors" TEXT[] DEFAULT ARRAY[]::TEXT[],
"sizes" TEXT[] DEFAULT ARRAY[]::TEXT[],
"packages" TEXT[] DEFAULT ARRAY[]::TEXT[],
"technicalFields" JSONB NOT NULL DEFAULT '[]',
CONSTRAINT "Category_new_pkey" PRIMARY KEY ("id")
);
INSERT INTO "Category_new" (
"id", "storeId", "parentId", "nameFa", "nameEn", "sortOrder",
"colors", "sizes", "packages", "technicalFields"
)
SELECT
m."new_id",
c."storeId",
mp."new_id",
c."nameFa",
c."nameEn",
c."sortOrder",
c."colors",
c."sizes",
c."packages",
COALESCE(c."technicalFields", '[]'::jsonb)
FROM "Category" c
JOIN "_category_id_map" m ON m."old_id" = c."id"
LEFT JOIN "_category_id_map" mp ON mp."old_id" = c."parentId";
ALTER TABLE "Product" ADD COLUMN "categoryId_new" INTEGER;
UPDATE "Product" AS p
SET "categoryId_new" = m."new_id"
FROM "_category_id_map" AS m
WHERE p."categoryId" = m."old_id";
ALTER TABLE "Product" DROP COLUMN "categoryId";
ALTER TABLE "Product" RENAME COLUMN "categoryId_new" TO "categoryId";
DROP TABLE "Category";
ALTER TABLE "Category_new" RENAME TO "Category";
ALTER TABLE "Category" RENAME CONSTRAINT "Category_new_pkey" TO "Category_pkey";
CREATE SEQUENCE "Category_id_seq" OWNED BY "Category"."id";
SELECT setval(
'"Category_id_seq"',
COALESCE((SELECT MAX("id") FROM "Category"), 1)
);
ALTER TABLE "Category" ALTER COLUMN "id" SET DEFAULT nextval('"Category_id_seq"');
CREATE INDEX "Category_storeId_idx" ON "Category"("storeId");
CREATE INDEX "Category_parentId_idx" ON "Category"("parentId");
CREATE INDEX "Category_sortOrder_idx" ON "Category"("sortOrder");
CREATE INDEX "Product_categoryId_idx" ON "Product"("categoryId");
ALTER TABLE "Category"
ADD CONSTRAINT "Category_storeId_fkey"
FOREIGN KEY ("storeId") REFERENCES "Store"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "Category"
ADD CONSTRAINT "Category_parentId_fkey"
FOREIGN KEY ("parentId") REFERENCES "Category"("id") ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "Product"
ADD CONSTRAINT "Product_categoryId_fkey"
FOREIGN KEY ("categoryId") REFERENCES "Category"("id") ON DELETE SET NULL ON UPDATE CASCADE;
DROP TABLE "_category_id_map";
@@ -0,0 +1,154 @@
-- Convert Product.id (cuid text) → serial integer; remap ProductVariant + OrderItem FKs.
ALTER TABLE "ProductVariant" DROP CONSTRAINT IF EXISTS "ProductVariant_productId_fkey";
ALTER TABLE "OrderItem" DROP CONSTRAINT IF EXISTS "OrderItem_productId_fkey";
ALTER TABLE "Product" DROP CONSTRAINT IF EXISTS "Product_storeId_fkey";
ALTER TABLE "Product" DROP CONSTRAINT IF EXISTS "Product_categoryId_fkey";
ALTER TABLE "Product" DROP CONSTRAINT IF EXISTS "Product_brandId_fkey";
DROP INDEX IF EXISTS "Product_storeId_idx";
DROP INDEX IF EXISTS "Product_categoryId_idx";
DROP INDEX IF EXISTS "Product_brandId_idx";
DROP INDEX IF EXISTS "Product_price_idx";
DROP INDEX IF EXISTS "Product_published_idx";
DROP INDEX IF EXISTS "Product_featuredSlot_idx";
DROP INDEX IF EXISTS "Product_vipEnabled_idx";
DROP INDEX IF EXISTS "Product_vipOnly_idx";
DROP INDEX IF EXISTS "ProductVariant_productId_idx";
DROP INDEX IF EXISTS "OrderItem_productId_idx";
CREATE TABLE "_product_id_map" (
"old_id" TEXT PRIMARY KEY,
"new_id" INTEGER NOT NULL UNIQUE
);
INSERT INTO "_product_id_map" ("old_id", "new_id")
SELECT "id", ROW_NUMBER() OVER (ORDER BY "createdAt", "id")::INTEGER
FROM "Product";
CREATE TABLE "Product_new" (
"id" INTEGER NOT NULL,
"storeId" TEXT NOT NULL,
"categoryId" INTEGER,
"brandId" TEXT,
"nameFa" TEXT NOT NULL,
"nameEn" TEXT NOT NULL DEFAULT '',
"summary" TEXT NOT NULL DEFAULT '',
"description" TEXT NOT NULL DEFAULT '',
"price" INTEGER NOT NULL DEFAULT 0,
"discountPrice" INTEGER,
"imageUrl" TEXT,
"imageKey" TEXT,
"galleryUrls" TEXT[] DEFAULT ARRAY[]::TEXT[],
"galleryKeys" TEXT[] DEFAULT ARRAY[]::TEXT[],
"tags" TEXT[] DEFAULT ARRAY[]::TEXT[],
"published" BOOLEAN NOT NULL DEFAULT true,
"featuredSlot" TEXT,
"vipEnabled" BOOLEAN NOT NULL DEFAULT false,
"vipOnly" BOOLEAN NOT NULL DEFAULT false,
"vipSpecialPrice" BOOLEAN NOT NULL DEFAULT false,
"vipPrice" INTEGER,
"technicalData" JSONB NOT NULL DEFAULT '{}',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Product_new_pkey" PRIMARY KEY ("id")
);
INSERT INTO "Product_new" (
"id", "storeId", "categoryId", "brandId", "nameFa", "nameEn", "summary", "description",
"price", "discountPrice", "imageUrl", "imageKey", "galleryUrls", "galleryKeys", "tags",
"published", "featuredSlot", "vipEnabled", "vipOnly", "vipSpecialPrice", "vipPrice",
"technicalData", "createdAt", "updatedAt"
)
SELECT
m."new_id",
p."storeId",
p."categoryId",
p."brandId",
p."nameFa",
p."nameEn",
p."summary",
p."description",
p."price",
p."discountPrice",
p."imageUrl",
p."imageKey",
p."galleryUrls",
p."galleryKeys",
p."tags",
p."published",
p."featuredSlot",
p."vipEnabled",
p."vipOnly",
p."vipSpecialPrice",
p."vipPrice",
COALESCE(p."technicalData", '{}'::jsonb),
p."createdAt",
p."updatedAt"
FROM "Product" p
JOIN "_product_id_map" m ON m."old_id" = p."id";
ALTER TABLE "ProductVariant" ADD COLUMN "productId_new" INTEGER;
UPDATE "ProductVariant" AS v
SET "productId_new" = m."new_id"
FROM "_product_id_map" AS m
WHERE v."productId" = m."old_id";
ALTER TABLE "ProductVariant" DROP COLUMN "productId";
ALTER TABLE "ProductVariant" RENAME COLUMN "productId_new" TO "productId";
ALTER TABLE "ProductVariant" ALTER COLUMN "productId" SET NOT NULL;
ALTER TABLE "OrderItem" ADD COLUMN "productId_new" INTEGER;
UPDATE "OrderItem" AS oi
SET "productId_new" = m."new_id"
FROM "_product_id_map" AS m
WHERE oi."productId" = m."old_id";
ALTER TABLE "OrderItem" DROP COLUMN "productId";
ALTER TABLE "OrderItem" RENAME COLUMN "productId_new" TO "productId";
DROP TABLE "Product";
ALTER TABLE "Product_new" RENAME TO "Product";
ALTER TABLE "Product" RENAME CONSTRAINT "Product_new_pkey" TO "Product_pkey";
CREATE SEQUENCE "Product_id_seq" OWNED BY "Product"."id";
SELECT setval(
'"Product_id_seq"',
COALESCE((SELECT MAX("id") FROM "Product"), 1)
);
ALTER TABLE "Product" ALTER COLUMN "id" SET DEFAULT nextval('"Product_id_seq"');
CREATE INDEX "Product_storeId_idx" ON "Product"("storeId");
CREATE INDEX "Product_categoryId_idx" ON "Product"("categoryId");
CREATE INDEX "Product_brandId_idx" ON "Product"("brandId");
CREATE INDEX "Product_price_idx" ON "Product"("price");
CREATE INDEX "Product_published_idx" ON "Product"("published");
CREATE INDEX "Product_featuredSlot_idx" ON "Product"("featuredSlot");
CREATE INDEX "Product_vipEnabled_idx" ON "Product"("vipEnabled");
CREATE INDEX "Product_vipOnly_idx" ON "Product"("vipOnly");
CREATE INDEX "ProductVariant_productId_idx" ON "ProductVariant"("productId");
CREATE INDEX "OrderItem_productId_idx" ON "OrderItem"("productId");
ALTER TABLE "Product"
ADD CONSTRAINT "Product_storeId_fkey"
FOREIGN KEY ("storeId") REFERENCES "Store"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "Product"
ADD CONSTRAINT "Product_categoryId_fkey"
FOREIGN KEY ("categoryId") REFERENCES "Category"("id") ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "Product"
ADD CONSTRAINT "Product_brandId_fkey"
FOREIGN KEY ("brandId") REFERENCES "Brand"("id") ON DELETE SET NULL ON UPDATE CASCADE;
ALTER TABLE "ProductVariant"
ADD CONSTRAINT "ProductVariant_productId_fkey"
FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "OrderItem"
ADD CONSTRAINT "OrderItem_productId_fkey"
FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE SET NULL ON UPDATE CASCADE;
DROP TABLE "_product_id_map";