Switch admin auth to mobile, restyle shell/login with Vuesax icons, and add product detail fields plus public product pages. Co-authored-by: Cursor <cursoragent@cursor.com>
156 lines
13 KiB
Markdown
156 lines
13 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های جدید را اعمال میکند و خارج میشود. Prisma CLI فقط در همین image هست و image اجرایی API سبک میماند.
|
|
- نام پروژهی 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`) هم جداگانه بکاپ بگیرید.
|
|
|
|
**اگر سرور به رجیستری npm دسترسی ندارد**، دو راه هست:
|
|
1. در `.env` مقدار `NPM_REGISTRY` را روی یک mirror در دسترس بگذارید.
|
|
2. imageها را روی سیستم توسعه بسازید و به سرور منتقل کنید:
|
|
```bash
|
|
docker compose build
|
|
docker save imen-api imen-migrate imen-web imen-admin | gzip > imen-images.tar.gz
|
|
# روی سرور:
|
|
gunzip -c imen-images.tar.gz | docker load && docker compose up -d
|
|
```
|
|
|
|
**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`).
|