using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.EntityFrameworkCore; using System.ComponentModel.DataAnnotations; using DrSousan.Api.Data; using DrSousan.Api.Models; namespace DrSousan.Api.Pages.Blog; public class PostModel : PageModel { private readonly AppDbContext _db; public PostModel(AppDbContext db) => _db = db; public BlogPost? Post { get; private set; } public List Comments { get; private set; } = new(); public bool CommentSent { get; private set; } = false; // (question, answer) pairs extracted from the post body for FAQPage JSON-LD public List<(string Q, string A)> Faqs { get; private set; } = new(); // Comment form binding [BindProperty] [Required(ErrorMessage = "نام الزامی است.")] [MaxLength(100)] public string CommentAuthor { get; set; } = ""; [BindProperty] [MaxLength(200)] [EmailAddress] public string CommentEmail { get; set; } = ""; [BindProperty] [Required(ErrorMessage = "متن نظر الزامی است.")] public string CommentBody { get; set; } = ""; public async Task OnGetAsync(string slug) { var post = await _db.BlogPosts .Include(p => p.Category) .FirstOrDefaultAsync(p => p.Slug == slug && p.IsPublished); if (post is null) return NotFound(); // Increment view count post.ViewCount++; await _db.SaveChangesAsync(); Post = post; Faqs = ExtractFaqs(post.Content); await LoadCommentsAsync(post.Id); await SetViewDataAsync(post); return Page(); } // Pull FAQ pairs from the body: an

whose text ends with the Persian // question mark (؟) followed by the next

. Drives FAQPage rich results. private static List<(string, string)> ExtractFaqs(string html) { var list = new List<(string, string)>(); if (string.IsNullOrEmpty(html)) return list; var rx = new System.Text.RegularExpressions.Regex( @"]*>(?.*?)

\s*]*>(?.*?)

", System.Text.RegularExpressions.RegexOptions.Singleline | System.Text.RegularExpressions.RegexOptions.IgnoreCase); foreach (System.Text.RegularExpressions.Match m in rx.Matches(html)) { var q = Strip(m.Groups["q"].Value); var a = Strip(m.Groups["a"].Value); if (q.EndsWith("؟") && a.Length > 0) list.Add((q, a)); } return list; } private static string Strip(string s) => System.Net.WebUtility.HtmlDecode( System.Text.RegularExpressions.Regex.Replace(s, "<[^>]*>", "")).Trim(); public async Task OnPostAsync(string slug) { var post = await _db.BlogPosts .Include(p => p.Category) .FirstOrDefaultAsync(p => p.Slug == slug && p.IsPublished); if (post is null) return NotFound(); Post = post; await LoadCommentsAsync(post.Id); await SetViewDataAsync(post); if (!ModelState.IsValid) return Page(); // Sanitise var body = System.Text.RegularExpressions.Regex.Replace(CommentBody ?? "", "<[^>]*>", "").Trim(); var authorName = System.Text.RegularExpressions.Regex.Replace(CommentAuthor ?? "", "<[^>]*>", "").Trim(); if (string.IsNullOrWhiteSpace(body) || string.IsNullOrWhiteSpace(authorName)) { ModelState.AddModelError("", "نام و متن نظر الزامی است."); return Page(); } _db.Comments.Add(new Comment { BlogPostId = post.Id, AuthorName = authorName, AuthorEmail = CommentEmail ?? "", Body = body, IsApproved = false, CreatedAt = DateTime.UtcNow }); await _db.SaveChangesAsync(); CommentSent = true; // Clear form CommentAuthor = ""; CommentEmail = ""; CommentBody = ""; ModelState.Clear(); return Page(); } private async Task LoadCommentsAsync(int postId) { var raw = await _db.Comments .Where(c => c.BlogPostId == postId && c.IsApproved && c.ParentId == null) .OrderBy(c => c.CreatedAt) .Select(c => new { c.Id, c.AuthorName, c.Body, c.CreatedAt, c.IsAdminReply, Replies = _db.Comments .Where(r => r.ParentId == c.Id && r.IsApproved) .OrderBy(r => r.CreatedAt) .Select(r => new { r.Id, r.AuthorName, r.Body, r.CreatedAt, r.IsAdminReply }) .ToList() }).ToListAsync(); Comments = raw.Select(c => new CommentVm { Id = c.Id, AuthorName = c.AuthorName, Body = c.Body, CreatedAt = c.CreatedAt, IsAdminReply = c.IsAdminReply, Replies = c.Replies.Select(r => new CommentVm { Id = r.Id, AuthorName = r.AuthorName, Body = r.Body, CreatedAt = r.CreatedAt, IsAdminReply = r.IsAdminReply }).ToList() }).ToList(); } private async Task SetViewDataAsync(BlogPost post) { var metaTitle = string.IsNullOrEmpty(post.MetaTitle) ? post.Title : post.MetaTitle; var metaDesc = string.IsNullOrEmpty(post.MetaDescription) ? post.Excerpt : post.MetaDescription; // Avoid duplicating the site name if MetaTitle already contains it var suffix = " | دکتر سوسن آل‌طه"; ViewData["Title"] = metaTitle.Contains("دکتر سوسن") ? metaTitle : metaTitle + suffix; ViewData["MetaDesc"] = metaDesc; ViewData["Keywords"] = post.Keywords; ViewData["OgImage"] = string.IsNullOrEmpty(post.OgImage) ? post.FeaturedImage : post.OgImage; ViewData["ArticleType"] = post.ArticleType; ViewData["Slug"] = post.Slug; var heroSettings = await _db.SiteSettings .Where(x => x.Section == "hero" && (x.Key == "name" || x.Key == "image" || x.Key == "tag")) .ToListAsync(); ViewData["SiteName"] = heroSettings.FirstOrDefault(x => x.Key == "name")?.Value ?? "دکتر سوسن آل‌طه"; ViewData["HeroImage"] = heroSettings.FirstOrDefault(x => x.Key == "image")?.Value ?? ""; ViewData["HeroTag"] = heroSettings.FirstOrDefault(x => x.Key == "tag")?.Value ?? "پزشک عمومی و متخصص زیبایی پوست"; } // View model for comments public class CommentVm { public int Id { get; set; } public string AuthorName { get; set; } = ""; public string Body { get; set; } = ""; public DateTime CreatedAt { get; set; } public bool IsAdminReply { get; set; } public List Replies { get; set; } = new(); } }