from datetime import datetime, timezone from typing import Optional from sqlalchemy import JSON, Boolean, DateTime, Integer, String, Text from sqlalchemy.orm import Mapped, mapped_column from .database import Base def utcnow() -> datetime: return datetime.now(timezone.utc) # ستون‌های *_en معادل انگلیسی فیلدهای متنیِ قابل نمایش هستند؛ # مقدار خالی یعنی «ترجمه‌ای ثبت نشده» و فرانت‌اند به نسخه فارسی برمی‌گردد. class AdminUser(Base): __tablename__ = "admin_users" id: Mapped[int] = mapped_column(primary_key=True) username: Mapped[str] = mapped_column(String(64), unique=True, index=True) password_hash: Mapped[str] = mapped_column(String(255)) # 'admin' همه‌کاره — 'creator' فقط نویسندهٔ محتوا (پیش‌نویس مقاله‌های خودش) role: Mapped[str] = mapped_column(String(16), default='admin') class Brand(Base): __tablename__ = "brand" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(String(128), default="") name_en: Mapped[str] = mapped_column(String(128), default="") logo: Mapped[str] = mapped_column(Text, default="") logo_en: Mapped[str] = mapped_column(Text, default="") hero: Mapped[str] = mapped_column(Text, default="") contact_bg: Mapped[str] = mapped_column(Text, default="") phone: Mapped[str] = mapped_column(String(64), default="") phone_href: Mapped[str] = mapped_column(String(128), default="") email: Mapped[str] = mapped_column(String(128), default="") address: Mapped[str] = mapped_column(Text, default="") address_en: Mapped[str] = mapped_column(Text, default="") class Service(Base): __tablename__ = "services" position: Mapped[int] = mapped_column(Integer, default=0) id: Mapped[int] = mapped_column(primary_key=True) title: Mapped[str] = mapped_column(String(256)) title_en: Mapped[str] = mapped_column(String(256), default="") desc: Mapped[str] = mapped_column(Text, default="") desc_en: Mapped[str] = mapped_column(Text, default="") icon: Mapped[str] = mapped_column(String(32), default="draft") class Project(Base): __tablename__ = "projects" position: Mapped[int] = mapped_column(Integer, default=0) id: Mapped[int] = mapped_column(primary_key=True) slug: Mapped[str] = mapped_column(String(128), unique=True, index=True) title: Mapped[str] = mapped_column(String(256)) title_en: Mapped[str] = mapped_column(String(256), default="") location: Mapped[str] = mapped_column(String(128), default="") location_en: Mapped[str] = mapped_column(String(128), default="") year: Mapped[str] = mapped_column(String(64), default="") year_en: Mapped[str] = mapped_column(String(64), default="") status: Mapped[str] = mapped_column(String(16), default="done") # done | progress image: Mapped[str] = mapped_column(Text, default="") gallery: Mapped[list] = mapped_column(JSON, default=list) area: Mapped[str] = mapped_column(String(64), default="") area_en: Mapped[str] = mapped_column(String(64), default="") floors: Mapped[str] = mapped_column(String(64), default="") floors_en: Mapped[str] = mapped_column(String(64), default="") land_area: Mapped[str] = mapped_column(String(64), default="") pilot_floors: Mapped[str] = mapped_column(String(32), default="") units_residential: Mapped[str] = mapped_column(String(32), default="") units_commercial: Mapped[str] = mapped_column(String(32), default="") units_office: Mapped[str] = mapped_column(String(32), default="") client: Mapped[str] = mapped_column(String(128), default="") client_en: Mapped[str] = mapped_column(String(128), default="") # مشخصات تکمیلی پروژه — به درخواست تیم: نوع پروژه و کاربری project_type: Mapped[str] = mapped_column(String(128), default="") project_type_en: Mapped[str] = mapped_column(String(128), default="") usage: Mapped[str] = mapped_column(String(128), default="") usage_en: Mapped[str] = mapped_column(String(128), default="") summary: Mapped[str] = mapped_column(Text, default="") summary_en: Mapped[str] = mapped_column(Text, default="") body: Mapped[list] = mapped_column(JSON, default=list) body_en: Mapped[list] = mapped_column(JSON, default=list) class Article(Base): __tablename__ = "articles" position: Mapped[int] = mapped_column(Integer, default=0) id: Mapped[int] = mapped_column(primary_key=True) slug: Mapped[str] = mapped_column(String(128), unique=True, index=True) title: Mapped[str] = mapped_column(String(256)) title_en: Mapped[str] = mapped_column(String(256), default="") date: Mapped[str] = mapped_column(String(64), default="") date_en: Mapped[str] = mapped_column(String(64), default="") category: Mapped[str] = mapped_column(String(64), default="") category_en: Mapped[str] = mapped_column(String(64), default="") image: Mapped[str] = mapped_column(Text, default="") # متن جایگزین و عنوان تصویر شاخص — سئو و دسترس‌پذیری image_alt: Mapped[str] = mapped_column(String(256), default="") image_title: Mapped[str] = mapped_column(String(256), default="") read: Mapped[str] = mapped_column(String(32), default="") read_en: Mapped[str] = mapped_column(String(32), default="") excerpt: Mapped[str] = mapped_column(Text, default="") excerpt_en: Mapped[str] = mapped_column(Text, default="") body: Mapped[list] = mapped_column(JSON, default=list) # سند TipTap (تاریخی: لیست پاراگراف) body_en: Mapped[dict] = mapped_column(JSON, default=dict) # سند TipTap انگلیسی # پرسش و پاسخ‌های پایان مقاله — [{question, answer}]؛ برای نتایج غنی سئو (FAQPage) faqs: Mapped[list] = mapped_column(JSON, default=list) faqs_en: Mapped[Optional[list]] = mapped_column(JSON) # NULL یعنی بدون ترجمه — برگشت به فارسی author_id: Mapped[int | None] = mapped_column(Integer, nullable=True) # نویسندهٔ creator — برای مقاله‌های ادمین NULL # نام نویسنده (رشتهٔ آزاد) — سربرگ مقاله و اسکیمای Article (نوع Person برای سیگنال E-E-A-T) author: Mapped[str] = mapped_column(String(128), default="") # draft | published — پیش‌نویس فقط برای ادمین دیده می‌شود status: Mapped[str] = mapped_column(String(16), default="published") # کلمه کلیدی هدف چک‌لیست سئو — راهنمای نوشتن، در سایت نمایش داده نمی‌شود seo_keyphrase: Mapped[str] = mapped_column(String(128), default="") # توضیحات سئوی اختصاصی مقاله — در تگ description و اسکیما، در اولویت بر چکیده meta_description: Mapped[str] = mapped_column(Text, default="") meta_description_en: Mapped[str] = mapped_column(Text, default="") # نشانی انگلیسی مقاله — مسیر /en/blog/...؛ خالی یعنی همان اسلاگ فارسی slug_en: Mapped[str] = mapped_column(String(128), default="", index=True) # کلمه کلیدی هدف نسخه انگلیسی — چک‌لیست سئو seo_keyphrase_en: Mapped[str] = mapped_column(String(128), default="") # نام نویسنده نسخه انگلیسی author_en: Mapped[str] = mapped_column(String(128), default="") # هشتگ‌ها/برچسب‌ها — هر زبان جدا tags: Mapped[list] = mapped_column(JSON, default=list) tags_en: Mapped[list] = mapped_column(JSON, default=list) class Testimonial(Base): __tablename__ = "testimonials" position: Mapped[int] = mapped_column(Integer, default=0) id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(String(128)) name_en: Mapped[str] = mapped_column(String(128), default="") role: Mapped[str] = mapped_column(String(128), default="") role_en: Mapped[str] = mapped_column(String(128), default="") quote: Mapped[str] = mapped_column(Text, default="") quote_en: Mapped[str] = mapped_column(Text, default="") class Faq(Base): __tablename__ = "faqs" position: Mapped[int] = mapped_column(Integer, default=0) id: Mapped[int] = mapped_column(primary_key=True) question: Mapped[str] = mapped_column(Text) question_en: Mapped[str] = mapped_column(Text, default="") answer: Mapped[str] = mapped_column(Text, default="") answer_en: Mapped[str] = mapped_column(Text, default="") class TeamMember(Base): __tablename__ = "team" position: Mapped[int] = mapped_column(Integer, default=0) id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(String(128)) name_en: Mapped[str] = mapped_column(String(128), default="") role: Mapped[str] = mapped_column(String(128), default="") role_en: Mapped[str] = mapped_column(String(128), default="") bio: Mapped[str] = mapped_column(Text, default="") bio_en: Mapped[str] = mapped_column(Text, default="") class TimelineItem(Base): __tablename__ = "timeline" position: Mapped[int] = mapped_column(Integer, default=0) id: Mapped[int] = mapped_column(primary_key=True) year: Mapped[str] = mapped_column(String(32)) year_en: Mapped[str] = mapped_column(String(32), default="") title: Mapped[str] = mapped_column(String(256)) title_en: Mapped[str] = mapped_column(String(256), default="") desc: Mapped[str] = mapped_column(Text, default="") desc_en: Mapped[str] = mapped_column(Text, default="") image: Mapped[str] = mapped_column(Text, default="") class Stat(Base): __tablename__ = "stats" position: Mapped[int] = mapped_column(Integer, default=0) id: Mapped[int] = mapped_column(primary_key=True) value: Mapped[int] = mapped_column(Integer) suffix: Mapped[str] = mapped_column(String(16), default="") suffix_en: Mapped[str] = mapped_column(String(16), default="") label: Mapped[str] = mapped_column(String(128)) label_en: Mapped[str] = mapped_column(String(128), default="") class WhyUsItem(Base): __tablename__ = "why_us" position: Mapped[int] = mapped_column(Integer, default=0) id: Mapped[int] = mapped_column(primary_key=True) title: Mapped[str] = mapped_column(String(256)) title_en: Mapped[str] = mapped_column(String(256), default="") desc: Mapped[str] = mapped_column(Text, default="") desc_en: Mapped[str] = mapped_column(Text, default="") class BeforeAfter(Base): __tablename__ = "before_after" id: Mapped[int] = mapped_column(primary_key=True) title: Mapped[str] = mapped_column(String(256), default="") title_en: Mapped[str] = mapped_column(String(256), default="") lead: Mapped[str] = mapped_column(Text, default="") lead_en: Mapped[str] = mapped_column(Text, default="") before: Mapped[str] = mapped_column(Text, default="") after: Mapped[str] = mapped_column(Text, default="") before_label: Mapped[str] = mapped_column(String(128), default="") before_label_en: Mapped[str] = mapped_column(String(128), default="") after_label: Mapped[str] = mapped_column(String(128), default="") after_label_en: Mapped[str] = mapped_column(String(128), default="") class NavItem(Base): __tablename__ = "nav_items" position: Mapped[int] = mapped_column(Integer, default=0) id: Mapped[int] = mapped_column(primary_key=True) label: Mapped[str] = mapped_column(String(64)) label_en: Mapped[str] = mapped_column(String(64), default="") href: Mapped[str] = mapped_column(String(128)) class Hero(Base): """محتوای هیرو صفحه اصلی — رکورد یگانه.""" __tablename__ = "hero" id: Mapped[int] = mapped_column(primary_key=True) kicker: Mapped[str] = mapped_column(Text, default="") kicker_en: Mapped[str] = mapped_column(Text, default="") title: Mapped[str] = mapped_column(Text, default="") title_en: Mapped[str] = mapped_column(Text, default="") title_highlight: Mapped[str] = mapped_column(String(128), default="") title_highlight_en: Mapped[str] = mapped_column(String(128), default="") subtitle: Mapped[str] = mapped_column(Text, default="") subtitle_en: Mapped[str] = mapped_column(Text, default="") badge_value: Mapped[str] = mapped_column(String(32), default="") badge_value_en: Mapped[str] = mapped_column(String(32), default="") badge_label: Mapped[str] = mapped_column(Text, default="") badge_label_en: Mapped[str] = mapped_column(Text, default="") cta_primary_label: Mapped[str] = mapped_column(String(128), default="") cta_primary_label_en: Mapped[str] = mapped_column(String(128), default="") cta_primary_href: Mapped[str] = mapped_column(String(128), default="") cta_secondary_label: Mapped[str] = mapped_column(String(128), default="") cta_secondary_label_en: Mapped[str] = mapped_column(String(128), default="") cta_secondary_href: Mapped[str] = mapped_column(String(128), default="") scroll_label: Mapped[str] = mapped_column(String(64), default="") scroll_label_en: Mapped[str] = mapped_column(String(64), default="") class StorySection(Base): """بخش «درباره ما» در خانه و صفحه درباره — رکورد یگانه.""" __tablename__ = "story" id: Mapped[int] = mapped_column(primary_key=True) title: Mapped[str] = mapped_column(String(256), default="") title_en: Mapped[str] = mapped_column(String(256), default="") paragraphs: Mapped[list] = mapped_column(JSON, default=list) paragraphs_en: Mapped[list] = mapped_column(JSON, default=list) quote: Mapped[str] = mapped_column(Text, default="") quote_en: Mapped[str] = mapped_column(Text, default="") quote_author: Mapped[str] = mapped_column(Text, default="") quote_author_en: Mapped[str] = mapped_column(Text, default="") stat1_value: Mapped[str] = mapped_column(String(32), default="") stat1_value_en: Mapped[str] = mapped_column(String(32), default="") stat1_label: Mapped[str] = mapped_column(String(128), default="") stat1_label_en: Mapped[str] = mapped_column(String(128), default="") stat2_value: Mapped[str] = mapped_column(String(32), default="") stat2_value_en: Mapped[str] = mapped_column(String(32), default="") stat2_label: Mapped[str] = mapped_column(String(128), default="") stat2_label_en: Mapped[str] = mapped_column(String(128), default="") link_label: Mapped[str] = mapped_column(String(128), default="") link_label_en: Mapped[str] = mapped_column(String(128), default="") link_href: Mapped[str] = mapped_column(String(128), default="") bento: Mapped[list] = mapped_column(JSON, default=list) # [{src, alt}] bento_en: Mapped[list] = mapped_column(JSON, default=list) # [{src, alt}] انگلیسی class SectionHeadingContent(Base): """تیتر و توضیح بخش‌ها — فرانت‌اند بر اساس key آن را پیدا می‌کند.""" __tablename__ = "section_headings" id: Mapped[int] = mapped_column(primary_key=True) key: Mapped[str] = mapped_column(String(64), unique=True, index=True) label: Mapped[str] = mapped_column(String(64), default="") label_en: Mapped[str] = mapped_column(String(64), default="") title: Mapped[str] = mapped_column(String(256), default="") title_en: Mapped[str] = mapped_column(String(256), default="") lead: Mapped[str] = mapped_column(Text, default="") lead_en: Mapped[str] = mapped_column(Text, default="") image: Mapped[str] = mapped_column(Text, default="") cta_label: Mapped[str] = mapped_column(String(128), default="") cta_label_en: Mapped[str] = mapped_column(String(128), default="") cta_href: Mapped[str] = mapped_column(String(128), default="") class PageHeroContent(Base): """هیرو بالای هر صفحه — key: about | services | projects | investment | blog | faq | contact.""" __tablename__ = "page_heroes" id: Mapped[int] = mapped_column(primary_key=True) key: Mapped[str] = mapped_column(String(32), unique=True, index=True) title: Mapped[str] = mapped_column(String(256), default="") title_en: Mapped[str] = mapped_column(String(256), default="") lead: Mapped[str] = mapped_column(Text, default="") lead_en: Mapped[str] = mapped_column(Text, default="") image: Mapped[str] = mapped_column(Text, default="") class InvestmentModel(Base): __tablename__ = "investment_models" position: Mapped[int] = mapped_column(Integer, default=0) id: Mapped[int] = mapped_column(primary_key=True) title: Mapped[str] = mapped_column(String(256)) title_en: Mapped[str] = mapped_column(String(256), default="") desc: Mapped[str] = mapped_column(Text, default="") desc_en: Mapped[str] = mapped_column(Text, default="") points: Mapped[list] = mapped_column(JSON, default=list) points_en: Mapped[list] = mapped_column(JSON, default=list) class ProcessStep(Base): __tablename__ = "process_steps" position: Mapped[int] = mapped_column(Integer, default=0) id: Mapped[int] = mapped_column(primary_key=True) step: Mapped[str] = mapped_column(String(16), default="") step_en: Mapped[str] = mapped_column(String(16), default="") title: Mapped[str] = mapped_column(String(256)) title_en: Mapped[str] = mapped_column(String(256), default="") desc: Mapped[str] = mapped_column(Text, default="") desc_en: Mapped[str] = mapped_column(Text, default="") class Office(Base): __tablename__ = "offices" position: Mapped[int] = mapped_column(Integer, default=0) id: Mapped[int] = mapped_column(primary_key=True) city: Mapped[str] = mapped_column(String(64)) city_en: Mapped[str] = mapped_column(String(64), default="") address: Mapped[str] = mapped_column(Text, default="") address_en: Mapped[str] = mapped_column(Text, default="") hours: Mapped[str] = mapped_column(String(128), default="") hours_en: Mapped[str] = mapped_column(String(128), default="") class GalleryImage(Base): __tablename__ = "gallery_images" position: Mapped[int] = mapped_column(Integer, default=0) id: Mapped[int] = mapped_column(primary_key=True) src: Mapped[str] = mapped_column(Text, default="") alt: Mapped[str] = mapped_column(Text, default="") alt_en: Mapped[str] = mapped_column(Text, default="") class StatusLabel(Base): """برچسب فارسی وضعیت پروژه — key: done | progress.""" __tablename__ = "status_labels" id: Mapped[int] = mapped_column(primary_key=True) key: Mapped[str] = mapped_column(String(16), unique=True, index=True) label: Mapped[str] = mapped_column(String(64)) label_en: Mapped[str] = mapped_column(String(64), default="") class ContactSectionContent(Base): """بخش فرم تماس — رکورد یگانه.""" __tablename__ = "contact_section" id: Mapped[int] = mapped_column(primary_key=True) label: Mapped[str] = mapped_column(String(64), default="") label_en: Mapped[str] = mapped_column(String(64), default="") title: Mapped[str] = mapped_column(String(256), default="") title_en: Mapped[str] = mapped_column(String(256), default="") lead: Mapped[str] = mapped_column(Text, default="") lead_en: Mapped[str] = mapped_column(Text, default="") address_label: Mapped[str] = mapped_column(String(64), default="") address_label_en: Mapped[str] = mapped_column(String(64), default="") phone_label: Mapped[str] = mapped_column(String(64), default="") phone_label_en: Mapped[str] = mapped_column(String(64), default="") email_label: Mapped[str] = mapped_column(String(64), default="") email_label_en: Mapped[str] = mapped_column(String(64), default="") name_label: Mapped[str] = mapped_column(String(64), default="") name_label_en: Mapped[str] = mapped_column(String(64), default="") name_placeholder: Mapped[str] = mapped_column(String(128), default="") name_placeholder_en: Mapped[str] = mapped_column(String(128), default="") phone_field_label: Mapped[str] = mapped_column(String(64), default="") phone_field_label_en: Mapped[str] = mapped_column(String(64), default="") phone_placeholder: Mapped[str] = mapped_column(String(64), default="") phone_placeholder_en: Mapped[str] = mapped_column(String(64), default="") note_label: Mapped[str] = mapped_column(String(64), default="") note_label_en: Mapped[str] = mapped_column(String(64), default="") note_optional_label: Mapped[str] = mapped_column(String(64), default="") note_optional_label_en: Mapped[str] = mapped_column(String(64), default="") note_placeholder: Mapped[str] = mapped_column(String(256), default="") note_placeholder_en: Mapped[str] = mapped_column(String(256), default="") submit_label: Mapped[str] = mapped_column(String(128), default="") submit_label_en: Mapped[str] = mapped_column(String(128), default="") sending_label: Mapped[str] = mapped_column(String(64), default="") sending_label_en: Mapped[str] = mapped_column(String(64), default="") success_title: Mapped[str] = mapped_column(String(256), default="") success_title_en: Mapped[str] = mapped_column(String(256), default="") success_text: Mapped[str] = mapped_column(Text, default="") success_text_en: Mapped[str] = mapped_column(Text, default="") success_button_label: Mapped[str] = mapped_column(String(128), default="") success_button_label_en: Mapped[str] = mapped_column(String(128), default="") class NewsletterSectionContent(Base): """بخش خبرنامه — رکورد یگانه.""" __tablename__ = "newsletter" id: Mapped[int] = mapped_column(primary_key=True) title: Mapped[str] = mapped_column(String(256), default="") title_en: Mapped[str] = mapped_column(String(256), default="") lead: Mapped[str] = mapped_column(Text, default="") lead_en: Mapped[str] = mapped_column(Text, default="") placeholder: Mapped[str] = mapped_column(String(128), default="") placeholder_en: Mapped[str] = mapped_column(String(128), default="") button_label: Mapped[str] = mapped_column(String(64), default="") button_label_en: Mapped[str] = mapped_column(String(64), default="") sending_label: Mapped[str] = mapped_column(String(64), default="") sending_label_en: Mapped[str] = mapped_column(String(64), default="") success_text: Mapped[str] = mapped_column(String(256), default="") success_text_en: Mapped[str] = mapped_column(String(256), default="") class FooterContent(Base): """متن‌های فوتر — رکورد یگانه.""" __tablename__ = "footer" id: Mapped[int] = mapped_column(primary_key=True) about_text: Mapped[str] = mapped_column(Text, default="") about_text_en: Mapped[str] = mapped_column(Text, default="") pages_heading: Mapped[str] = mapped_column(String(64), default="") pages_heading_en: Mapped[str] = mapped_column(String(64), default="") services_heading: Mapped[str] = mapped_column(String(64), default="") services_heading_en: Mapped[str] = mapped_column(String(64), default="") contact_heading: Mapped[str] = mapped_column(String(64), default="") contact_heading_en: Mapped[str] = mapped_column(String(64), default="") copyright: Mapped[str] = mapped_column(String(256), default="") copyright_en: Mapped[str] = mapped_column(String(256), default="") tagline: Mapped[str] = mapped_column(String(256), default="") tagline_en: Mapped[str] = mapped_column(String(256), default="") socials: Mapped[list] = mapped_column(JSON, default=list) # [{label, href, icon}] socials_en: Mapped[list] = mapped_column(JSON, default=list) # [{label, href, icon}] انگلیسی class SeoMeta(Base): """عنوان و توضیح سئوی هر صفحه — page_key: home / about / services / ...""" __tablename__ = "seo_meta" id: Mapped[int] = mapped_column(primary_key=True) page_key: Mapped[str] = mapped_column(String(32), unique=True, index=True) title: Mapped[str] = mapped_column(String(256), default="") title_en: Mapped[str] = mapped_column(String(256), default="") description: Mapped[str] = mapped_column(Text, default="") description_en: Mapped[str] = mapped_column(Text, default="") class ContactMessage(Base): __tablename__ = "contact_messages" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(String(128)) phone: Mapped[str] = mapped_column(String(32)) note: Mapped[str] = mapped_column(Text, default="") is_read: Mapped[bool] = mapped_column(Boolean, default=False) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow) class Subscriber(Base): __tablename__ = "subscribers" id: Mapped[int] = mapped_column(primary_key=True) email: Mapped[str] = mapped_column(String(256), unique=True, index=True) is_active: Mapped[bool] = mapped_column(Boolean, default=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)