وبلاگ: نام‌گذاری و متادیتای تصاویر، پرسش‌وپاسخ مقاله، میزبانی تصاویر چسبانده‌شده

- Alt/Title تصویر شاخص اکنون ذخیره می‌شود (ستون‌های جدید image_alt/image_title — قبلاً بی‌صدا دور ریخته می‌شد)
- Alt و Title تصاویر داخل متن پس از درج هم قابل ویرایش‌اند (کنار کپشن)
- نام فایل ذخیره‌سازی خوانا: نام دلخواه هنگام درج یا نام خود فایل (به‌جای UUID خالی)
- پنل پرسش‌وپاسخ پایان مقاله (ستون‌های faqs/faqs_en) با ظاهر آکاردئونی سایت و اسکیمای FAQPage
- چسباندن از Outline و هر منبع خارجی: تصاویر خودکار دانلود و در Object Storage سایت میزبانی می‌شوند (endpoint جدید import-image با محافظ SSRF) + دکمه میزبانی مجدد روی تصاویر خارجی موجود
This commit is contained in:
MMD
2026-09-26 22:46:56 +03:30
parent 851e3fc2dc
commit 1d29e50852
15 changed files with 580 additions and 45 deletions
+13
View File
@@ -77,12 +77,17 @@ class ArticleOut(ORMModel):
category: str
category_en: str = ""
image: str
image_alt: str = ""
image_title: str = ""
read: str
read_en: str = ""
excerpt: str
excerpt_en: str = ""
body: dict
body_en: Optional[dict] = None
# پرسش‌وپاسخ‌های پایان مقاله — [{question, answer}]؛ سطرهای قدیمی ممکن است NULL باشند
faqs: Optional[list[dict]] = None
faqs_en: Optional[list[dict]] = None
status: str
seo_keyphrase: str
@@ -484,12 +489,16 @@ class ArticleIn(BaseModel):
category: str = ""
category_en: str = ""
image: str = ""
image_alt: str = ""
image_title: str = ""
read: str = ""
read_en: str = ""
excerpt: str = ""
excerpt_en: str = ""
body: dict = Field(default_factory=lambda: dict(EMPTY_DOC))
body_en: dict = Field(default_factory=lambda: dict(EMPTY_DOC))
faqs: list[dict] = []
faqs_en: list[dict] = []
status: Literal["draft", "published"] = "draft"
seo_keyphrase: str = Field(default="", max_length=128)
_validate_body = field_validator("body")(validate_doc_body)
@@ -786,12 +795,16 @@ class ArticlePatch(BaseModel):
category: Optional[str] = None
category_en: Optional[str] = None
image: Optional[str] = None
image_alt: Optional[str] = None
image_title: Optional[str] = None
read: Optional[str] = None
read_en: Optional[str] = None
excerpt: Optional[str] = None
excerpt_en: Optional[str] = None
body: Optional[dict] = None
body_en: Optional[dict] = None
faqs: Optional[list[dict]] = None
faqs_en: Optional[list[dict]] = None
status: Optional[Literal["draft", "published"]] = None
seo_keyphrase: Optional[str] = None
_validate_body = field_validator("body")(validate_doc_body)