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:
co-authored by
Cursor
parent
15dd3597f0
commit
835c8a0ac1
@@ -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";
|
||||
+41
-10
@@ -131,24 +131,27 @@ model Store {
|
||||
blogs BlogPost[]
|
||||
orderItems OrderItem[]
|
||||
fulfillments OrderStoreFulfillment[]
|
||||
websiteSlots WebsiteSlotItem[]
|
||||
|
||||
@@index([status])
|
||||
}
|
||||
|
||||
model Category {
|
||||
id String @id @default(cuid())
|
||||
id Int @id @default(autoincrement())
|
||||
storeId String
|
||||
store Store @relation(fields: [storeId], references: [id], onDelete: Cascade)
|
||||
parentId String?
|
||||
parentId Int?
|
||||
parent Category? @relation("CategoryTree", fields: [parentId], references: [id], onDelete: SetNull)
|
||||
children Category[] @relation("CategoryTree")
|
||||
nameFa String
|
||||
nameEn String @default("")
|
||||
sortOrder Int @default(0)
|
||||
colors String[] @default([])
|
||||
sizes String[] @default([])
|
||||
packages String[] @default([])
|
||||
products Product[]
|
||||
colors String[] @default([])
|
||||
sizes String[] @default([])
|
||||
packages String[] @default([])
|
||||
/// Ordered form-builder defs: [{ id, labelFa, labelEn?, type: "text", required? }]
|
||||
technicalFields Json @default("[]")
|
||||
products Product[]
|
||||
|
||||
@@index([storeId])
|
||||
@@index([parentId])
|
||||
@@ -169,10 +172,10 @@ model Brand {
|
||||
}
|
||||
|
||||
model Product {
|
||||
id String @id @default(cuid())
|
||||
id Int @id @default(autoincrement())
|
||||
storeId String
|
||||
store Store @relation(fields: [storeId], references: [id], onDelete: Cascade)
|
||||
categoryId String?
|
||||
categoryId Int?
|
||||
category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull)
|
||||
brandId String?
|
||||
brand Brand? @relation(fields: [brandId], references: [id], onDelete: SetNull)
|
||||
@@ -188,10 +191,14 @@ model Product {
|
||||
galleryKeys String[] @default([])
|
||||
tags String[] @default([])
|
||||
published Boolean @default(true)
|
||||
/// Home carousel featured slot: promo-1 | promo-2 | promo-3
|
||||
featuredSlot String?
|
||||
vipEnabled Boolean @default(false)
|
||||
vipOnly Boolean @default(false)
|
||||
vipSpecialPrice Boolean @default(false)
|
||||
vipPrice Int?
|
||||
/// Values keyed by category technical field id: { [fieldId]: string }
|
||||
technicalData Json @default("{}")
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
variants ProductVariant[]
|
||||
@@ -202,13 +209,14 @@ model Product {
|
||||
@@index([brandId])
|
||||
@@index([price])
|
||||
@@index([published])
|
||||
@@index([featuredSlot])
|
||||
@@index([vipEnabled])
|
||||
@@index([vipOnly])
|
||||
}
|
||||
|
||||
model ProductVariant {
|
||||
id String @id @default(cuid())
|
||||
productId String
|
||||
productId Int
|
||||
product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
|
||||
colorId String @default("")
|
||||
sizeId String @default("")
|
||||
@@ -289,7 +297,7 @@ model OrderItem {
|
||||
order Order @relation(fields: [orderId], references: [id], onDelete: Cascade)
|
||||
storeId String
|
||||
store Store @relation(fields: [storeId], references: [id], onDelete: Restrict)
|
||||
productId String?
|
||||
productId Int?
|
||||
product Product? @relation(fields: [productId], references: [id], onDelete: SetNull)
|
||||
nameFa String
|
||||
variantLabel String?
|
||||
@@ -313,3 +321,26 @@ model OrderStoreFulfillment {
|
||||
@@index([storeId])
|
||||
@@index([status])
|
||||
}
|
||||
|
||||
/// Homepage / storefront image slots (hero slides, banners, category thumbs, …)
|
||||
model WebsiteSlotItem {
|
||||
id String @id @default(cuid())
|
||||
storeId String
|
||||
store Store @relation(fields: [storeId], references: [id], onDelete: Cascade)
|
||||
/// e.g. home.hero | home.category.shirts | home.banner.leather
|
||||
slotKey String
|
||||
sortOrder Int @default(0)
|
||||
nameFa String @default("")
|
||||
nameEn String @default("")
|
||||
subtext String @default("")
|
||||
linkText String @default("")
|
||||
link String @default("")
|
||||
imageUrl String?
|
||||
imageKey String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([storeId])
|
||||
@@index([storeId, slotKey])
|
||||
@@index([storeId, slotKey, sortOrder])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user