fix: resolve backend TS compilation errors, startup order, and add clipboard paste upload

- Fix products.service.ts: ingredients -> ingredientList (Prisma relation name)
- Fix seed-blogs.ts: template literal refactor (array join) to avoid esbuild parse error
- Fix seed-custom.ts: category string -> connectOrCreate relation
- Add start-dev.js: sequential startup (backend first, then frontends)
- Add Ctrl+V paste upload to admin MediaSelector component
- Create missing images/vets/ and images/slider/ directories
This commit is contained in:
parsa aghaei 2026-07-11 13:51:12 +03:30
parent 0ea6c6d271
commit cd17a532d9
6 changed files with 153 additions and 50 deletions

View File

@ -20,28 +20,59 @@ async function main() {
});
}
const blog1Content = [
'<p>کلسیم یکی از مهم‌ترین مواد معدنی برای رشد ساختار استخوانی است. توله‌سگ‌ها در ماه‌های اول زندگی به کلسیم بیشتری نیاز دارند تا اسکلت‌بندی محکمی داشته باشند.</p>',
'',
'<h2>نسبت Ca:P (کلسیم به فسفر)</h2>',
'<p>نسبت ایده‌آل کلسیم به فسفر در رژیم توله‌سگ‌ها <strong>۱.۲:۱ تا ۱.۵:۱</strong> است. عدم تعادل در این نسبت می‌تواند منجر به ناهنجاری‌های استخوانی مانند پاهای پرانتزی و دیسپلازی هیپ شود. <strong>پودر پوسته تخم مرغ کانینو</strong> با ۳۶٪ کلسیم خالص و فسفر ۰.۱٪ بهترین گزینه برای تنظیم این نسبت است.</p>',
'',
'<h2>نقش ویتامین D3 در جذب کلسیم</h2>',
'<p>ویتامین D3 (کوله‌کلسیفرول) جذب کلسیم از روده را تا ۳۰٪ افزایش می‌دهد. <strong>مصرف همزمان ویتامین D2 (ارگوکلسیفرول) با D3 مجاز نیست</strong> زیرا این دو با یکدیگر رقابت کرده و جذب مؤثر را کاهش می‌دهند.</p>',
'',
'<h2>محصولات توصیه شده برای توله‌سگ‌ها</h2>',
'<ul>',
'<li><strong>CANHYDROX GAG:</strong> از ۸ هفتگی برای استحکام اسکلت و دندان‌ها (۲ قرص به ازای ۱۰kg تا ۱۵ ماهگی)</li>',
'<li><strong>Welpenmilch:</strong> شیر جایگزین مادر از روز اول (غنی از تورین و ویتامین‌ها)</li>',
'<li><strong>Welpenbrei:</strong> فرنی پروتئین بالا از هفته سوم</li>',
'<li><strong>Eierschalenpulver:</strong> پودر پوسته تخم مرغ برای تنظیم نسبت Ca:P</li>',
'</ul>',
'',
'<h2>هشدار برای نژادهای بزرگ</h2>',
'<p>نژادهای بزرگ (ژرمن شپرد، لابرادور، گلدن رتریور و ...) مستعد دیسپلازی هیپ و مشکلات مفصلی هستند. مصرف مکمل‌های ارتوپدی مانند <strong>Flexan</strong> (کلاژن هیدرولیز شده با صدف لب سبز) از دوران توله‌گی توصیه می‌شود.</p>'
].join('\n');
const blog2Content = [
'<p>ویتامین‌های B Complex (B1، B2، B6، B12 و ...) کوآنزیم‌های ضروری چرخه کربس هستند که انرژی سلولی (ATP) را تولید می‌کنند. کمبود این ویتامین‌ها منجر به خستگی مزمن، ریزش مو و التهابات پوستی می‌شود.</p>',
'',
'<h2>هر ویتامین B چه می‌کند؟</h2>',
'<ul>',
'<li><strong>B1 (تیامین):</strong> متابولیسم کربوهیدرات‌ها و عملکرد سیستم عصبی</li>',
'<li><strong>B2 (ریبوفلاوین):</strong> تولید انرژی و سلامت پوست</li>',
'<li><strong>B6 (پیریدوکسین):</strong> سنتز پروتئین و تولید گلبول قرمز</li>',
'<li><strong>B7 (بیوتین / ویتامین H):</strong> کراتین‌سازی مو و پنجه - <strong>Biotin-Tabs کانینو</strong> با ۱ میلیون میکروگرم بیوتین در کیلوگرم، قوی‌ترین دوز موجود در بازار است</li>',
'<li><strong>B12 (کوبالامین):</strong> خونسازی و عملکرد عصبی</li>',
'</ul>',
'',
'<h2>نقش بیوتین در سلامت پوشش</h2>',
'<p>بیوتین کوآنزیم کربوکسیلازهای مسئول سنتز اسیدهای چرب و گلوکونئوژنز است. <strong>ریزش مو و شکنندگی پنجه</strong> از نشانه‌های کلاسیک کمبود بیوتین هستند. قرص بیوتین کانینو با تأثیر از هفته چهارم، پوشش براق و پنجه‌های محکم را تضمین می‌کند.</p>',
'',
'<h2>محصولات تقویت‌کننده پوست و مو</h2>',
'<ul>',
'<li><strong>Biotin-Tabs:</strong> دوز بالای بیوتین برای ریزش مو</li>',
'<li><strong>Lachs-Öl:</strong> روغن سالمون با امگا ۳ (EPA/DHA) ۱۴٪</li>',
'<li><strong>Seealgen:</strong> جلبک دریایی ارگانیک برای تقویت رنگدانه</li>',
'<li><strong>Marine-Ölmischung PREMIUM:</strong> ترکیب روغن آرگان و گل رز با آنتی‌اکسیدان قوی</li>',
'</ul>',
'',
'<h2>توصیه تغذیه‌ای</h2>',
'<p>برای سلامت پوست و مو، ترکیب <strong>Biotin-Tabs</strong> + <strong>Lachs-Öl</strong> بهترین نتیجه را می‌دهد. بیوتین از داخل کراتین‌سازی را تقویت کرده و امگا ۳ التهاب پوستی را کاهش می‌دهد.</p>'
].join('\n');
const blogs = [
{
title: 'اهمیت کلسیم در رشد استخوان توله‌سگ‌ها',
slug: 'calcium-importance-for-puppies',
content: `<p>کلسیم یکی از مهم‌ترین مواد معدنی برای رشد ساختار استخوانی است. توله‌سگ‌ها در ماه‌های اول زندگی به کلسیم بیشتری نیاز دارند تا اسکلت‌بندی محکمی داشته باشند.</p>
<h2>نسبت Ca:P (کلسیم به فسفر)</h2>
<p>نسبت ایدهآل کلسیم به فسفر در رژیم تولهسگها <strong>۱:۱ تا ۱.۵:۱</strong> است. عدم تعادل در این نسبت میتواند منجر به ناهنجاریهای استخوانی مانند پاهای پرانتزی و دیسپلازی هیپ شود. <strong>پودر پوسته تخم مرغ کانینو</strong> با ۳۶٪ کلسیم خالص و فسفر ۰.۱٪ بهترین گزینه برای تنظیم این نسبت است.</p>
<h2>نقش ویتامین D3 در جذب کلسیم</h2>
<p>ویتامین D3 (کولهکلسیفرول) جذب کلسیم از روده را تا ۳۰٪ افزایش میدهد. <strong>مصرف همزمان ویتامین D2 (ارگوکلسیفرول) با D3 مجاز نیست</strong> زیرا این دو با یکدیگر رقابت کرده و جذب مؤثر را کاهش میدهند.</p>
<h2>محصولات توصیه شده برای تولهسگها</h2>
<ul>
<li><strong>CANHYDROX GAG:</strong> از ۸ هفتگی برای استحکام اسکلت و دندانها (۲ قرص به ازای ۱۰kg تا ۱۵ ماهگی)</li>
<li><strong>Welpenmilch:</strong> شیر جایگزین مادر از روز اول (غنی از تورین و ویتامینها)</li>
<li><strong>Welpenbrei:</strong> فرنی پروتئین بالا از هفته سوم</li>
<li><strong>Eierschalenpulver:</strong> پودر پوسته تخم مرغ برای تنظیم نسبت Ca:P</li>
</ul>
<h2>هشدار برای نژادهای بزرگ</h2>
<p>نژادهای بزرگ (ژرمن شپرد، لابرادور، گلدن رتریور و ...) مستعد دیسپلازی هیپ و مشکلات مفصلی هستند. مصرف مکملهای ارتوپدی مانند <strong>Flexan</strong> (کلاژن هیدرولیز شده با صدف لب سبز) از دوران تولهگی توصیه میشود.</p>`
content: blog1Content,
imageUrl: '/images/wiki/calcium.webp',
metaTitle: 'اهمیت کلسیم در رشد سگ',
metaDescription: 'چرا کلسیم برای توله سگ ها مهم است؟',
@ -51,30 +82,7 @@ async function main() {
{
title: 'ویتامین‌های گروه B و سلامت پوست و مو',
slug: 'b-vitamins-and-skin-health',
content: `<p>ویتامین‌های B Complex (B1، B2، B6، B12 و ...) کوآنزیم‌های ضروری چرخه کربس هستند که انرژی سلولی (ATP) را تولید می‌کنند. کمبود این ویتامین‌ها منجر به خستگی مزمن، ریزش مو و التهابات پوستی می‌شود.</p>
<h2>هر ویتامین B چه میکند؟</h2>
<ul>
<li><strong>B1 (تیامین):</strong> متابولیسم کربوهیدراتها و عملکرد سیستم عصبی</li>
<li><strong>B2 (ریبوفلاوین):</strong> تولید انرژی و سلامت پوست</li>
<li><strong>B6 (پیریدوکسین):</strong> سنتز پروتئین و تولید گلبول قرمز</li>
<li><strong>B7 (بیوتین / ویتامین H):</strong> کراتینسازی مو و پنجه - <strong>Biotin-Tabs کانینو</strong> با ۱ میلیون میکروگرم بیوتین در کیلوگرم، قویترین دوز موجود در بازار است</li>
<li><strong>B12 (کوبالامین):</strong> خونسازی و عملکرد عصبی</li>
</ul>
<h2>نقش بیوتین در سلامت پوشش</h2>
<p>بیوتین کوآنزیم کربوکسیلازهای مسئول سنتز اسیدهای چرب و گلوکونئوژنز است. <strong>ریزش مو و شکنندگی پنجه</strong> از نشانههای کلاسیک کمبود بیوتین هستند. قرص بیوتین کانینو با تأثیر از هفته چهارم، پوشش براق و پنجههای محکم را تضمین میکند.</p>
<h2>محصولات تقویتکننده پوست و مو</h2>
<ul>
<li><strong>Biotin-Tabs:</strong> دوز بالای بیوتین برای ریزش مو</li>
<li><strong>Lachs-Öl:</strong> روغن سالمون با امگا ۳ (EPA/DHA) ۱۴٪</li>
<li><strong>Seealgen:</strong> جلبک دریایی ارگانیک برای تقویت رنگدانه</li>
<li><strong>Marine-Ölmischung PREMIUM:</strong> ترکیب روغن آرگان و گل رز با آنتیاکسیدان قوی</li>
</ul>
<h2>توصیه تغذیهای</h2>
<p>برای سلامت پوست و مو، ترکیب <strong>Biotin-Tabs</strong> + <strong>Lachs-Öl</strong> بهترین نتیجه را میدهد. بیوتین از داخل کراتینسازی را تقویت کرده و امگا ۳ التهاب پوستی را کاهش میدهد.</p>`
content: blog2Content,
imageUrl: '/images/wiki/vitamins.webp',
metaTitle: 'ویتامین ب در سلامت پت',
metaDescription: 'تاثیر ویتامین بی بر پوست و مو',

View File

@ -70,10 +70,10 @@ async function main() {
create: {
artNo,
name,
slug: artNo,
scientificTagline,
description,
shortDescription,
category,
categorySlug,
priceValue,
priceDisplay,
@ -81,7 +81,8 @@ async function main() {
packageSize,
dosageLogic,
suitableFor,
imageUrl
imageUrl,
category: { connectOrCreate: { where: { slug: categorySlug }, create: { name: category, slug: categorySlug } } }
}
});

View File

@ -35,7 +35,7 @@ export class ProductsService {
take: limit,
orderBy: { [sortBy]: sortOrder },
include: {
ingredients: true,
ingredientList: true,
symptoms: true,
},
}),
@ -58,7 +58,7 @@ export class ProductsService {
return this.prisma.product.findFirst({
where: isUuid ? { id: idOrSlug } : { slug: idOrSlug },
include: {
ingredients: true,
ingredientList: true,
symptoms: true,
},
});

View File

@ -1,5 +1,5 @@
import { useState, useEffect, useRef } from 'react';
import { X, Upload, Image as ImageIcon, Trash2, CheckCircle2 } from 'lucide-react';
import { X, Upload, Image as ImageIcon, Trash2, CheckCircle2, Clipboard } from 'lucide-react';
import api from '../../services/api';
import Spinner from './Spinner';
@ -21,7 +21,9 @@ export default function MediaSelector({ isOpen, onClose, onSelect, multiple = fa
const [mediaList, setMediaList] = useState<Media[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [isUploading, setIsUploading] = useState(false);
const [pasteStatus, setPasteStatus] = useState<string | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const pasteTimerRef = useRef<ReturnType<typeof setTimeout>>();
useEffect(() => {
if (isOpen) {
@ -29,6 +31,44 @@ export default function MediaSelector({ isOpen, onClose, onSelect, multiple = fa
}
}, [isOpen]);
useEffect(() => {
if (!isOpen) return;
const handlePaste = (e: ClipboardEvent) => {
const items = e.clipboardData?.items;
if (!items) return;
for (const item of Array.from(items)) {
if (item.type.startsWith('image/')) {
e.preventDefault();
setPasteStatus('در حال آپلود از کلیپ‌بورد...');
const file = item.getAsFile();
if (!file) continue;
const formData = new FormData();
formData.append('file', file, `clipboard-${Date.now()}.png`);
api.post('/admin/media/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' }
}).then(() => {
setPasteStatus('آپلود موفق!');
fetchMedia();
}).catch(() => {
setPasteStatus('خطا در آپلود');
}).finally(() => {
if (pasteTimerRef.current) clearTimeout(pasteTimerRef.current);
pasteTimerRef.current = setTimeout(() => setPasteStatus(null), 3000);
});
break;
}
}
};
document.addEventListener('paste', handlePaste);
return () => {
document.removeEventListener('paste', handlePaste);
if (pasteTimerRef.current) clearTimeout(pasteTimerRef.current);
};
}, [isOpen]);
const fetchMedia = async () => {
try {
setIsLoading(true);
@ -100,8 +140,19 @@ export default function MediaSelector({ isOpen, onClose, onSelect, multiple = fa
{/* Toolbar */}
<div className="p-4 border-b border-gray-100 flex justify-between items-center bg-white">
<p className="text-sm font-bold text-gray-600">{mediaList.length} فایل موجود</p>
<div>
<div className="flex items-center gap-3">
<p className="text-sm font-bold text-gray-600">{mediaList.length} فایل موجود</p>
{pasteStatus && (
<span className={`text-xs px-2 py-1 rounded-full font-medium ${pasteStatus.includes('موفق') ? 'bg-green-100 text-green-700' : pasteStatus.includes('خطا') ? 'bg-red-100 text-red-700' : 'bg-blue-100 text-blue-700'}`}>
{pasteStatus}
</span>
)}
</div>
<div className="flex items-center gap-3">
<span className="text-xs text-gray-400 hidden sm:flex items-center gap-1">
<Clipboard className="w-3.5 h-3.5" />
Ctrl+V برای چسباندن تصویر
</span>
<input
type="file"
ref={fileInputRef}

View File

@ -7,7 +7,7 @@
"dev:backend": "cd backend && npm run start:dev",
"dev:frontend": "cd frontend/application && npm run dev",
"dev:admin": "cd frontend/admin-panel && npm run dev",
"dev": "concurrently -c \"blue,magenta,green,yellow\" -n \"backend,store,admin,open\" \"npm run dev:backend\" \"npm run dev:frontend\" \"npm run dev:admin\" \"node scripts/open-browsers.js\"",
"dev": "node scripts/start-dev.js"
"build:backend": "cd backend && npm run build",
"build:frontend": "cd frontend/application && npm run build",
"build:admin": "cd frontend/admin-panel && npm run build",

43
scripts/start-dev.js Normal file
View File

@ -0,0 +1,43 @@
const { spawn } = require('child_process');
const http = require('http');
const BACKEND_PORT = 4001;
const MAX_WAIT = 60000;
function waitForBackend(port, timeout) {
return new Promise((resolve, reject) => {
const start = Date.now();
function check() {
const req = http.get(`http://127.0.0.1:${port}/api/docs`, (res) => {
resolve();
});
req.on('error', () => {
if (Date.now() - start > timeout) {
reject(new Error(`Backend did not start within ${timeout/1000}s`));
} else {
setTimeout(check, 1000);
}
});
req.setTimeout(3000, () => { req.destroy(); });
}
setTimeout(check, 3000);
});
}
const backend = spawn('npm', ['run', 'dev:backend'], { stdio: 'inherit', shell: true });
waitForBackend(BACKEND_PORT, MAX_WAIT).then(() => {
const store = spawn('npm', ['run', 'dev:frontend'], { stdio: 'inherit', shell: true });
const admin = spawn('npm', ['run', 'dev:admin'], { stdio: 'inherit', shell: true });
const openBrowsers = spawn('node', ['scripts/open-browsers.js'], { stdio: 'inherit', shell: true });
process.on('SIGINT', () => {
backend.kill(); store.kill(); admin.kill(); openBrowsers.kill();
process.exit();
});
}).catch((err) => {
console.error(err.message);
backend.kill();
process.exit(1);
});