grand commit
This commit is contained in:
67
lib/config/resources.ts
Normal file
67
lib/config/resources.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import {ResourceType} from '@prisma/client'
|
||||
import im, {Features} from 'imagemagick'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
import {db} from '@/lib/db/prisma/client'
|
||||
|
||||
export const PUBLIC_UPLOAD_PRODUCTS_DIR = '/uploads/products'
|
||||
export const UPLOAD_PRODUCTS_DIR = path.resolve(
|
||||
`./public${PUBLIC_UPLOAD_PRODUCTS_DIR}`
|
||||
)
|
||||
|
||||
export function getFilesByProductId(id: number, fullPath: boolean = true) {
|
||||
const files = fs
|
||||
.readdirSync(UPLOAD_PRODUCTS_DIR)
|
||||
.filter(file => file.startsWith(`${id}-`))
|
||||
|
||||
return !fullPath
|
||||
? files
|
||||
: files.map(file => path.join(UPLOAD_PRODUCTS_DIR, file))
|
||||
}
|
||||
|
||||
interface ProductImage {
|
||||
type: string
|
||||
productId: number
|
||||
filesize: number
|
||||
height: number
|
||||
width: number
|
||||
'mime type'?: string
|
||||
mimeType: string
|
||||
quality: number
|
||||
properties: {
|
||||
signature: string
|
||||
}
|
||||
signature: string
|
||||
}
|
||||
|
||||
export async function getMetaOfFile(id: number) {
|
||||
getFilesByProductId(id).forEach(file => {
|
||||
im.identify(file, async (err, features) => {
|
||||
if (err) throw err
|
||||
// { format: 'JPEG', width: 3904, height: 2622, depth: 8 }
|
||||
const data = features as ProductImage
|
||||
|
||||
try {
|
||||
const result = await db.productResource.create({
|
||||
data: {
|
||||
type: 'IMAGE' as ResourceType,
|
||||
productId: id,
|
||||
uri: PUBLIC_UPLOAD_PRODUCTS_DIR + '/' + path.basename(file),
|
||||
filesize: parseInt(data.filesize as unknown as string),
|
||||
height: data.height,
|
||||
width: data.width,
|
||||
quality: data.quality,
|
||||
mimeType: data['mime type'] || 'image/*',
|
||||
signature: data.properties.signature
|
||||
//meta: features
|
||||
}
|
||||
})
|
||||
|
||||
//return result
|
||||
} catch (e) {
|
||||
//console.log(e)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -5,7 +5,7 @@ export const data = {
|
||||
{
|
||||
name: 'Про нас',
|
||||
slug: slug('Про нас'),
|
||||
href: '/search?tag='
|
||||
href: '/about-us'
|
||||
},
|
||||
{
|
||||
name: "Цікаво про здоров'я",
|
||||
|
||||
22
lib/data/models/category.ts
Normal file
22
lib/data/models/category.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
'use server'
|
||||
|
||||
import {CategoryLocale} from '@prisma/client'
|
||||
|
||||
import {STORE_ID} from '@/lib/config/constants'
|
||||
import {db} from '@/lib/db/prisma/client'
|
||||
|
||||
export const getCategoryBySlug = async (slug: string) => {
|
||||
return db.categoryLocale.findFirst({
|
||||
where: {
|
||||
slug
|
||||
},
|
||||
select: {
|
||||
category: {
|
||||
include: {
|
||||
locales: true,
|
||||
categoriesOnProducts: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,12 +1,23 @@
|
||||
'use server'
|
||||
|
||||
import {Lang, Product, ProductLocale, ProductToStore} from '@prisma/client'
|
||||
import {
|
||||
CategoriesOnProducts,
|
||||
CategoryLocale,
|
||||
Lang,
|
||||
Product,
|
||||
ProductLocale,
|
||||
ProductResource,
|
||||
ProductToStore
|
||||
} from '@prisma/client'
|
||||
|
||||
import {STORE_ID} from '@/lib/config/constants'
|
||||
import {db, dbQueryLog} from '@/lib/db/prisma/client'
|
||||
|
||||
export interface ProductProps extends Product {
|
||||
locales: ProductLocale[]
|
||||
toStore: ProductToStore[]
|
||||
categoriesOnProducts: CategoriesOnProducts[]
|
||||
resources: ProductResource[]
|
||||
}
|
||||
|
||||
export const getProductBySlug = async (data: {
|
||||
@@ -25,7 +36,27 @@ export const getProductById = async (id: unknown): Promise<Product | null> => {
|
||||
return db.product.findUnique({
|
||||
where: {id: id as number},
|
||||
include: {
|
||||
resources: true,
|
||||
categoriesOnProducts: {
|
||||
where: {
|
||||
storeId: STORE_ID
|
||||
},
|
||||
include: {
|
||||
category: {
|
||||
include: {
|
||||
locales: {
|
||||
orderBy: {
|
||||
lang: 'asc'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
locales: {
|
||||
orderBy: {
|
||||
lang: 'asc'
|
||||
},
|
||||
include: {
|
||||
meta: true
|
||||
}
|
||||
@@ -39,6 +70,9 @@ export const getProducts = async (): Promise<Product[] | null> => {
|
||||
return db.product.findMany({
|
||||
include: {
|
||||
locales: {
|
||||
orderBy: {
|
||||
lang: 'asc'
|
||||
},
|
||||
omit: {
|
||||
description: true,
|
||||
content: true,
|
||||
@@ -52,3 +86,13 @@ export const getProducts = async (): Promise<Product[] | null> => {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const getProductResources = async (
|
||||
productId: number | null
|
||||
): Promise<ProductResource[] | null> => {
|
||||
return db.productResource.findMany({
|
||||
where: {
|
||||
productId: productId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
23
lib/data/models/sqlSchemas.ts
Normal file
23
lib/data/models/sqlSchemas.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import Decimal from 'decimal.js'
|
||||
|
||||
import {i18nLocalesCodes} from '@/i18n-config'
|
||||
import {locales} from '@/i18n/routing'
|
||||
|
||||
export type CategoryPageSqlSchema = {
|
||||
productId: number
|
||||
lang: string
|
||||
slug?: string | null | undefined
|
||||
image?: string | null | undefined
|
||||
imageWidth?: number | null | undefined
|
||||
imageHeight?: number | null | undefined
|
||||
categoryId?: number
|
||||
title: string
|
||||
shortTitle?: string | null | undefined
|
||||
description?: string | null | undefined
|
||||
content?: string | null | undefined
|
||||
headingTitle?: string | null | undefined
|
||||
instruction?: string | null | undefined
|
||||
categorySlug?: string | null | undefined
|
||||
categoryTitle?: string | null | undefined
|
||||
price?: string | null | Decimal
|
||||
}
|
||||
@@ -6,7 +6,7 @@ model Category {
|
||||
storeId Int @map("store_id")
|
||||
locales CategoryLocale[]
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
categoriesOnPruducts CategoriesOnProducts[]
|
||||
categoriesOnProducts CategoriesOnProducts[]
|
||||
|
||||
@@map("categories")
|
||||
}
|
||||
@@ -36,5 +36,5 @@ model CategoriesOnProducts {
|
||||
categoryId Int @map("category_id")
|
||||
|
||||
@@id([storeId, productId, categoryId])
|
||||
@@map("categories_on_pruducts")
|
||||
@@map("categories_on_products")
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ model ProductLocale {
|
||||
shortTitle String? @map("short_title")
|
||||
headingTitle String? @map("heading_title")
|
||||
description String? @db.MediumText
|
||||
content String @db.MediumText
|
||||
content String? @db.MediumText
|
||||
instruction String? @db.MediumText
|
||||
product Product @relation(fields: [productId], references: [id])
|
||||
productId Int @map("product_id")
|
||||
@@ -40,12 +40,13 @@ model ProductResource {
|
||||
id Int @id @default(autoincrement())
|
||||
type ResourceType
|
||||
mimeType String? @map("mime_type")
|
||||
isFeature Boolean? @default(false)
|
||||
filesize Int? @db.UnsignedInt
|
||||
width Int? @db.UnsignedSmallInt
|
||||
height Int? @db.UnsignedSmallInt
|
||||
quality Int? @db.UnsignedTinyInt
|
||||
signature String? @db.Char(64)
|
||||
uri String @db.Text
|
||||
quality Float? @db.Float
|
||||
signature String @db.Char(64)
|
||||
uri String @db.VarChar(1024)
|
||||
title String? @db.VarChar(255)
|
||||
description String? @db.Text
|
||||
meta Json? @db.Json
|
||||
@@ -54,6 +55,7 @@ model ProductResource {
|
||||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(3)
|
||||
updatedAt DateTime? @default(dbgenerated("NULL DEFAULT NULL ON UPDATE current_timestamp(3)")) @map("updated_at") @db.Timestamp(3)
|
||||
|
||||
@@unique([productId, signature])
|
||||
@@map("product_resources")
|
||||
}
|
||||
|
||||
@@ -85,15 +87,16 @@ model ProductAttribute {
|
||||
}
|
||||
|
||||
model ProductToStore {
|
||||
id Int @id @default(autoincrement())
|
||||
position Int @default(0)
|
||||
published Boolean @default(false)
|
||||
available Boolean @default(false)
|
||||
price Decimal @default(0) @db.Decimal(7, 2)
|
||||
pricePromotional Decimal @default(0) @map("price_promotional") @db.Decimal(7, 2)
|
||||
productId Int @map("product_id")
|
||||
storeId Int @map("store_id")
|
||||
product Product @relation(fields: [productId], references: [id])
|
||||
id Int @id @default(autoincrement())
|
||||
position Int @default(0)
|
||||
published Boolean @default(false)
|
||||
available Boolean @default(false)
|
||||
price Decimal @default(0) @db.Decimal(7, 2)
|
||||
pricePromotional Decimal @default(0) @map("price_promotional") @db.Decimal(7, 2)
|
||||
productId Int @map("product_id")
|
||||
storeId Int @map("store_id")
|
||||
product Product @relation(fields: [productId], references: [id])
|
||||
createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(3)
|
||||
|
||||
@@unique([storeId, productId])
|
||||
@@map("product_to_store")
|
||||
|
||||
20
lib/db/prisma/sql/getCatalogIndexData.sql
Normal file
20
lib/db/prisma/sql/getCatalogIndexData.sql
Normal file
@@ -0,0 +1,20 @@
|
||||
SELECT DISTINCT
|
||||
p.id as productId,
|
||||
pl.lang,
|
||||
pl.slug,
|
||||
pr.uri as image,
|
||||
pr.width as imageWidth,
|
||||
pr.height as imageHeight,
|
||||
pts.price,
|
||||
pts.price_promotional as pricePromotional,
|
||||
pl.title,
|
||||
pl.short_title as shortTitle,
|
||||
pl.description
|
||||
-- pl.content, pl.heading_title as headingTitle, pl.instruction
|
||||
FROM products p
|
||||
JOIN categories_on_products cop ON p.id = cop.product_id
|
||||
JOIN product_locale pl ON pl.product_id = p.id
|
||||
JOIN product_to_store pts ON pts.product_id = p.id AND pts.store_id = cop.store_id
|
||||
LEFT JOIN product_resources pr ON p.id = pr.product_id AND pr.isFeature = TRUE
|
||||
WHERE cop.store_id = 1 AND pl.lang = ?
|
||||
ORDER BY p.id DESC;
|
||||
26
lib/db/prisma/sql/getCategoryBySlugWitData.sql
Normal file
26
lib/db/prisma/sql/getCategoryBySlugWitData.sql
Normal file
@@ -0,0 +1,26 @@
|
||||
SELECT
|
||||
p.id as productId,
|
||||
pl.lang,
|
||||
pl.slug,
|
||||
pr.uri as image,
|
||||
pr.width as imageWidth,
|
||||
pr.height as imageHeight,
|
||||
pts.price,
|
||||
pts.price_promotional as pricePromotional,
|
||||
cl.category_id as categoryId,
|
||||
pl.title,
|
||||
pl.short_title as shortTitle,
|
||||
pl.description,
|
||||
-- pl.content,
|
||||
pl.heading_title as headingTitle,
|
||||
-- pl.instruction,
|
||||
cl.slug as categorySlug,
|
||||
cl.title as categoryTitle
|
||||
FROM products p
|
||||
JOIN categories_on_products cop ON p.id = cop.product_id
|
||||
JOIN product_locale pl ON pl.product_id = p.id
|
||||
JOIN category_locales cl ON cl.category_id = cop.category_id
|
||||
JOIN product_to_store pts ON pts.product_id = p.id AND pts.store_id = cop.store_id
|
||||
LEFT JOIN product_resources pr ON p.id = pr.product_id AND pr.isFeature = TRUE
|
||||
WHERE cop.store_id = 1 AND cl.slug = ?
|
||||
ORDER BY pl.lang;
|
||||
26
lib/db/prisma/sql/getProductByIdWitData.sql
Normal file
26
lib/db/prisma/sql/getProductByIdWitData.sql
Normal file
@@ -0,0 +1,26 @@
|
||||
SELECT DISTINCT
|
||||
p.id as productId,
|
||||
pl.lang,
|
||||
pl.slug,
|
||||
pr.uri as image,
|
||||
pr.width as imageWidth,
|
||||
pr.height as imageHeight,
|
||||
pts.price,
|
||||
pts.price_promotional as pricePromotional,
|
||||
cl.category_id as categoryId,
|
||||
pl.title,
|
||||
pl.short_title as shortTitle,
|
||||
pl.description,
|
||||
pl.content,
|
||||
pl.heading_title as headingTitle,
|
||||
pl.instruction,
|
||||
cl.slug as categorySlug,
|
||||
cl.title as categoryTitle
|
||||
FROM products p
|
||||
JOIN categories_on_products cop ON p.id = cop.product_id
|
||||
JOIN product_locale pl ON pl.product_id = p.id
|
||||
JOIN category_locales cl ON cl.category_id = cop.category_id
|
||||
JOIN product_to_store pts ON pts.product_id = p.id AND pts.store_id = cop.store_id
|
||||
LEFT JOIN product_resources pr ON p.id = pr.product_id AND pr.isFeature = TRUE
|
||||
WHERE cop.store_id = 1 AND p.id = ?
|
||||
ORDER BY pl.lang
|
||||
15
lib/utils.ts
15
lib/utils.ts
@@ -1,9 +1,12 @@
|
||||
import {Prisma} from '@prisma/client'
|
||||
import bcrypt from 'bcryptjs'
|
||||
import {type ClassValue, clsx} from 'clsx'
|
||||
import {getLocale} from 'next-intl/server'
|
||||
import slugify from 'slugify'
|
||||
import {twMerge} from 'tailwind-merge'
|
||||
|
||||
import {i18nDefaultLocale} from '@/i18n-config'
|
||||
|
||||
/**
|
||||
* Just output dump using pretty output
|
||||
*
|
||||
@@ -16,6 +19,8 @@ export function dump(variable: any): [string, string] {
|
||||
]
|
||||
}
|
||||
|
||||
export const toPrice = (price: any) => parseFloat(price).toFixed(2)
|
||||
|
||||
/**
|
||||
* Create fallback avatar for showing during login process or in case if empty
|
||||
*
|
||||
@@ -118,6 +123,16 @@ export const toEmptyParams = (data: object | object[]) => {
|
||||
return result
|
||||
}
|
||||
|
||||
export const thisLocales = async (locales: any) => {
|
||||
const loc = await getLocale()
|
||||
return locales.filter((locale: any) => locale.lang === loc)
|
||||
}
|
||||
|
||||
export const thisLocale = async (locales: any) => {
|
||||
const loc = await getLocale()
|
||||
return locales.find((locale: any) => locale.lang === loc)
|
||||
}
|
||||
|
||||
export const dbErrorHandling = (e: unknown, message?: string | null) => {
|
||||
if (e instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
return {error: `${e.code}: ${e.message}`}
|
||||
|
||||
Reference in New Issue
Block a user