Files
imen_pishghaman/README.md
T
S. EsmaeiliandClaude Haiku 5.5 10e453e4be build: ship pre-built API bundle; images copy output instead of installing
- API: `pnpm build` bundles dist/ into apps/api/.output (esbuild), with
  Linux musl native packages for sharp and argon2 kept alongside
- docker/api.Dockerfile copies that output; no pnpm install on the server
- add dist/migrate.js (prisma migrate deploy equivalent, same
  _prisma_migrations table and lock) so the Prisma CLI is not needed at runtime
- pnpm-workspace: supportedArchitectures for linux/x64/musl
- admin: apps/admin/.env.production sets VITE_SITE_URL for the production build
- drop NPM_REGISTRY / SITE_URL build args from compose and .env.example
- gitignore: anchor node_modules rules so pre-built node_modules are committed
- README: describe the pre-built deploy flow

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-24 14:41:31 +03:30

151 lines
14 KiB
Markdown

# ایمن پیشگامان — مونوریپو (سایت + API + پنل مدیریت)
سایت ایمن پیشگامان به‌همراه بکند و پنل مدیریت محتوا. همه‌ی متن‌ها، تصاویر، دسته‌ها و محصولات سایت از پنل مدیریت قابل ویرایش‌اند.
| بخش | مسیر | فناوری |
|---|---|---|
| سایت | `apps/web` | Nuxt 4 (SSR + کش SWR)، Vue 3، Tailwind v4، Ant Design Vue، Pinia |
| API | `apps/api` | NestJS 12، Prisma 7، PostgreSQL، ذخیره‌ساز S3 (MinIO) |
| پنل مدیریت | `apps/admin` | Vue 3 + Vite، Ant Design Vue، Tailwind v4، Pinia، Vue Router |
| کد مشترک | `packages/shared` | اسکیماهای Zod و تایپ‌ها (مشترک بین هر سه اپ) |
ابزار مونوریپو: **pnpm workspaces + Turborepo**.
```
مرورگر ──► gateway (nginx)
├─ imenpishgaman.ir → web (Nuxt SSR، صفحات کش‌شده)
├─ admin.imenpishgaman.ir → admin (فایل‌های استاتیک)
├─ /api/* → api (NestJS) ──► PostgreSQL
└─ /media/* → MinIO (تصاویر آپلودشده)
ذخیره در پنل ──► API ──► POST web:/_internal/revalidate ──► کش صفحات سایت پاک می‌شود
```
## ساختار پوشه‌ها
```
apps/
web/ سایت (همان طراحی قبلی؛ داده‌ها از API)
app/stores/site.ts محتوای سایت از GET /api/public/site (یک بار در هر رندر)
app/plugins/api.ts $api — در SSR آدرس داخلی API، در مرورگر /api
server/routes/ sitemap.xml (از دیتابیس)، robots.txt، manifest، _internal/revalidate
api/
prisma/schema.prisma مدل داده؛ migrations/ کنار آن
src/ ماژول‌ها: auth, users, catalog, media, sections, contact, public, dashboard
src/seed/ مقداردهی اولیه (محتوای فعلی سایت + ۸ دسته / ۸۹ محصول)
seed-assets/ تصاویر اولیه که seed در MinIO آپلود می‌کند
test/ تست‌های e2e
admin/
src/pages/ داشبورد، محصولات، دسته‌ها، بخش‌های سایت، پیام‌ها، رسانه، کاربران
src/sections/ فرم اختصاصی هر بخش سایت
packages/shared/src/ sections.ts (اسکیمای بخش‌ها)، catalog.ts، admin.ts، icons.ts
docker/ Dockerfile هر اپ و پیکربندی nginx
docker-compose.yml استقرار production
docker-compose.dev.yml PostgreSQL + MinIO برای توسعه
```
## راه‌اندازی محیط توسعه
پیش‌نیاز: Node.js 22.19 یا جدیدتر (۲۴ توصیه می‌شود)، Docker.
```bash
corepack enable pnpm # در ویندوز بدون دسترسی ادمین:
# corepack enable pnpm --install-directory "%APPDATA%\npm"
pnpm install
pnpm infra:up # PostgreSQL روی 5433 و MinIO روی 9000 (کنسول: 9001)
cp apps/api/.env.example apps/api/.env
cp apps/web/.env.example apps/web/.env
pnpm db:migrate # ساخت جدول‌ها
pnpm db:seed # محتوای اولیه + کاربر مدیر (ADMIN_PHONE / ADMIN_PASSWORD در apps/api/.env)
pnpm dev # هر سه اپ با هم
```
| آدرس | چیست |
|---|---|
| http://localhost:3000 | سایت |
| http://localhost:5173 | پنل مدیریت |
| http://localhost:4000/api | API |
| http://localhost:9001 | کنسول MinIO |
در توسعه، سرور Nuxt و Vite مسیرهای `/api` و `/media` را به API و MinIO پراکسی می‌کنند؛ در production همین کار را nginx انجام می‌دهد. به همین دلیل آدرس تصاویر همه‌جا به‌صورت `/media/...` ذخیره می‌شود.
### اسکریپت‌ها (از ریشه‌ی پروژه)
| دستور | کار |
|---|---|
| `pnpm dev` | اجرای همه‌ی اپ‌ها در حالت توسعه |
| `pnpm build` | build همه‌ی پکیج‌ها |
| `pnpm typecheck` | بررسی TypeScript همه‌ی پکیج‌ها |
| `pnpm test` | تست‌های e2e بکند (روی دیتابیس توسعه؛ داده‌های تست پاک می‌شوند) |
| `pnpm db:migrate` | ساخت/اعمال migration (`prisma migrate dev`) |
| `pnpm db:seed` | مقداردهی اولیه؛ اجرای دوباره چیزی را بازنویسی نمی‌کند |
| `pnpm db:studio` | Prisma Studio برای دیدن داده‌ها |
| `pnpm infra:up` / `infra:down` | روشن/خاموش کردن PostgreSQL و MinIO |
برای برگرداندن متن‌های سایت به محتوای اولیه: `SEED_OVERWRITE_SECTIONS=1 pnpm db:seed`.
## پنل مدیریت
- **محصولات**: افزودن/ویرایش، تصویر اصلی و گالری، توضیحات کامل، جدول مشخصات فنی، ویژگی‌های کلیدی، کاربردها، استانداردها، سایزها و مدل‌ها، برند و کشور سازنده، بسته‌بندی و حداقل سفارش، سئو، انتشار یا پیش‌نویس، کپی، مرتب‌سازی (با فیلتر یک دسته). هر محصول صفحه‌ی اختصاصی `/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 روی سرور لازم نیست.
- نام پروژه‌ی Compose ثابت و برابر `imen` است، تا با `docker-compose.dev.yml` (PostgreSQL و MinIO توسعه) هیچ کانتینر یا volume مشترکی نداشته باشد.
- سرویس `backup` هر روز یک dump از دیتابیس در `./backups` می‌سازد و dumpهای قدیمی‌تر از ۱۴ روز را پاک می‌کند. بازگردانی:
`docker compose exec -T postgres pg_restore -U imen -d imen --clean < backups/imen-YYYY-MM-DD.dump`
- از فایل‌های MinIO (volume `miniodata`) هم جداگانه بکاپ بگیرید.
**روی سرور چیزی نصب یا 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 نیازی نیست.
## کارهای باقی‌مانده و پیشنهادها
- **پر کردن جزئیات محصولات** (توضیحات کامل، مشخصات فنی، استانداردها و ...) از پنل؛ این فیلدها برای محصولات فعلی خالی‌اند و فعلاً صفحه‌ی محصول توضیح دسته را نشان می‌دهد.
- عکس برای ۱۷ محصول پوشاک کار و تجهیزات ترافیک (فعلاً کاور دسته نمایش داده می‌شود).
- **ایمپورت/اکسپورت اکسل** محصولات برای ورود دسته‌جمعی.
- **اعلان پیام تماس جدید** با ایمیل، پیامک یا ربات بله/تلگرام.
- پوشه‌ی `محصولات/` (عکس‌ها و فایل‌های Word خام) و فایل‌های `_dev.log` و `landing-nextjs-seo.skill` بهتر است از ریپو خارج شوند.
- لوگوی رسمی (`apps/web/public/images/logo.png`)، تصویر اختصاصی Open Graph با ابعاد ۱۲۰۰×۶۳۰ و آیکون‌های PNG برای PWA.
- CI روی سرور git (`pnpm typecheck && pnpm build && pnpm test`).