- docker-compose.yml همه مقادیر محرمانه را با ${VAR:?} از .env میخواند — بدون .env استک بالا نمیآید
- docker-compose.prod.yml: ورودی 443 + ریدایرکت HTTP به HTTPS + گواهی Let's Encrypt و قواعد Host برای همه روترها
- healthcheck بکاند (api/health) و فرانت (wget) + سوار کردن frontend/dist در بکاند برای تزریق متا + روتر سئو (robots/sitemap/blog/projects) با اولویت 50
- سرویس mc با پروفایل tools + scripts/backup.sh (pg_dump + آینه MinIO با نگهداری ۱۴ روزه)
- .env.example ریشه + بروزرسانی READMEها
193 lines
14 KiB
Markdown
193 lines
14 KiB
Markdown
# گروه ساختمانی آرامش — پروژه کامل (فرانتاند + بکاند)
|
|
|
|
مونوریپوی سایت گروه ساختمانی آرامش شامل:
|
|
|
|
- **`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 (سادهترین راه)
|
|
|
|
```bash
|
|
cp .env.example .env # مقادیر change-me را عوض کنید — بدون .env استک بالا نمیآید
|
|
docker compose up --build
|
|
```
|
|
|
|
- سایت: http://localhost:5173 (از طریق Traefik)
|
|
- مستندات API: http://localhost:8001/docs (با `docker-compose.override.yml` محلی)
|
|
- کنسول MinIO: http://localhost:9001 — کاربر/رمز از متغیرهای `MINIO_ROOT_*` در compose
|
|
- دیتابیس PostgreSQL و آبجکتاستور MinIO هر دو در کانتینر جدا با volume دائمی
|
|
- بکاند هنگام شروع، خودش جداول را میسازد و **فقط جدولهای خالی** را seed میکند —
|
|
ارتقای نسخه دادههای فعلی را از بین نمیبرد
|
|
- در اولین اجرا، سرویس `minio-setup` باکت `aramesh-media` را میسازد و خواندن عمومی را فعال میکند
|
|
|
|
## انتشار واقعی (TLS)
|
|
|
|
برای بالا آوردن با HTTPS روی دامنه واقعی، لایه `docker-compose.prod.yml` را هم سوار کنید:
|
|
|
|
```bash
|
|
# در .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 مطلق شوند
|
|
|
|
## اجرای محلی (توسعه)
|
|
|
|
### بکاند
|
|
|
|
```bash
|
|
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
|
|
```
|
|
|
|
### فرانتاند
|
|
|
|
```bash
|
|
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
|
|
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 تزریقشده سئو هم افشا نمیشوند
|