2026-09-12 10:56:03 +03:30
2026-09-12 10:56:03 +03:30

گروه ساختمانی آرامش — پروژه کامل (فرانت‌اند + بک‌اند)

مونوریپوی سایت گروه ساختمانی آرامش شامل:

  • frontend/ — سایت React + Vite + Tailwind (تمام متن‌ها و تصاویر از API خوانده می‌شود)
  • backend/ — بک‌اند FastAPI + SQLAlchemy 2 + PostgreSQL + JWT + MinIO

معماری

Traefik (پورت ۵۱۷۳ — تنها نقطه ورود)
├── /         →  Caddy (فایل‌های استاتیک SPA)
├── /api      →  FastAPI (بک‌اند)
└── /media    →  MinIO (باکت aramesh-media — تصاویر آپلودی)
  • همه محتوای متنی سایت در PostgreSQL است — هیچ متنی در کد فرانت‌اند هاردکد نشده؛ با یک درخواست GET /api/site بارگذاری و از پنل مدیریت ویرایش می‌شود
  • تصاویر آپلودی در MinIO (Object Storage سازگار با S3) ذخیره می‌شوند و با مسیر نسبی /media/... برگردانده می‌شوند — بک‌اند ذخیره‌سازی بدون تغییر محتوای دیتابیس قابل تعویض است
  • مسیریابی همه‌چیز روی Traefik است (با Docker labels) — nginx در پروژه وجود ندارد

ساختار

├── frontend/             # سایت React
│   ├── src/              # کد منبع (api.tsx = لایه اتصال به بک‌اند)
│   ├── Dockerfile        # بیلد Node + سرو استاتیک با Caddy
│   └── Caddyfile         # فقط سرو فایل‌های استاتیک (مسیریابی روی Traefik است)
├── backend/              # بک‌اند FastAPI
│   ├── app/              # کد منبع (آپلود تصویر: app/api/admin.py — سئوی سرور: app/seo_pages.py)
│   ├── tests/            # تست‌های API (pytest)
│   ├── requirements.txt
│   ├── Dockerfile
│   └── .env.example
├── scripts/backup.sh     # پشتیبان‌گیری دیتابیس و تصاویر
├── docker-compose.yml    # اجرای کامل با یک دستور
└── docker-compose.prod.yml  # لایه تولید: TLS با Let's Encrypt

اجرا با Docker (ساده‌ترین راه)

پیش‌نیاز: Docker Desktop. بیلد فرانت‌اند روی هاست انجام می‌شود (کانتینر فقط فایل‌های dist/ را سرو می‌کند)، پس اولین بار باید یک‌بار npm run build بزنید:

cp .env.example .env                      # مقادیر change-me را عوض کنید — بدون .env استک بالا نمی‌آید
cd frontend && npm install && npm run build && cd ..
docker compose up -d --build

سایت: http://localhost:5173 — ورود مدیریت: /admin/login (کاربر/رمز از ADMIN_* در .env)

  • مستندات API: http://localhost:8001/docs (با docker-compose.override.yml محلی)
  • کنسول MinIO: http://localhost:9001 — کاربر/رمز از متغیرهای MINIO_ROOT_* در .env
  • دیتابیس PostgreSQL و آبجکت‌استور MinIO هر دو در کانتینر جدا با volume دائمی
  • بک‌اند هنگام شروع، خودش جداول را می‌سازد و فقط جدول‌های خالی را seed می‌کند — ارتقای نسخه داده‌های فعلی را از بین نمی‌برد
  • در اولین اجرا، سرویس minio-setup باکت aramesh-media را می‌سازد، خواندن عمومی را فعال می‌کند و تصاویر داخل seed-media/ را در باکت می‌ریزد — پس یک کلون تازه هم همان تصاویر سایت را دارد

نکته برای تازه‌واردها: هیچ فایل رمزدار در گیت نیست. فقط .env.example را کپی کنید؛ فایل‌های .env/env در .gitignore هستند و هرگز commit نشوند.

انتشار واقعی (TLS)

برای بالا آوردن با HTTPS روی دامنه واقعی، لایه docker-compose.prod.yml را هم سوار کنید:

# در .env: DOMAIN=example.com و ACME_EMAIL=you@example.com
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
  • Traefik گواهی Let's Encrypt می‌گیرد و تمدید می‌کند؛ همه درخواست‌های HTTP به HTTPS هدایت می‌شوند
  • در این حالت SITE_URL=https://example.com را هم در .env بگذارید تا sitemap و آدرس‌های OG مطلق شوند

اجرای محلی (توسعه)

بک‌اند

cd backend
python -m venv .venv
.venv\Scripts\activate        # ویندوز — در لینوکس: source .venv/bin/activate
pip install -r requirements.txt

# فایل .env از روی .env.example بسازید و DATABASE_URL را تنظیم کنید
python -m app.seed            # ساخت جداول + محتوای اولیه
uvicorn app.main:app --reload

فرانت‌اند

cd frontend
npm install
npm run dev

در حالت توسعه، Vite درخواست‌های /api و /media را به Traefik (پورت ۵۱۷۳) پروکسی می‌کند؛ پس استک Docker باید در حال اجرا باشد. آدرس بک‌اند در frontend/src/config.ts از VITE_API_URL خوانده می‌شود — پیش‌فرض خالی یعنی درخواست‌های هم‌مبدأ.

امکانات بک‌اند

  • API عمومی: برند، منو، هیرو، خدمات، پروژه‌ها، مقالات، نظرات، سوالات متداول، تیم، تایم‌لاین، آمار، دفاتر، مدل‌های سرمایه‌گذاری و... — همه در دیتابیس
  • فرم تماس POST /api/contact و خبرنامه POST /api/newsletter
  • پنل مدیریت با JWT: ورود POST /api/auth/login (پیش‌فرض: admin / aramesh123) و سپس CRUD کامل همه محتواها در /api/admin/...
  • تغییر رمز POST /api/auth/change-password — از نوار مدیریت پایین سایت (دکمه «رمز») هم در دسترس است
  • محدودسازی ورود: بعد از ۵ ورود ناموفق در ۱۵ دقیقه از یک IP، درخواست با 429 رد می‌شود
  • آپلود تصویر POST /api/admin/upload (فقط توکن ادمین، حداکثر ۱۰MB) — فایل در MinIO ذخیره و URL نسبی /media/... برمی‌گردد
  • ویرایش جزئی PATCH /api/admin/{resource}/{id} — فقط فیلدهای ارسال‌شده عوض می‌شوند
  • ترتیب‌بندی POST /api/admin/{resource}/reorder — با {"ids": [...]}
  • همه محتوا در یک درخواست: GET /api/site (مقاله‌های پیش‌نویس در آن نمی‌آیند)
  • تست‌ها: cd backend && pip install -r requirements-dev.txt && pytest tests

در محیط واقعی حتماً SECRET_KEY، ADMIN_PASSWORD و MINIO_ROOT_PASSWORD را تغییر دهید (از .env) و بعد از اولین ورود، رمز را از نوار مدیریت عوض کنید.

ویرایش زنده سایت (دوقلوی دیجیتال)

ادمین‌ها نیازی به داشبورد جدا ندارند — خود سایت ویرایشگر است:

  1. به /admin/login بروید و وارد شوید
  2. نوار شناور پایین صفحه ظاهر می‌شود؛ حالت ویرایش را روشن کنید
  3. روی هر متن کلیک کنید و ویرایش کنید — خروج از کادر، بلافاصله ذخیره می‌شود (Enter ذخیره، Esc لغو)؛ وضعیت ذخیره در نوار پایین دیده می‌شود
  4. آیتم‌های مجموعه‌ها (خدمات، پروژه‌ها، مقالات، نظرات، تیم و...) با هاور، دکمه‌های ویرایش / حذف / جابه‌جایی ترتیب نشان می‌دهند؛ دکمه «افزودن» هم انتهای هر لیست هست
  5. تصویرها از همان کشوها به MinIO آپلود می‌شوند
  • در حالت ویرایش، لینک‌ها کلیک نمی‌شوند تا ویرایش متن ممکن باشد
  • بازدیدکننده‌های عادی هیچ اثری از این لایه نمی‌بینند (بدون توکن، همه درخواست‌های مدیریتی 401 برمی‌گردند)
  • تمام تغییرها بلافاصله در PostgreSQL ثبت می‌شوند — با Ctrl+F5 هم می‌مانند

وبلاگ — نوشتن با ویرایشگر WYSIWYG

مقالات با ویرایشگر غنی TipTap نوشته می‌شوند (همان تجربه Medium):

  • در حالت ویرایش، روی هر مقاله در صفحه وبلاگ دکمه ویرایش را بزنید — کشوی نوشتن باز می‌شود
  • تیتر (H2/H3)، بولد/ایتالیک/زیرخط، لیست نقطه‌ای و شماره‌ای، نقل‌قول، لینک و واگرد
  • درج تصویر داخل متن: دکمه تصویر، یا چسباندن/کشیدن تصویر داخل نوشته — فایل مستقیم در MinIO ذخیره و در متن قرار می‌گیرد
  • راست‌به‌چپ کامل با فونت و تم سایت
  • ذخیره خودکار: هر مکث کوتاه در تایپ، تغییرها را ذخیره می‌کند؛ بستن کشو هم ذخیره می‌کند

پیش‌نویس و انتشار: مقاله تازه همیشه پیش‌نویس شروع می‌شود — در سایت عمومی دیده نمی‌شود و تا انتشار، تغییرهای خودکار آن چیزی را خراب نمی‌کند. با دکمه «انتشار در سایت» عمومی می‌شود؛ دوباره می‌توان آن را به پیش‌نویس برگرداند. پیش‌نویس‌ها فقط برای ادمین (در صفحه وبلاگ، با برچسب «پیش‌نویس») دیده می‌شوند و در GET /api/site هرگز نمی‌آیند.

فرمت ذخیره: بدنه مقاله یک سند ساختاریافته TipTap (JSON) در PostgreSQL است — نه HTML خام. صفحه مقاله آن را با رندر استاتیک TipTap به HTML تبدیل می‌کند، پس فقط تگ‌های شناخته‌شده (پاراگراف، تیتر، لیست، نقل‌قول، تصویر، لینک) تولید می‌شوند. مقالات قدیمی (لیست پاراگراف) در اولین اجرا به‌صورت خودکار به این فرمت تبدیل می‌شوند.

سئو

  • بررسی سئو در صفحه نوشتن: فهرست سبز/نارنجی/قرمز به سبک یوست ولی سازگار با فارسی — طول عنوان، کلمه کلیدی هدف (در عنوان/پاراگراف اول/تیترها)، طول چکیده، تعداد واژه، تیتربندی H2، متن جایگزین تصاویر، پیوندها و نشانی (slug) — کلمه کلیدی هدف در دیتابیس ذخیره می‌شود (فیلد seo_keyphrase مقاله)
  • سئوی هر صفحه: در حالت ویرایش، دکمه سئو روی هیرو هر صفحه (و کنار هیرو صفحه اصلی) — عنوان تب و توضیح متای هر صفحه از دیتابیس خوانده و اعمال می‌شود؛ اگر ردیف سئوی صفحه‌ای هنوز ساخته نشده باشد، دکمه + همان‌جا آن را می‌سازد
  • صفحه مقاله و پروژه به‌طور خودکار عنوان و توضیح متا را از محتوای خودشان می‌گیرند
  • تگ‌های OG و توییتر و canonical برای همه صفحات از src/seo.ts ست می‌شود و مقاله‌ها اسکیمای JSON-LD نوع Article می‌گیرند
  • robots.txt و sitemap.xml از بک‌اند سرو می‌شوند و مقاله/پروژه‌های منتشرشده را شامل می‌شوند (پیش‌نویس‌ها نمی‌آیند) — برای آدرس مطلق، SITE_URL را در .env بگذارید
  • متا برای خزنده‌های بدون جاوااسکریپت (تلگرام، واتس‌اپ و...): بک‌اند صفحات /blog، /blog/{slug}، /projects و /projects/{slug} را با همان index.html فرانت‌اند سرو می‌کند و پیش از ارسال، title / description / OG / JSON-LD را از دیتابیس داخل آن تزریق می‌کند (app/seo_pages.py) — بازدیدکننده‌های عادی همان SPA همیشگی را می‌بینند

پشتیبان‌گیری

همه محتوای سایت در PostgreSQL و تصاویر در MinIO است — هر دو را با یک دستور پشتیبان بگیرید:

bash scripts/backup.sh        # در backups/ — نگهداری پیش‌فرض ۱۴ روز
  • خروجی: backups/db-<زمان>.sql.gz (فشرده) و backups/images-<زمان>/ (آینه باکت)
  • زمان‌بندی با cron یا Task Scheduler: نمونه دستور داخل خود اسکریپت هست
  • بازیابی دیتابیس: gunzip -c backups/db-<زمان>.sql.gz | docker compose exec -T db psql -U aramesh arameshplaza

نکات امنیتی

  • مقادیر محرمانه در .env ریشه هستند (در .gitignore) و در docker-compose.yml با سینتکس ${VAR:?} اجباری شده‌اند — بدون .env استک بالا نمی‌آید
  • رمز عبور ادمین با bcrypt هش می‌شود و از نوار مدیریت پایین سایت (دکمه «رمز») قابل تعویض است
  • ورود با محدودیت تلاش محافظت می‌شود: ۵ ورود ناموفق در ۱۵ دقیقه از هر IP → خطای 429
  • توکن‌های JWT امضا دارند و مدت اعتبارشان قابل تنظیم است
  • تصاویر باکت MinIO عمومی (فقط خواندن) هستند؛ آپلود فقط با توکن ادمین ممکن است
  • مقاله‌های پیش‌نویس در هیچ انتهای عمومی نمی‌آیند و در HTML تزریق‌شده سئو هم افشا نمی‌شوند
S
Description
No description provided
Readme
61 MiB
Languages
TypeScript 53%
Python 37.6%
JavaScript 5.2%
Shell 2%
CSS 1.5%
Other 0.6%