"""انتهای عمومی — خواندن محتوای سایت بدون نیاز به احراز هویت.""" import boto3 from botocore.config import Config as BotoConfig from fastapi import APIRouter, Depends, HTTPException from fastapi.responses import JSONResponse from sqlalchemy import text from sqlalchemy.orm import Session from ..config import settings from ..database import get_db from .. import models from ..schemas import ( ArticleOut, BeforeAfterOut, BrandOut, FaqOut, ProjectOut, ServiceOut, StatOut, TeamMemberOut, TestimonialOut, TimelineItemOut, WhyUsItemOut, ) router = APIRouter() @router.get("/live") def live(): """زنده‌بودن پروسه — بدون وابستگی؛ برای restart gate استفاده نمی‌شود.""" return {"status": "ok"} @router.get("/health") def health(db: Session = Depends(get_db)): """بررسی عمق سلامت — اتصال DB و MinIO واقعاً آزموده می‌شود؛ در صورت خرابی هر وابستگی، 503 با جزئیات برمی‌گردد.""" checks: dict[str, str] = {} try: db.execute(text("SELECT 1")) checks["database"] = "ok" except Exception: checks["database"] = "error" try: s3 = boto3.client( "s3", endpoint_url=settings.s3_endpoint, aws_access_key_id=settings.s3_access_key, aws_secret_access_key=settings.s3_secret_key, region_name="us-east-1", config=BotoConfig(connect_timeout=2, read_timeout=2, retries={"max_attempts": 1}), ) s3.head_bucket(Bucket=settings.s3_bucket) checks["storage"] = "ok" except Exception: checks["storage"] = "error" if any(state != "ok" for state in checks.values()): return JSONResponse( status_code=503, content={"status": "degraded", "checks": checks}, ) return {"status": "ok", "app": "Aramesh Building Group API", "checks": checks} @router.get("/brand", response_model=BrandOut) def get_brand(db: Session = Depends(get_db)): brand = db.query(models.Brand).first() if brand is None: raise HTTPException(status_code=404, detail="اطلاعات برند یافت نشد") return brand @router.get("/services", response_model=list[ServiceOut]) def get_services(db: Session = Depends(get_db)): return db.query(models.Service).order_by(models.Service.position, models.Service.id).all() @router.get("/projects", response_model=list[ProjectOut]) def get_projects(db: Session = Depends(get_db)): return db.query(models.Project).order_by(models.Project.position, models.Project.id).all() @router.get("/projects/{slug}", response_model=ProjectOut) def get_project(slug: str, db: Session = Depends(get_db)): project = db.query(models.Project).filter(models.Project.slug == slug).first() if project is None: raise HTTPException(status_code=404, detail="پروژه یافت نشد") return project @router.get("/articles", response_model=list[ArticleOut]) def get_articles(db: Session = Depends(get_db)): return ( db.query(models.Article) .filter(models.Article.status == "published") .order_by(models.Article.position, models.Article.id) .all() ) @router.get("/articles/{slug}", response_model=ArticleOut) def get_article(slug: str, db: Session = Depends(get_db)): article = ( db.query(models.Article) .filter(models.Article.slug == slug, models.Article.status == "published") .first() ) if article is None: raise HTTPException(status_code=404, detail="مقاله یافت نشد") return article @router.get("/testimonials", response_model=list[TestimonialOut]) def get_testimonials(db: Session = Depends(get_db)): return db.query(models.Testimonial).order_by(models.Testimonial.position, models.Testimonial.id).all() @router.get("/faqs", response_model=list[FaqOut]) def get_faqs(db: Session = Depends(get_db)): return db.query(models.Faq).order_by(models.Faq.position, models.Faq.id).all() @router.get("/team", response_model=list[TeamMemberOut]) def get_team(db: Session = Depends(get_db)): return db.query(models.TeamMember).order_by(models.TeamMember.position, models.TeamMember.id).all() @router.get("/timeline", response_model=list[TimelineItemOut]) def get_timeline(db: Session = Depends(get_db)): return db.query(models.TimelineItem).order_by(models.TimelineItem.position, models.TimelineItem.id).all() @router.get("/stats", response_model=list[StatOut]) def get_stats(db: Session = Depends(get_db)): return db.query(models.Stat).order_by(models.Stat.position, models.Stat.id).all() @router.get("/why-us", response_model=list[WhyUsItemOut]) def get_why_us(db: Session = Depends(get_db)): return db.query(models.WhyUsItem).order_by(models.WhyUsItem.position, models.WhyUsItem.id).all() @router.get("/before-after", response_model=BeforeAfterOut) def get_before_after(db: Session = Depends(get_db)): item = db.query(models.BeforeAfter).first() if item is None: raise HTTPException(status_code=404, detail="بخش قبل/بعد یافت نشد") return item @router.get("/site") def get_site(db: Session = Depends(get_db)): """همه محتوای سایت در یک درخواست — مناسب برای بارگذاری اولیه فرانت‌اند.""" return { "brand": db.query(models.Brand).first(), "nav": db.query(models.NavItem).order_by(models.NavItem.position, models.NavItem.id).all(), "hero": db.query(models.Hero).first(), "story": db.query(models.StorySection).first(), "section_headings": db.query(models.SectionHeadingContent).order_by(models.SectionHeadingContent.id).all(), "page_heroes": db.query(models.PageHeroContent).order_by(models.PageHeroContent.id).all(), "services": db.query(models.Service).order_by(models.Service.position, models.Service.id).all(), "projects": db.query(models.Project).order_by(models.Project.position, models.Project.id).all(), # فقط مقاله‌های منتشرشده — پیش‌نویس‌ها از انتهای مدیریتی خوانده می‌شوند "articles": db.query(models.Article) .filter(models.Article.status == "published") .order_by(models.Article.position, models.Article.id) .all(), "testimonials": db.query(models.Testimonial).order_by(models.Testimonial.position, models.Testimonial.id).all(), "faqs": db.query(models.Faq).order_by(models.Faq.position, models.Faq.id).all(), "team": db.query(models.TeamMember).order_by(models.TeamMember.position, models.TeamMember.id).all(), "timeline": db.query(models.TimelineItem).order_by(models.TimelineItem.position, models.TimelineItem.id).all(), "stats": db.query(models.Stat).order_by(models.Stat.position, models.Stat.id).all(), "why_us": db.query(models.WhyUsItem).order_by(models.WhyUsItem.position, models.WhyUsItem.id).all(), "before_after": db.query(models.BeforeAfter).first(), "investment_models": db.query(models.InvestmentModel).order_by(models.InvestmentModel.position, models.InvestmentModel.id).all(), "process_steps": db.query(models.ProcessStep).order_by(models.ProcessStep.position, models.ProcessStep.id).all(), "offices": db.query(models.Office).order_by(models.Office.position, models.Office.id).all(), "gallery": db.query(models.GalleryImage).order_by(models.GalleryImage.position, models.GalleryImage.id).all(), "status_labels": db.query(models.StatusLabel).order_by(models.StatusLabel.id).all(), "contact_section": db.query(models.ContactSectionContent).first(), "newsletter": db.query(models.NewsletterSectionContent).first(), "footer": db.query(models.FooterContent).first(), "seo_meta": db.query(models.SeoMeta).order_by(models.SeoMeta.id).all(), }