This commit is contained in:
shafi54 2026-07-29 20:56:16 +05:30
parent 8fae91b582
commit 7553badfeb
7 changed files with 7240 additions and 33 deletions

View file

@ -8,8 +8,10 @@ routes = [
[[d1_databases]]
binding = "DB"
database_name = "freshyo-backend-dev"
database_id = "6b93ddc9-9b24-4cfc-9320-e81aec38887a"
#database_name = "freshyo-backend-dev"
#database_id = "6b93ddc9-9b24-4cfc-9320-e81aec38887a"
database_name = "freshyo-dev"
database_id = "3cad440f-dc14-4baa-ab05-04eb08e206de"
migrations_dir="../../packages/db_helper_sqlite/drizzle"
migrations_pattern="migration.sql"
[durable_objects]

View file

@ -0,0 +1,229 @@
-- Migration: split product_info into product_info (catalog) + product_skus + sku_features
-- and move all buyable references from product ids to sku ids.
-- PRAGMA foreign_keys=OFF;
PRAGMA defer_foreign_keys = on;
-- 1. Create new SKU tables.
CREATE TABLE `product_skus` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`product_id` integer NOT NULL,
`name` text,
`price` text NOT NULL,
`market_price` text,
`images` text,
`is_out_of_stock` integer DEFAULT false NOT NULL,
`is_suspended` integer DEFAULT false NOT NULL,
`is_flash_available` integer DEFAULT false NOT NULL,
`flash_price` text,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`product_id`) REFERENCES `product_info`(`id`) ON UPDATE no action ON DELETE no action
);
CREATE TABLE `sku_features` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`sku_id` integer NOT NULL,
`feature_name` text NOT NULL,
`feature_value` text NOT NULL,
FOREIGN KEY (`sku_id`) REFERENCES `product_skus`(`id`) ON UPDATE no action ON DELETE no action
);
CREATE UNIQUE INDEX `unique_sku_feature_name` ON `sku_features` (`sku_id`,`feature_name`);
-- 2. Migrate each existing product into one default SKU plus a mandatory quantity feature.
INSERT INTO `product_skus` (
`product_id`, `name`, `price`, `market_price`, `images`, `is_out_of_stock`,
`is_suspended`, `is_flash_available`, `flash_price`, `created_at`
)
SELECT
`id`,
NULL,
`price`,
`market_price`,
`images`,
`is_out_of_stock`,
`is_suspended`,
`is_flash_available`,
`flash_price`,
`created_at`
FROM `product_info`;
INSERT INTO `sku_features` (`sku_id`, `feature_name`, `feature_value`)
SELECT
`ps`.`id`,
'quantity',
CASE
WHEN CAST(`pi`.`product_quantity` AS INTEGER) = `pi`.`product_quantity`
THEN CAST(CAST(`pi`.`product_quantity` AS INTEGER) AS TEXT)
ELSE CAST(`pi`.`product_quantity` AS TEXT)
END || COALESCE(`u`.`short_notation`, '')
FROM `product_info` `pi`
JOIN `product_skus` `ps` ON `ps`.`product_id` = `pi`.`id`
LEFT JOIN `units` `u` ON `u`.`id` = `pi`.`unit_id`;
-- 3. Build a product_id -> sku_id mapping for downstream tables.
CREATE TABLE `__product_to_sku` (
`product_id` integer PRIMARY KEY,
`sku_id` integer NOT NULL
);
INSERT INTO `__product_to_sku` (`product_id`, `sku_id`)
SELECT `product_id`, `id` FROM `product_skus`;
-- 4. Recreate product_info with only catalog-level fields.
CREATE TABLE `__new_product_info` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`name` text NOT NULL,
`short_description` text,
`long_description` text,
`store_id` integer,
`increment_step` real DEFAULT 1 NOT NULL,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`store_id`) REFERENCES `store_info`(`id`) ON UPDATE no action ON DELETE no action
);
INSERT INTO `__new_product_info` (
`id`, `name`, `short_description`, `long_description`, `store_id`, `increment_step`, `created_at`
)
SELECT
`id`, `name`, `short_description`, `long_description`, `store_id`, `increment_step`, `created_at`
FROM `product_info`;
DROP TABLE `product_info`;
ALTER TABLE `__new_product_info` RENAME TO `product_info`;
-- 5. Recreate tables that previously referenced product_info.id so they now reference product_skus.id.
CREATE TABLE `__new_cart_items` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`user_id` integer NOT NULL,
`sku_id` integer NOT NULL,
`quantity` text NOT NULL,
`added_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action,
FOREIGN KEY (`sku_id`) REFERENCES `product_skus`(`id`) ON UPDATE no action ON DELETE no action
);
INSERT INTO `__new_cart_items` (`id`, `user_id`, `sku_id`, `quantity`, `added_at`)
SELECT
`ci`.`id`, `ci`.`user_id`, `m`.`sku_id`, `ci`.`quantity`, `ci`.`added_at`
FROM `cart_items` `ci`
JOIN `__product_to_sku` `m` ON `m`.`product_id` = `ci`.`product_id`;
DROP TABLE `cart_items`;
ALTER TABLE `__new_cart_items` RENAME TO `cart_items`;
CREATE UNIQUE INDEX `unique_user_sku` ON `cart_items` (`user_id`,`sku_id`);
CREATE TABLE `__new_order_items` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`order_id` integer NOT NULL,
`sku_id` integer NOT NULL,
`quantity` text NOT NULL,
`price` text NOT NULL,
`discounted_price` text,
`is_packaged` integer DEFAULT false NOT NULL,
`is_package_verified` integer DEFAULT false NOT NULL,
FOREIGN KEY (`order_id`) REFERENCES `orders`(`id`) ON UPDATE no action ON DELETE no action,
FOREIGN KEY (`sku_id`) REFERENCES `product_skus`(`id`) ON UPDATE no action ON DELETE no action
);
INSERT INTO `__new_order_items` (
`id`, `order_id`, `sku_id`, `quantity`, `price`, `discounted_price`, `is_packaged`, `is_package_verified`
)
SELECT
`oi`.`id`, `oi`.`order_id`, `m`.`sku_id`, `oi`.`quantity`, `oi`.`price`,
`oi`.`discounted_price`, `oi`.`is_packaged`, `oi`.`is_package_verified`
FROM `order_items` `oi`
JOIN `__product_to_sku` `m` ON `m`.`product_id` = `oi`.`product_id`;
DROP TABLE `order_items`;
ALTER TABLE `__new_order_items` RENAME TO `order_items`;
CREATE TABLE `__new_special_deals` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`sku_id` integer NOT NULL,
`quantity` text NOT NULL,
`price` text NOT NULL,
`valid_till` text NOT NULL,
FOREIGN KEY (`sku_id`) REFERENCES `product_skus`(`id`) ON UPDATE no action ON DELETE no action
);
INSERT INTO `__new_special_deals` (`id`, `sku_id`, `quantity`, `price`, `valid_till`)
SELECT
`sd`.`id`, `m`.`sku_id`, `sd`.`quantity`, `sd`.`price`, `sd`.`valid_till`
FROM `special_deals` `sd`
JOIN `__product_to_sku` `m` ON `m`.`product_id` = `sd`.`product_id`;
DROP TABLE `special_deals`;
ALTER TABLE `__new_special_deals` RENAME TO `special_deals`;
CREATE TABLE `__new_coupon_applicable_products` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`coupon_id` integer NOT NULL,
`sku_id` integer NOT NULL,
FOREIGN KEY (`coupon_id`) REFERENCES `coupons`(`id`) ON UPDATE no action ON DELETE no action,
FOREIGN KEY (`sku_id`) REFERENCES `product_skus`(`id`) ON UPDATE no action ON DELETE no action
);
INSERT INTO `__new_coupon_applicable_products` (`id`, `coupon_id`, `sku_id`)
SELECT
`cap`.`id`, `cap`.`coupon_id`, `m`.`sku_id`
FROM `coupon_applicable_products` `cap`
JOIN `__product_to_sku` `m` ON `m`.`product_id` = `cap`.`product_id`;
DROP TABLE `coupon_applicable_products`;
ALTER TABLE `__new_coupon_applicable_products` RENAME TO `coupon_applicable_products`;
CREATE UNIQUE INDEX `unique_coupon_sku` ON `coupon_applicable_products` (`coupon_id`,`sku_id`);
-- 6. Remap JSON product id arrays to sku id arrays (before renaming the columns).
UPDATE `delivery_slot_info` AS `target`
SET `product_ids` = COALESCE(
(SELECT json_group_array(`m`.`sku_id`)
FROM json_each(`target`.`product_ids`) AS `je`
JOIN `__product_to_sku` `m` ON `m`.`product_id` = `je`.`value`),
CASE WHEN `target`.`product_ids` IS NULL THEN NULL ELSE '[]' END
);
UPDATE `home_banners` AS `target`
SET `product_ids` = COALESCE(
(SELECT json_group_array(`m`.`sku_id`)
FROM json_each(`target`.`product_ids`) AS `je`
JOIN `__product_to_sku` `m` ON `m`.`product_id` = `je`.`value`),
CASE WHEN `target`.`product_ids` IS NULL THEN NULL ELSE '[]' END
);
UPDATE `coupons` AS `target`
SET `product_ids` = COALESCE(
(SELECT json_group_array(`m`.`sku_id`)
FROM json_each(`target`.`product_ids`) AS `je`
JOIN `__product_to_sku` `m` ON `m`.`product_id` = `je`.`value`),
CASE WHEN `target`.`product_ids` IS NULL THEN NULL ELSE '[]' END
);
UPDATE `reserved_coupons` AS `target`
SET `product_ids` = COALESCE(
(SELECT json_group_array(`m`.`sku_id`)
FROM json_each(`target`.`product_ids`) AS `je`
JOIN `__product_to_sku` `m` ON `m`.`product_id` = `je`.`value`),
CASE WHEN `target`.`product_ids` IS NULL THEN NULL ELSE '[]' END
);
UPDATE `vendor_snippets` AS `target`
SET `product_ids` = COALESCE(
(SELECT json_group_array(`m`.`sku_id`)
FROM json_each(`target`.`product_ids`) AS `je`
JOIN `__product_to_sku` `m` ON `m`.`product_id` = `je`.`value`),
'[]'
);
-- 7. Rename JSON columns from product_ids to sku_ids.
ALTER TABLE `delivery_slot_info` RENAME COLUMN `product_ids` TO `sku_ids`;
ALTER TABLE `home_banners` RENAME COLUMN `product_ids` TO `sku_ids`;
ALTER TABLE `coupons` RENAME COLUMN `product_ids` TO `sku_ids`;
ALTER TABLE `reserved_coupons` RENAME COLUMN `product_ids` TO `sku_ids`;
ALTER TABLE `vendor_snippets` RENAME COLUMN `product_ids` TO `sku_ids`;
-- 8. Clean up helper table.
DROP TABLE `__product_to_sku`;
-- PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys = off;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -8,6 +8,20 @@
"when": 1774588140474,
"tag": "0000_nifty_sauron",
"breakpoints": true
},
{
"idx": 1,
"version": "6",
"when": 1785336112559,
"tag": "0001_migrate_product_slots",
"breakpoints": true
},
{
"idx": 2,
"version": "6",
"when": 1785336600165,
"tag": "0002_sku_split",
"breakpoints": true
}
]
}

View file

@ -189,7 +189,15 @@ export const productInfo = sqliteTable('product_info', {
name: text().notNull(),
shortDescription: text('short_description'),
longDescription: text('long_description'),
unitId: integer('unit_id').notNull().references(() => units.id),
storeId: integer('store_id').references(() => storeInfo.id),
incrementStep: real('increment_step').notNull().default(1),
createdAt: timestampText('created_at').notNull().default(sql`CURRENT_TIMESTAMP`),
})
export const productSkus = sqliteTable('product_skus', {
id: integer().primaryKey({ autoIncrement: true }),
productId: integer('product_id').notNull().references(() => productInfo.id),
name: text(),
price: numericText('price').notNull(),
marketPrice: numericText('market_price'),
images: jsonText<string[] | null>('images'),
@ -198,11 +206,17 @@ export const productInfo = sqliteTable('product_info', {
isFlashAvailable: integer('is_flash_available', { mode: 'boolean' }).notNull().default(false),
flashPrice: numericText('flash_price'),
createdAt: timestampText('created_at').notNull().default(sql`CURRENT_TIMESTAMP`),
incrementStep: real('increment_step').notNull().default(1),
productQuantity: real('product_quantity').notNull().default(1),
storeId: integer('store_id').references(() => storeInfo.id),
})
export const skuFeatures = sqliteTable('sku_features', {
id: integer().primaryKey({ autoIncrement: true }),
skuId: integer('sku_id').notNull().references(() => productSkus.id),
featureName: text('feature_name').notNull(),
featureValue: text('feature_value').notNull(),
}, (t) => ({
unq_sku_feature_name: uniqueIndex('unique_sku_feature_name').on(t.skuId, t.featureName),
}))
export const productGroupInfo = sqliteTable('product_group_info', {
id: integer().primaryKey({ autoIncrement: true }),
groupName: text('group_name').notNull(),
@ -223,7 +237,7 @@ export const homeBanners = sqliteTable('home_banners', {
name: text('name').notNull(),
imageUrl: text('image_url').notNull(),
description: text('description'),
productIds: jsonText<number[] | null>('product_ids'),
skuIds: jsonText<number[] | null>('sku_ids'),
redirectUrl: text('redirect_url'),
serialNum: integer('serial_num'),
isActive: integer('is_active', { mode: 'boolean' }).notNull().default(false),
@ -280,7 +294,7 @@ export const deliverySlotInfo = sqliteTable('delivery_slot_info', {
isCapacityFull: integer('is_capacity_full', { mode: 'boolean' }).notNull().default(false),
deliverySequence: jsonText<Record<string, number>>('delivery_sequence').$defaultFn(() => ({})),
groupIds: jsonText<number[]>('group_ids').$defaultFn(() => []),
productIds: jsonText<number[]>('product_ids').$defaultFn(() => []),
skuIds: jsonText<number[]>('sku_ids').$defaultFn(() => []),
})
export const vendorSnippets = sqliteTable('vendor_snippets', {
@ -288,14 +302,14 @@ export const vendorSnippets = sqliteTable('vendor_snippets', {
snippetCode: text('snippet_code').notNull().unique(),
slotId: integer('slot_id').references(() => deliverySlotInfo.id),
isPermanent: integer('is_permanent', { mode: 'boolean' }).notNull().default(false),
productIds: jsonText<number[]>('product_ids').notNull(),
skuIds: jsonText<number[]>('sku_ids').notNull(),
validTill: timestampText('valid_till'),
createdAt: timestampText('created_at').notNull().default(sql`CURRENT_TIMESTAMP`),
})
export const specialDeals = sqliteTable('special_deals', {
id: integer().primaryKey({ autoIncrement: true }),
productId: integer('product_id').notNull().references(() => productInfo.id),
skuId: integer('sku_id').notNull().references(() => productSkus.id),
quantity: numericText('quantity').notNull(),
price: numericText('price').notNull(),
validTill: timestampText('valid_till').notNull(),
@ -333,7 +347,7 @@ export const orders = sqliteTable('orders', {
export const orderItems = sqliteTable('order_items', {
id: integer().primaryKey({ autoIncrement: true }),
orderId: integer('order_id').notNull().references(() => orders.id),
productId: integer('product_id').notNull().references(() => productInfo.id),
skuId: integer('sku_id').notNull().references(() => productSkus.id),
quantity: text('quantity').notNull(),
price: numericText('price').notNull(),
discountedPrice: numericText('discounted_price'),
@ -403,11 +417,11 @@ export const productCategories = sqliteTable('product_categories', {
export const cartItems = sqliteTable('cart_items', {
id: integer().primaryKey({ autoIncrement: true }),
userId: integer('user_id').notNull().references(() => users.id),
productId: integer('product_id').notNull().references(() => productInfo.id),
skuId: integer('sku_id').notNull().references(() => productSkus.id),
quantity: numericText('quantity').notNull(),
addedAt: timestampText('added_at').notNull().default(sql`CURRENT_TIMESTAMP`),
}, (t) => ({
unq_user_product: uniqueIndex('unique_user_product').on(t.userId, t.productId),
unq_user_sku: uniqueIndex('unique_user_sku').on(t.userId, t.skuId),
}))
export const complaints = sqliteTable('complaints', {
@ -428,7 +442,7 @@ export const coupons = sqliteTable('coupons', {
discountPercent: numericText('discount_percent'),
flatDiscount: numericText('flat_discount'),
minOrder: numericText('min_order'),
productIds: jsonText<number[] | null>('product_ids'),
skuIds: jsonText<number[] | null>('sku_ids'),
createdBy: integer('created_by').notNull().references(() => staffUsers.id),
maxValue: numericText('max_value'),
isApplyForAll: integer('is_apply_for_all', { mode: 'boolean' }).notNull().default(false),
@ -459,9 +473,9 @@ export const couponApplicableUsers = sqliteTable('coupon_applicable_users', {
export const couponApplicableProducts = sqliteTable('coupon_applicable_products', {
id: integer().primaryKey({ autoIncrement: true }),
couponId: integer('coupon_id').notNull().references(() => coupons.id),
productId: integer('product_id').notNull().references(() => productInfo.id),
skuId: integer('sku_id').notNull().references(() => productSkus.id),
}, (t) => ({
unq_coupon_product: uniqueIndex('unique_coupon_product').on(t.couponId, t.productId),
unq_coupon_sku: uniqueIndex('unique_coupon_sku').on(t.couponId, t.skuId),
}))
export const userIncidents = sqliteTable('user_incidents', {
@ -481,7 +495,7 @@ export const reservedCoupons = sqliteTable('reserved_coupons', {
discountPercent: numericText('discount_percent'),
flatDiscount: numericText('flat_discount'),
minOrder: numericText('min_order'),
productIds: jsonText<number[] | null>('product_ids'),
skuIds: jsonText<number[] | null>('sku_ids'),
maxValue: numericText('max_value'),
validTill: timestampText('valid_till'),
maxLimitForUser: integer('max_limit_for_user'),
@ -548,20 +562,29 @@ export const addressesRelations = relations(addresses, ({ one, many }) => ({
zone: one(addressZones, { fields: [addresses.zoneId], references: [addressZones.id] }),
}))
export const unitsRelations = relations(units, ({ many }) => ({
products: many(productInfo),
export const unitsRelations = relations(units, ({}) => ({
// Units are no longer linked to products/SKUs; kept as a reference table.
}))
export const productInfoRelations = relations(productInfo, ({ one, many }) => ({
unit: one(units, { fields: [productInfo.unitId], references: [units.id] }),
store: one(storeInfo, { fields: [productInfo.storeId], references: [storeInfo.id] }),
skus: many(productSkus),
tags: many(productTags),
reviews: many(productReviews),
groups: many(productGroupMembership),
}))
export const productSkusRelations = relations(productSkus, ({ one, many }) => ({
product: one(productInfo, { fields: [productSkus.productId], references: [productInfo.id] }),
features: many(skuFeatures),
specialDeals: many(specialDeals),
orderItems: many(orderItems),
cartItems: many(cartItems),
tags: many(productTags),
applicableCoupons: many(couponApplicableProducts),
reviews: many(productReviews),
groups: many(productGroupMembership),
}))
export const skuFeaturesRelations = relations(skuFeatures, ({ one }) => ({
sku: one(productSkus, { fields: [skuFeatures.skuId], references: [productSkus.id] }),
}))
export const productTagInfoRelations = relations(productTagInfo, ({ many }) => ({
@ -579,7 +602,7 @@ export const deliverySlotInfoRelations = relations(deliverySlotInfo, ({ many })
}))
export const specialDealsRelations = relations(specialDeals, ({ one }) => ({
product: one(productInfo, { fields: [specialDeals.productId], references: [productInfo.id] }),
sku: one(productSkus, { fields: [specialDeals.skuId], references: [productSkus.id] }),
}))
export const ordersRelations = relations(orders, ({ one, many }) => ({
@ -597,7 +620,7 @@ export const ordersRelations = relations(orders, ({ one, many }) => ({
export const orderItemsRelations = relations(orderItems, ({ one }) => ({
order: one(orders, { fields: [orderItems.orderId], references: [orders.id] }),
product: one(productInfo, { fields: [orderItems.productId], references: [productInfo.id] }),
sku: one(productSkus, { fields: [orderItems.skuId], references: [productSkus.id] }),
}))
export const orderStatusRelations = relations(orderStatus, ({ one }) => ({
@ -626,7 +649,7 @@ export const productCategoriesRelations = relations(productCategories, ({}) => (
export const cartItemsRelations = relations(cartItems, ({ one }) => ({
user: one(users, { fields: [cartItems.userId], references: [users.id] }),
product: one(productInfo, { fields: [cartItems.productId], references: [productInfo.id] }),
sku: one(productSkus, { fields: [cartItems.skuId], references: [productSkus.id] }),
}))
export const complaintsRelations = relations(complaints, ({ one }) => ({
@ -672,7 +695,7 @@ export const couponApplicableUsersRelations = relations(couponApplicableUsers, (
export const couponApplicableProductsRelations = relations(couponApplicableProducts, ({ one }) => ({
coupon: one(coupons, { fields: [couponApplicableProducts.couponId], references: [coupons.id] }),
product: one(productInfo, { fields: [couponApplicableProducts.productId], references: [productInfo.id] }),
sku: one(productSkus, { fields: [couponApplicableProducts.skuId], references: [productSkus.id] }),
}))
export const reservedCouponsRelations = relations(reservedCoupons, ({ one }) => ({
@ -704,7 +727,7 @@ export const productGroupMembershipRelations = relations(productGroupMembership,
}))
export const homeBannersRelations = relations(homeBanners, ({}) => ({
// Relations for productIds array would be more complex, skipping for now
// Relations for skuIds array would be more complex, skipping for now
}))
export const staffRolesRelations = relations(staffRoles, ({ many }) => ({

View file

@ -4,6 +4,8 @@ import type {
addresses,
units,
productInfo,
productSkus,
skuFeatures,
deliverySlotInfo,
specialDeals,
orders,
@ -19,6 +21,8 @@ export type User = InferSelectModel<typeof users>
export type Address = InferSelectModel<typeof addresses>
export type Unit = InferSelectModel<typeof units>
export type ProductInfo = InferSelectModel<typeof productInfo>
export type ProductSku = InferSelectModel<typeof productSkus>
export type SkuFeature = InferSelectModel<typeof skuFeatures>
export type DeliverySlotInfo = InferSelectModel<typeof deliverySlotInfo>
export type SpecialDeal = InferSelectModel<typeof specialDeals>
export type Order = InferSelectModel<typeof orders>
@ -30,16 +34,16 @@ export type CartItem = InferSelectModel<typeof cartItems>
export type Coupon = InferSelectModel<typeof coupons>
// Combined types
export type ProductWithUnit = ProductInfo & {
unit: Unit
export type ProductWithSkus = ProductInfo & {
skus: (ProductSku & { features: SkuFeature[] })[]
}
export type OrderWithItems = Order & {
items: (OrderItem & { product: ProductInfo })[]
items: (OrderItem & { sku: ProductSku & { product: ProductInfo } })[]
address: Address
slot: DeliverySlotInfo
}
export type CartItemWithProduct = CartItem & {
product: ProductInfo
export type CartItemWithSku = CartItem & {
sku: ProductSku & { product: ProductInfo }
}