S. EsmaeiliandClaude Opus 5.5 789d224ea0 feat: monorepo with NestJS API, admin panel and DB-driven website
Restructure the repo into a pnpm + Turborepo monorepo:

- apps/web: Nuxt 4 site, now server-rendered and reading all content
  from the API; pages cached with SWR and purged on content changes
- apps/api: NestJS 12 + Prisma 7 + PostgreSQL. Auth with short-lived
  access tokens and rotating httpOnly refresh cookies, roles (ADMIN,
  EDITOR), catalog, editable site sections, media upload to S3-compatible
  storage (converted to WebP), contact messages, audit log
- apps/admin: Vue 3 admin panel (Ant Design Vue, Pinia, Tailwind) for
  products, categories, site sections, messages, media and users
- packages/shared: Zod schemas and types shared by API and admin

Also:
- seed script that loads the original 8 categories / 89 products and
  section content
- Docker images for api (with a one-off migrate service), web, admin,
  and an nginx gateway for both domains
- dev and production compose files, daily database backup service
- API e2e tests

Removes the committed static out/ build and the old root Dockerfile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 20:31:05 +03:30

ایمن پیشگامان — مونوریپو (سایت + 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.

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_EMAIL / 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.

پنل مدیریت

  • محصولات: افزودن/ویرایش، آپلود تصویر اصلی و گالری، ویژگی‌ها و مدل‌ها، انتشار یا پیش‌نویس، کپی، مرتب‌سازی (با فیلتر یک دسته).
  • دسته‌بندی‌ها: کاور، زیرگروه‌ها (فیلترهای صفحه‌ی دسته)، سئو، ترتیب نمایش.
  • بخش‌های سایت: 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)

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ها را روی سیستم توسعه بسازید و به سرور منتقل کنید:
    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 نیازی نیست.

کارهای باقی‌مانده و پیشنهادها

  • صفحه‌ی جداگانه برای هر محصول (/products/[slug]) با JSON-LD نوع Product. فیلد slug و گالری محصول از الان در دیتابیس هست.
  • ایمپورت/اکسپورت اکسل محصولات برای ورود دسته‌جمعی.
  • اعلان پیام تماس جدید با ایمیل، پیامک یا ربات بله/تلگرام.
  • پوشه‌ی محصولات/ (عکس‌ها و فایل‌های 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).
S
Description
No description provided
Readme
29 MiB
Languages
HTML 80.4%
Vue 10.4%
TypeScript 8.1%
CSS 1.1%