feat(blog): add SEO fields, scheduling, and representative ownership to blog posts

- Introduced new SEO fields (meta_title, meta_description, primary_keyword, secondary_keywords, faq, internal_links, external_links, reading_time, canonical_url, og_image) to the Blog entity.
- Added scheduling capability with a scheduled_at field to manage automatic publishing of blog posts.
- Implemented representative ownership through a foreign key representation_id in the Blog entity, allowing representatives to manage their own posts.
- Updated BlogController and RepresentationBlogController to handle new fields and ensure proper data handling for SEO and scheduling.
- Created BlogWriter service to encapsulate the logic for applying SEO and scheduling fields to blog entities.
- Added PublishScheduledBlogsMessage and its handler to manage the publishing of scheduled blogs.
- Implemented ScheduledBlogPublisher service to publish drafts whose scheduled_at has arrived, respecting review status.
- Created migration to update the database schema with new fields and constraints.
- Added tests to ensure the correct functionality of new features, including SEO fields, representative scope, and scheduled publishing.
This commit is contained in:
hamed
2026-07-23 22:05:23 +03:30
parent 14730e43ce
commit 62b2f28c4f
14 changed files with 786 additions and 0 deletions
+15
View File
@@ -446,6 +446,21 @@ export interface Blog {
review_note?: string | null;
/** کلید یکتای موضوع پایپ‌لاین (specialty, angle) */
topic_slug?: string | null;
// SEO
meta_title?: string | null;
meta_description?: string | null;
primary_keyword?: string | null;
secondary_keywords?: string[];
faq?: { q: string; a: string }[];
internal_links?: (string | { url: string; anchor?: string })[];
external_links?: (string | { url: string; anchor?: string })[];
reading_time?: number | null;
canonical_url?: string | null;
og_image?: string | null;
/** زمان انتشار خودکار (unix)، null = دستی */
scheduled_at?: number | null;
/** نمایندهٔ مالک، null = پست ادمین/سراسری */
representation?: { uuid: string; name: string; domain?: string | null } | null;
created_at: string;
updated_at?: string;
}