در توسعه، سرور Nuxt و Vite مسیرهای `/api` و `/media` را به API و MinIO پراکسی میکنند؛ در production همین کار را nginx انجام میدهد. به همین دلیل آدرس تصاویر همهجا بهصورت `/media/...` ذخیره میشود.
### اسکریپتها (از ریشهی پروژه)
| دستور | کار |
|---|---|
| `pnpm dev` | اجرای همهی اپها در حالت توسعه |
| `pnpm build` | build همهی پکیجها |
| `pnpm typecheck` | بررسی TypeScript همهی پکیجها |
- **محصولات**: افزودن/ویرایش، تصویر اصلی و گالری، توضیحات کامل، جدول مشخصات فنی، ویژگیهای کلیدی، کاربردها، استانداردها، سایزها و مدلها، برند و کشور سازنده، بستهبندی و حداقل سفارش، سئو، انتشار یا پیشنویس، کپی، مرتبسازی (با فیلتر یک دسته). هر محصول صفحهی اختصاصی `/products/<اسلاگ>` دارد (گالری با بزرگنمایی، مشخصات، محصولات مرتبط، دکمهی درخواست قیمت که فرم تماس را با نام محصول پر میکند، JSON-LD نوع Product). بخشهایی که پر نشدهاند در صفحه نمایش داده نمیشوند.
- **دستهبندیها**: کاور، زیرگروهها (فیلترهای صفحهی دسته)، سئو، ترتیب نمایش.
- **بخشهای سایت**: Hero، نوار متحرک، آمار، معرفی محصولات، درباره ما، خدمات، استانداردها، صنایع، نظرات، تماس، فوتر و تنظیمات سایت (تلفن، ایمیل، نشانی، ساعات کاری، شبکههای اجتماعی، سئوی پیشفرض).
- **پیامهای تماس**: درخواستهای فرم مشاورهی سایت، با وضعیت جدید / خواندهشده / بایگانی.
- **کتابخانه رسانه**: همهی تصاویر بهصورت خودکار بهینه و به WebP تبدیل میشوند. تصویری که جایی استفاده شده قابل حذف نیست.
- **کاربران و نقشها**: «مدیر کل» به همهچیز دسترسی دارد. «ویرایشگر» بهجز کاربران و تنظیمات سایت، همهی بخشها را میبیند.
تغییرات پس از ذخیره، حداکثر ظرف چند ثانیه روی سایت دیده میشوند. اگر فراخوانی پاککردن کش به هر دلیلی نرسد، صفحهها در هر صورت هر ۶۰ ثانیه تازه میشوند.
**امنیت ورود**: access token کوتاهعمر (۱۵ دقیقه) فقط در حافظهی مرورگر نگه داشته میشود و refresh token در کوکی httpOnly است. refresh token در هر استفاده عوض میشود. اگر یک توکن باطلشده دوباره استفاده شود، همهی نشستهای آن کاربر بسته میشوند. رمزها با argon2 ذخیره میشوند و تعداد تلاش برای ورود و ارسال فرم تماس محدود است.
## راهنمای توسعه
**افزودن فیلد به یک بخش سایت** (مثلاً یک متن جدید در Hero):
1. اسکیمای آن بخش را در `packages/shared/src/sections.ts` تغییر دهید. اگر فیلد اجباری است، مقدار پیشفرض بدهید (`text()`)، وگرنه دادههای فعلی دیتابیس دیگر معتبر نیستند.
2. فیلد را به فرم همان بخش در `apps/admin/src/sections/` اضافه کنید.
3. در کامپوننت سایت (`apps/web/app/components/sections/`) از آن استفاده کنید.
4. مقدار اولیه را در `apps/api/src/seed/data/sections.ts` اضافه کنید.
یک اسکیمای Zod هم ورودی API را اعتبارسنجی میکند و هم فرم پنل را. برای همین پیامهای خطا در هر دو یکسان و فارسیاند.
**تغییر مدل دیتابیس**: `apps/api/prisma/schema.prisma` را ویرایش کنید و `pnpm db:migrate` را اجرا کنید. اسم migration را میپرسد. فایل migration را هم commit کنید.
**آیکونها**: آیکونهای قابلانتخاب در پنل در `packages/shared/src/icons.ts` تعریف شدهاند. برای افزودن آیکون جدید، آن را به همین لیست، به `apps/web/app/components/ui/DynamicIcon.vue` و به `apps/admin/src/components/icons.ts` اضافه کنید. این تکرار عمدی است تا فقط همین آیکونها وارد باندل شوند.
**Ant Design و Tailwind**: استایلهای Tailwind v4 داخل cascade layer هستند و CSS-in-JS آنتدیزاین بیرون از layer. بنابراین روی کامپوننتهای antd کلاس ظاهری Tailwind نگذارید. در سایت، پالت رنگ در دو جا تعریف شده است: `@theme` در `main.css` و `palette` در `app/design/theme.ts`. هنگام تغییر، هر دو را همگام کنید.
## استقرار (production)
```bash
cp .env.example .env # همهی رمزها و دامنهها را تنظیم کنید
docker compose up -d --build
docker compose exec api node dist/seed/seed.js # فقط بار اول
```
- کانتینر `gateway` روی پورت `HTTP_PORT` (پیشفرض 8080) گوش میدهد و هر دو دامنه (`SITE_DOMAIN` و `ADMIN_DOMAIN`) را سرو میکند. TLS را nginx یا Traefik روی سرور یا CDN جلوی آن تأمین میکند. هر دو دامنه باید به سرور اشاره کنند.
- سرویس یکبارهی `migrate` پیش از `api` اجرا میشود، migrationهای جدید را اعمال میکند و خارج میشود. این کار را `dist/migrate.js` انجام میدهد که همارز `prisma migrate deploy` است و از همان جدول `_prisma_migrations` استفاده میکند، پس Prisma CLI روی سرور لازم نیست.
**روی سرور چیزی نصب یا build نمیشود.** imageها فقط خروجی آمادهای را که در ریپو commit شده کپی میکنند، پس سرور به رجیستری npm نیازی ندارد. قبل از هر push روی سیستم توسعه `pnpm build` بگیرید و خروجیها را هم commit کنید:
-`apps/web/.output` (همراه `server/node_modules`)، `apps/admin/dist` و `apps/api/.output`.
-`apps/api/.output` را `apps/api/scripts/bundle.mjs` میسازد. کد API و همهی وابستگیهای JS در آن bundle میشوند و فقط پکیجهای native (`sharp` و `@node-rs/argon2`) با باینری Linux (Alpine، x64) کنارش کپی میشوند. این باینریها را `supportedArchitectures` در `pnpm-workspace.yaml` موقع `pnpm install` دانلود میکند.
- آدرس سایت در لینکهای پنل ادمین موقع build از `apps/admin/.env.production` خوانده میشود.
**Object Storage ابرآروان (یا هر S3 دیگر) بهجای MinIO**: در سرویس `api`، مقادیر `S3_ENDPOINT`، `S3_BUCKET` و کلیدها را تنظیم کنید و `S3_ENSURE_BUCKET` را `false` بگذارید. `MEDIA_PUBLIC_URL` را روی آدرس عمومی باکت یا CDN بگذارید. بعد از آن به سرویس `minio` و مسیر `/media` در gateway نیازی نیست.
- **پر کردن جزئیات محصولات** (توضیحات کامل، مشخصات فنی، استانداردها و ...) از پنل؛ این فیلدها برای محصولات فعلی خالیاند و فعلاً صفحهی محصول توضیح دسته را نشان میدهد.
- عکس برای ۱۷ محصول پوشاک کار و تجهیزات ترافیک (فعلاً کاور دسته نمایش داده میشود).