- مقالهها ستون status (draft/published) گرفتند — GET /api/site و همه انتهای عمومی فقط منتشرشده را برمیگردانند؛ autosave دیگر پیشنویس نیمهکاره را زنده نمیکند - کلمه کلیدی هدف چکلیست سئو فیلد seo_keyphrase شد (قبلاً localStorage بود) - robots.txt و sitemap.xml از بکاند + سرو /blog و /projects با تزریق title/description/OG/JSON-LD از دیتابیس برای خزندههای بدون JS (app/seo_pages.py) - فرانت: applySeo در seo.ts (OG/توییتر/canonical/JSON-LD)، ادغام پیشنویس برای ادمین، برچسب پیشنویس در وبلاگ، حالت خالی وبلاگ، دکمه انتشار در صفحه نوشتن - تغییر رمز ادمین POST /api/auth/change-password + دیالوگ «رمز» در نوار مدیریت - محدودسازی ورود: ۵ تلاش ناموفق در ۱۵ دقیقه از هر IP → 429 - ساخت دکمه + برای ردیف سئوی صفحههای بدون ردیف (SingletonPill) - تستهای pytest (۱۵ مورد) + پینکردن requirements + حذف package.json سرگردان فرانت
گروه ساختمانی آرامش — پروژه کامل (فرانتاند + بکاند)
مونوریپوی سایت گروه ساختمانی آرامش شامل:
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)
│ ├── requirements.txt
│ ├── Dockerfile
│ └── .env.example
└── docker-compose.yml # اجرای کامل با یک دستور
اجرا با Docker (سادهترین راه)
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را میسازد و خواندن عمومی را فعال میکند
اجرای محلی (توسعه)
بکاند
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/admin/upload(فقط توکن ادمین، حداکثر ۱۰MB) — فایل در MinIO ذخیره و URL نسبی/media/...برمیگردد - ویرایش جزئی
PATCH /api/admin/{resource}/{id}— فقط فیلدهای ارسالشده عوض میشوند - ترتیببندی
POST /api/admin/{resource}/reorder— با{"ids": [...]} - همه محتوا در یک درخواست:
GET /api/site
در محیط واقعی حتماً
SECRET_KEY،ADMIN_PASSWORDوMINIO_ROOT_PASSWORDرا تغییر دهید.
ویرایش زنده سایت (دوقلوی دیجیتال)
ادمینها نیازی به داشبورد جدا ندارند — خود سایت ویرایشگر است:
- به
/admin/loginبروید و وارد شوید - نوار شناور پایین صفحه ظاهر میشود؛ حالت ویرایش را روشن کنید
- روی هر متن کلیک کنید و ویرایش کنید — خروج از کادر، بلافاصله ذخیره میشود (Enter ذخیره، Esc لغو)؛ وضعیت ذخیره در نوار پایین دیده میشود
- آیتمهای مجموعهها (خدمات، پروژهها، مقالات، نظرات، تیم و...) با هاور، دکمههای ویرایش / حذف / جابهجایی ترتیب نشان میدهند؛ دکمه «افزودن» هم انتهای هر لیست هست
- تصویرها از همان کشوها به MinIO آپلود میشوند
- در حالت ویرایش، لینکها کلیک نمیشوند تا ویرایش متن ممکن باشد
- بازدیدکنندههای عادی هیچ اثری از این لایه نمیبینند (بدون توکن، همه درخواستهای
مدیریتی
401برمیگردند) - تمام تغییرها بلافاصله در PostgreSQL ثبت میشوند — با Ctrl+F5 هم میمانند
وبلاگ — نوشتن با ویرایشگر WYSIWYG
مقالات با ویرایشگر غنی TipTap نوشته میشوند (همان تجربه Medium):
- در حالت ویرایش، روی هر مقاله در صفحه وبلاگ دکمه ویرایش را بزنید — کشوی نوشتن باز میشود
- تیتر (H2/H3)، بولد/ایتالیک/زیرخط، لیست نقطهای و شمارهای، نقلقول، لینک و واگرد
- درج تصویر داخل متن: دکمه تصویر، یا چسباندن/کشیدن تصویر داخل نوشته — فایل مستقیم در MinIO ذخیره و در متن قرار میگیرد
- راستبهچپ کامل با فونت و تم سایت
- ذخیره خودکار: هر مکث کوتاه در تایپ، تغییرها را ذخیره میکند؛ بستن کشو هم ذخیره میکند
فرمت ذخیره: بدنه مقاله یک سند ساختاریافته TipTap (JSON) در PostgreSQL است — نه HTML خام. صفحه مقاله آن را با رندر استاتیک TipTap به HTML تبدیل میکند، پس فقط تگهای شناختهشده (پاراگراف، تیتر، لیست، نقلقول، تصویر، لینک) تولید میشوند. مقالات قدیمی (لیست پاراگراف) در اولین اجرا بهصورت خودکار به این فرمت تبدیل میشوند.
سئو
- بررسی سئو در صفحه نوشتن: فهرست سبز/نارنجی/قرمز به سبک یوست ولی سازگار با فارسی — طول عنوان، کلمه کلیدی هدف (در عنوان/پاراگراف اول/تیترها)، طول چکیده، تعداد واژه، تیتربندی H2، متن جایگزین تصاویر، پیوندها و نشانی (slug)
- سئوی هر صفحه: در حالت ویرایش، دکمه سئو روی هیرو هر صفحه (و کنار هیرو صفحه اصلی) — عنوان تب و توضیح متای هر صفحه از دیتابیس خوانده و اعمال میشود
- صفحه مقاله و پروژه بهطور خودکار عنوان و توضیح متا را از محتوای خودشان میگیرند
نکات امنیتی
- فایل
.envهرگز commit نمیشود (در.gitignoreاست) - رمز عبور ادمین با bcrypt هش میشود
- توکنهای JWT امضا دارند و مدت اعتبارشان قابل تنظیم است
- تصاویر باکت MinIO عمومی (فقط خواندن) هستند؛ آپلود فقط با توکن ادمین ممکن است