Add dock category seed helper and product tag/prep fields.
Ensures chocolate and baklava exist for the website categories dock, and supports tag-filtered product lists for home carousels. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
d09eca8702
commit
5d1f8991f9
+2
-1
@@ -24,7 +24,8 @@
|
|||||||
"db:up": "docker compose up -d",
|
"db:up": "docker compose up -d",
|
||||||
"db:down": "docker compose down",
|
"db:down": "docker compose down",
|
||||||
"create-super-admin": "ts-node -r tsconfig-paths/register scripts/create-super-admin.ts",
|
"create-super-admin": "ts-node -r tsconfig-paths/register scripts/create-super-admin.ts",
|
||||||
"send-sms": "ts-node -r tsconfig-paths/register scripts/send-sms.ts"
|
"send-sms": "ts-node -r tsconfig-paths/register scripts/send-sms.ts",
|
||||||
|
"ensure:dock-categories": "ts-node --transpile-only scripts/ensure-dock-categories.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.1101.0",
|
"@aws-sdk/client-s3": "^3.1101.0",
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Product" ADD COLUMN "preparationHours" INTEGER;
|
||||||
@@ -165,6 +165,7 @@ model Product {
|
|||||||
category Category @relation(fields: [categoryId], references: [id], onDelete: Restrict)
|
category Category @relation(fields: [categoryId], references: [id], onDelete: Restrict)
|
||||||
intro String @default("")
|
intro String @default("")
|
||||||
description String @default("")
|
description String @default("")
|
||||||
|
preparationHours Int?
|
||||||
mainImageUrl String?
|
mainImageUrl String?
|
||||||
mainImageKey String?
|
mainImageKey String?
|
||||||
tags String[] @default([])
|
tags String[] @default([])
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { PrismaClient } from '@prisma/client'
|
||||||
|
|
||||||
|
const prisma = new PrismaClient()
|
||||||
|
|
||||||
|
const cats = [
|
||||||
|
{ nameFa: 'شکلات', nameEn: 'Chocolate', slug: 'chocolate', sortOrder: 10 },
|
||||||
|
{ nameFa: 'باقلوا', nameEn: 'Baklava', slug: 'baklava', sortOrder: 11 },
|
||||||
|
]
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
for (const cat of cats) {
|
||||||
|
await prisma.category.upsert({
|
||||||
|
where: { slug: cat.slug },
|
||||||
|
create: cat,
|
||||||
|
update: {
|
||||||
|
nameFa: cat.nameFa,
|
||||||
|
nameEn: cat.nameEn,
|
||||||
|
sortOrder: cat.sortOrder,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const roots = await prisma.category.findMany({
|
||||||
|
where: { parentId: null },
|
||||||
|
select: { slug: true, nameFa: true, nameEn: true },
|
||||||
|
orderBy: { sortOrder: 'asc' },
|
||||||
|
})
|
||||||
|
console.log(JSON.stringify(roots, null, 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err)
|
||||||
|
process.exitCode = 1
|
||||||
|
})
|
||||||
|
.finally(async () => {
|
||||||
|
await prisma.$disconnect()
|
||||||
|
})
|
||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
IsString,
|
IsString,
|
||||||
Min,
|
Min,
|
||||||
MinLength,
|
MinLength,
|
||||||
|
ValidateIf,
|
||||||
ValidateNested,
|
ValidateNested,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
|
|
||||||
@@ -61,6 +62,14 @@ export class CreateProductDto {
|
|||||||
@IsString()
|
@IsString()
|
||||||
description?: string;
|
description?: string;
|
||||||
|
|
||||||
|
/** null = ready immediately (no prep time) */
|
||||||
|
@IsOptional()
|
||||||
|
@ValidateIf((_, value) => value !== null && value !== undefined)
|
||||||
|
@Type(() => Number)
|
||||||
|
@IsInt()
|
||||||
|
@Min(1)
|
||||||
|
preparationHours?: number | null;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@IsString({ each: true })
|
@IsString({ each: true })
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ export class ListProductsQueryDto {
|
|||||||
@IsString()
|
@IsString()
|
||||||
categorySlug?: string;
|
categorySlug?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
tag?: string;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@Type(() => Number)
|
@Type(() => Number)
|
||||||
@IsInt()
|
@IsInt()
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
IsString,
|
IsString,
|
||||||
Min,
|
Min,
|
||||||
MinLength,
|
MinLength,
|
||||||
|
ValidateIf,
|
||||||
ValidateNested,
|
ValidateNested,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
import {
|
import {
|
||||||
@@ -47,6 +48,14 @@ export class UpdateProductDto {
|
|||||||
@IsString()
|
@IsString()
|
||||||
description?: string;
|
description?: string;
|
||||||
|
|
||||||
|
/** null = ready immediately (no prep time) */
|
||||||
|
@IsOptional()
|
||||||
|
@ValidateIf((_, value) => value !== null && value !== undefined)
|
||||||
|
@Type(() => Number)
|
||||||
|
@IsInt()
|
||||||
|
@Min(1)
|
||||||
|
preparationHours?: number | null;
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@IsString({ each: true })
|
@IsString({ each: true })
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ export class ProductsService {
|
|||||||
|
|
||||||
const where = {
|
const where = {
|
||||||
...(categoryIds ? { categoryId: { in: categoryIds } } : {}),
|
...(categoryIds ? { categoryId: { in: categoryIds } } : {}),
|
||||||
|
...(query.tag?.trim()
|
||||||
|
? { tags: { has: query.tag.trim() } }
|
||||||
|
: {}),
|
||||||
...(query.minPrice !== undefined || query.maxPrice !== undefined
|
...(query.minPrice !== undefined || query.maxPrice !== undefined
|
||||||
? {
|
? {
|
||||||
price: {
|
price: {
|
||||||
@@ -100,6 +103,7 @@ export class ProductsService {
|
|||||||
sellUnit: dto.sellUnit,
|
sellUnit: dto.sellUnit,
|
||||||
intro: dto.intro ?? '',
|
intro: dto.intro ?? '',
|
||||||
description: dto.description ?? '',
|
description: dto.description ?? '',
|
||||||
|
preparationHours: dto.preparationHours ?? null,
|
||||||
tags: dto.tags ?? [],
|
tags: dto.tags ?? [],
|
||||||
mainImageUrl: dto.mainImageUrl,
|
mainImageUrl: dto.mainImageUrl,
|
||||||
mainImageKey: dto.mainImageKey,
|
mainImageKey: dto.mainImageKey,
|
||||||
@@ -178,6 +182,9 @@ export class ProductsService {
|
|||||||
...(dto.sellUnit !== undefined ? { sellUnit: dto.sellUnit } : {}),
|
...(dto.sellUnit !== undefined ? { sellUnit: dto.sellUnit } : {}),
|
||||||
...(dto.intro !== undefined ? { intro: dto.intro } : {}),
|
...(dto.intro !== undefined ? { intro: dto.intro } : {}),
|
||||||
...(dto.description !== undefined ? { description: dto.description } : {}),
|
...(dto.description !== undefined ? { description: dto.description } : {}),
|
||||||
|
...(dto.preparationHours !== undefined
|
||||||
|
? { preparationHours: dto.preparationHours }
|
||||||
|
: {}),
|
||||||
...(dto.tags !== undefined ? { tags: dto.tags } : {}),
|
...(dto.tags !== undefined ? { tags: dto.tags } : {}),
|
||||||
...(dto.mainImageUrl !== undefined
|
...(dto.mainImageUrl !== undefined
|
||||||
? { mainImageUrl: dto.mainImageUrl }
|
? { mainImageUrl: dto.mainImageUrl }
|
||||||
|
|||||||
Reference in New Issue
Block a user