using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using RMuseum.DbContext; using RMuseum.Models.Ganjoor; using RMuseum.Models.Ganjoor.ViewModels; using RMuseum.Models.GanjoorAudio; using RMuseum.Models.GanjoorAudio.ViewModels; using RMuseum.Models.MusicCatalogue; using RSecurityBackend.Models.Generic; using RSecurityBackend.Services; using System; using System.Collections.Generic; using System.Data; using System.Globalization; using System.Linq; using System.Threading.Tasks; using RMuseum.Models.Artifact; using RSecurityBackend.Services.Implementation; using DNTPersianUtils.Core; using Microsoft.Extensions.Caching.Memory; namespace RMuseum.Services.Implementation { /// /// IGanjoorService implementation /// public partial class GanjoorService : IGanjoorService { /// /// Get List of poets /// /// /// /// public async Task> GetPoets(bool websitePoets, bool includeBio = true) { try { var cacheKey = $"/api/ganjoor/poets?websitePoets={websitePoets}&includeBio={includeBio}"; if (!_memoryCache.TryGetValue(cacheKey, out GanjoorPoetViewModel[] poets)) { var res = await (from poet in _context.GanjoorPoets join cat in _context.GanjoorCategories.Where(c => c.ParentId == null) on poet.Id equals cat.PoetId where poet.Published select new GanjoorPoetViewModel() { Id = poet.Id, Name = poet.Name, Description = includeBio ? poet.Description : null, FullUrl = cat.FullUrl, RootCatId = cat.Id, Nickname = poet.Nickname, Published = poet.Published, ImageUrl = poet.RImageId == null ? "" : $"/api/ganjoor/poet/image{cat.FullUrl}.png" } ) .AsNoTracking() .ToListAsync(); StringComparer fa = StringComparer.Create(new CultureInfo("fa-IR"), true); res.Sort((a, b) => fa.Compare(a.Nickname, b.Nickname)); poets = res.ToArray(); _memoryCache.Set(cacheKey, poets); } return new RServiceResult ( poets ); } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// get poet by id /// /// /// public async Task> GetPoetById(int id) { try { var cacheKey = $"/api/ganjoor/poet/{id}"; if(!_memoryCache.TryGetValue(cacheKey, out GanjoorPoetCompleteViewModel poetCat)) { var poet = await _context.GanjoorPoets.Where(p => p.Id == id).AsNoTracking().FirstOrDefaultAsync(); if (poet == null) return new RServiceResult(null); var cat = await _context.GanjoorCategories.Where(c => c.ParentId == null && c.PoetId == id).AsNoTracking().FirstOrDefaultAsync(); poetCat = (await GetCatById(cat.Id)).Result; if(poetCat != null) { _memoryCache.Set(cacheKey, poetCat); } } return new RServiceResult(poetCat); } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// get poet by url /// /// /// public async Task> GetPoetByUrl(string url) { try { // /hafez/ => /hafez : if (url.LastIndexOf('/') == url.Length - 1) { url = url.Substring(0, url.Length - 1); } var cat = await _context.GanjoorCategories.Where(c => c.FullUrl == url && c.ParentId == null).AsNoTracking().SingleOrDefaultAsync(); if (cat == null) return new RServiceResult(null); return await GetCatById(cat.Id); } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// poet image id by url /// /// /// public async Task> GetPoetImageIdByUrl(string url) { try { // /hafez/ => /hafez : if (url.LastIndexOf('/') == url.Length - 1) { url = url.Substring(0, url.Length - 1); } var cat = await _context.GanjoorCategories.Where(c => c.FullUrl == url && c.ParentId == null).AsNoTracking().SingleOrDefaultAsync(); if (cat == null) return new RServiceResult(Guid.Empty); var poet = await _context.GanjoorPoets.Where(p => p.Id == cat.PoetId).AsNoTracking().SingleOrDefaultAsync(); return new RServiceResult((Guid)poet.RImageId); } catch (Exception exp) { return new RServiceResult(Guid.Empty, exp.ToString()); } } /// /// get cat by url /// /// /// /// public async Task> GetCatByUrl(string url, bool poems = true) { try { // /hafez/ => /hafez : if (url.LastIndexOf('/') == url.Length - 1) { url = url.Substring(0, url.Length - 1); } var cat = await _context.GanjoorCategories.Where(c => c.FullUrl == url).AsNoTracking().SingleOrDefaultAsync(); if (cat == null) return new RServiceResult(null); return await GetCatById(cat.Id); } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// get cat by id /// /// /// /// public async Task> GetCatById(int id, bool poems = true) { try { var cat = await _context.GanjoorCategories.Include(c => c.Poet).Include(c => c.Parent).Where(c => c.Id == id).AsNoTracking().FirstOrDefaultAsync(); if (cat == null) return new RServiceResult(null); List ancetors = new List(); var parent = cat.Parent; while (parent != null) { ancetors.Insert(0, new GanjoorCatViewModel() { Id = parent.Id, Title = parent.Title, UrlSlug = parent.UrlSlug, FullUrl = parent.FullUrl }); parent = await _context.GanjoorCategories.Where(c => c.Id == parent.ParentId).AsNoTracking().FirstOrDefaultAsync(); } int nextCatId = await _context.GanjoorCategories.Where(c => c.PoetId == cat.PoetId && c.ParentId == cat.ParentId && c.Id > id).AnyAsync() ? await _context.GanjoorCategories.Where(c => c.PoetId == cat.PoetId && c.ParentId == cat.ParentId && c.Id > id).MinAsync(c => c.Id) : 0; var nextCat = nextCatId == 0 ? null : await _context .GanjoorCategories .Where(c => c.Id == nextCatId) .Select ( c => new GanjoorCatViewModel() { Id = c.Id, Title = c.Title, UrlSlug = c.UrlSlug, FullUrl = c.FullUrl //other fields null } ).AsNoTracking().SingleOrDefaultAsync(); int preCatId = await _context.GanjoorCategories.Where(c => c.PoetId == cat.PoetId && c.ParentId == cat.ParentId && c.Id < id).AnyAsync() ? await _context.GanjoorCategories.Where(c => c.PoetId == cat.PoetId && c.ParentId == cat.ParentId && c.Id < id).MaxAsync(c => c.Id) : 0; var preCat = preCatId == 0 ? null : await _context .GanjoorCategories .Where(c => c.Id == preCatId) .Select ( c => new GanjoorCatViewModel() { Id = c.Id, Title = c.Title, UrlSlug = c.UrlSlug, FullUrl = c.FullUrl //other fields null } ).AsNoTracking().SingleOrDefaultAsync(); GanjoorCatViewModel catViewModel = new GanjoorCatViewModel() { Id = cat.Id, Title = cat.Title, UrlSlug = cat.UrlSlug, FullUrl = cat.FullUrl, Next = nextCat, Previous = preCat, Ancestors = ancetors, Children = await _context.GanjoorCategories.Where(c => c.ParentId == cat.Id).OrderBy(cat => cat.Id).Select ( c => new GanjoorCatViewModel() { Id = c.Id, Title = c.Title, UrlSlug = c.UrlSlug, FullUrl = c.FullUrl } ).AsNoTracking().ToListAsync(), Poems = poems ? await _context.GanjoorPoems.Where(p => p.CatId == cat.Id).OrderBy(p => p.Id).Select ( p => new GanjoorPoemSummaryViewModel() { Id = p.Id, Title = p.Title, UrlSlug = p.UrlSlug, Excerpt = _context.GanjoorVerses.Where(v => v.PoemId == p.Id && v.VOrder == 1).FirstOrDefault().Text } ).AsNoTracking().ToListAsync() : null }; return new RServiceResult ( new GanjoorPoetCompleteViewModel() { Poet = await _context.GanjoorPoets.Where(p => p.Id == cat.PoetId) .Select(p => new GanjoorPoetViewModel() { Id = p.Id, Name = p.Name, Description = p.Description, FullUrl = _context.GanjoorCategories.Where(c => c.PoetId == p.Id && c.ParentId == null).Single().FullUrl, RootCatId = _context.GanjoorCategories.Where(c => c.PoetId == p.Id && c.ParentId == null).Single().Id, Nickname = p.Nickname, Published = p.Published, ImageUrl = p.RImageId == null ? "" : $"/api/ganjoor/poet/image{_context.GanjoorCategories.Where(c => c.PoetId == p.Id && c.ParentId == null).Single().FullUrl}.png" }).AsNoTracking().FirstOrDefaultAsync(), Cat = catViewModel } ); } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// get page url by id /// /// /// public async Task> GetPageUrlById(int id) { try { var dbPage = await _context.GanjoorPages.Where(p => p.Id == id).AsNoTracking().SingleOrDefaultAsync(); if (dbPage == null) return new RServiceResult(null); //not found return new RServiceResult(dbPage.FullUrl); } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// clean cache for paeg by id /// /// /// public async Task CacheCleanForPageById(int id) { var dbPage = await _context.GanjoorPages.Where(p => p.Id == id).AsNoTracking().SingleOrDefaultAsync(); if (dbPage != null) { CacheCleanForPageByUrl(dbPage.FullUrl); } } /// /// clean cache for page by url /// /// public void CacheCleanForPageByUrl(string url) { var cachKey = $"GanjoorService::GetPageByUrl::{url}"; if (_memoryCache.TryGetValue(cachKey, out GanjoorPageCompleteViewModel page)) { _memoryCache.Remove(cachKey); var poemCachKey = $"GetPoemById({page.Id}, {true}, {false}, {true}, {true}, {true}, {true}, {true}, {true}, {true})"; if(_memoryCache.TryGetValue(poemCachKey, out GanjoorPoemCompleteViewModel p)) { _memoryCache.Remove(poemCachKey); } } } /// /// clean cache for page by comment /// /// /// public async Task CacheCleanForComment(int commentId) { var comment = await _context.GanjoorComments.Where(c => c.Id == commentId).SingleOrDefaultAsync(); if(comment != null) { await CacheCleanForPageById(comment.PoemId); } } /// /// get page by url /// /// /// /// public async Task> GetPageByUrl(string url, bool catPoems = true) { try { if (url.IndexOf('?') != -1) { url = url.Substring(0, url.IndexOf('?')); } // /hafez/ => /hafez : if (url.LastIndexOf('/') == url.Length - 1) { url = url.Substring(0, url.Length - 1); } url = url.Replace("//", "/"); //duplicated slashes would be merged var cachKey = $"GanjoorService::GetPageByUrl::{url}"; if (!_memoryCache.TryGetValue(cachKey, out GanjoorPageCompleteViewModel page)) { var dbPage = await _context.GanjoorPages.Where(p => p.FullUrl == url).AsNoTracking().SingleOrDefaultAsync(); if (dbPage == null) return new RServiceResult(null); //not found var secondPoet = dbPage.SecondPoetId == null ? null : await (from poet in _context.GanjoorPoets join cat in _context.GanjoorCategories.Where(c => c.ParentId == null) on poet.Id equals cat.PoetId where poet.Id == (int)dbPage.SecondPoetId orderby poet.Name descending select new GanjoorPoetViewModel() { Id = poet.Id, Name = poet.Name, FullUrl = cat.FullUrl, RootCatId = cat.Id, Nickname = poet.Nickname, Published = poet.Published, ImageUrl = poet.RImageId == null ? "" : $"/api/ganjoor/poet/image{cat.FullUrl}.png" } ) .AsNoTracking().SingleAsync(); page = new GanjoorPageCompleteViewModel() { Id = dbPage.Id, GanjoorPageType = dbPage.GanjoorPageType, Title = dbPage.Title, FullTitle = dbPage.FullTitle, UrlSlug = dbPage.UrlSlug, FullUrl = dbPage.FullUrl, HtmlText = dbPage.HtmlText, SecondPoet = secondPoet }; switch (page.GanjoorPageType) { case GanjoorPageType.PoemPage: { var poemRes = await GetPoemById((int)dbPage.PoemId); if (!string.IsNullOrEmpty(poemRes.ExceptionString)) { return new RServiceResult(null, poemRes.ExceptionString); } page.Poem = poemRes.Result; } break; case GanjoorPageType.CatPage: { var catRes = await GetCatById((int)dbPage.CatId); if (!string.IsNullOrEmpty(catRes.ExceptionString)) { return new RServiceResult(null, catRes.ExceptionString); } page.PoetOrCat = catRes.Result; } break; default: { if (dbPage.PoetId != null) { var poetRes = await GetPoetById((int)dbPage.PoetId); if (!string.IsNullOrEmpty(poetRes.ExceptionString)) { return new RServiceResult(null, poetRes.ExceptionString); } page.PoetOrCat = poetRes.Result; var pre = await _context.GanjoorPages.Where(p => p.GanjoorPageType == page.GanjoorPageType && p.ParentId == dbPage.ParentId && p.PoetId == dbPage.PoetId && ((p.PageOrder < dbPage.PageOrder) || (p.PageOrder == dbPage.PageOrder && p.Id < dbPage.Id))) .OrderByDescending(p => p.PageOrder) .ThenByDescending(p => p.Id) .AsNoTracking() .FirstOrDefaultAsync(); if (pre != null) { page.Previous = new GanjoorPageSummaryViewModel() { Id = pre.Id, Title = pre.Title, FullUrl = pre.FullUrl }; } var next = await _context.GanjoorPages.Where(p => p.GanjoorPageType == page.GanjoorPageType && p.ParentId == dbPage.ParentId && p.PoetId == dbPage.PoetId && ((p.PageOrder > dbPage.PageOrder) || (p.PageOrder == dbPage.PageOrder && p.Id > dbPage.Id))) .OrderBy(p => p.PageOrder) .ThenBy(p => p.Id) .AsNoTracking() .FirstOrDefaultAsync(); if (next != null) { page.Next = new GanjoorPageSummaryViewModel() { Id = next.Id, Title = next.Title, FullUrl = next.FullUrl }; } } } break; } if(page.FullUrl != "/hashieha" && page.FullUrl != "/vazn" && page.FullUrl != "/simi" && page.FullUrl != "/audioclip") { _memoryCache.Set(cachKey, page); } } return new RServiceResult(page); } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// get poem recitations (PlainText/HtmlText are intentionally empty) /// /// /// public async Task> GetPoemRecitations(int id) { try { var source = from audio in _context.Recitations join poem in _context.GanjoorPoems on audio.GanjoorPostId equals poem.Id where audio.ReviewStatus == AudioReviewStatus.Approved && poem.Id == id orderby audio.AudioOrder select new PublicRecitationViewModel() { Id = audio.Id, PoemId = audio.GanjoorPostId, PoemFullTitle = poem.FullTitle, PoemFullUrl = poem.FullUrl, AudioTitle = audio.AudioTitle, AudioArtist = audio.AudioArtist, AudioArtistUrl = audio.AudioArtistUrl, AudioSrc = audio.AudioSrc, AudioSrcUrl = audio.AudioSrcUrl, LegacyAudioGuid = audio.LegacyAudioGuid, Mp3FileCheckSum = audio.Mp3FileCheckSum, Mp3SizeInBytes = audio.Mp3SizeInBytes, PublishDate = audio.ReviewDate, FileLastUpdated = audio.FileLastUpdated, Mp3Url = $"https://ganjgah.ir/api/audio/file/{audio.Id}.mp3", XmlText = $"https://ganjgah.ir/api/audio/xml/{audio.Id}", PlainText = "", //poem.PlainText HtmlText = "",//poem.HtmlText }; return new RServiceResult(await source.AsNoTracking().ToArrayAsync()); } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// get poem comments /// /// /// /// public async Task> GetPoemComments(int poemId, Guid userId) { try { var source = from comment in _context.GanjoorComments.Include(c => c.User) where (comment.Status == PublishStatus.Published || (userId != Guid.Empty && comment.Status == PublishStatus.Awaiting && comment.UserId == userId)) && comment.PoemId == poemId orderby comment.CommentDate select new GanjoorCommentSummaryViewModel() { Id = comment.Id, AuthorName = comment.User == null ? comment.AuthorName : $"{comment.User.NickName}", AuthorUrl = comment.AuthorUrl, CommentDate = comment.CommentDate, HtmlComment = comment.HtmlComment, PublishStatus = comment.Status == PublishStatus.Awaiting ? "در انتظار تأیید" : "", InReplyToId = comment.InReplyToId, UserId = comment.UserId }; GanjoorCommentSummaryViewModel[] allComments = await source.AsNoTracking().ToArrayAsync(); foreach (GanjoorCommentSummaryViewModel comment in allComments) { comment.AuthorName = comment.AuthorName.ToPersianNumbers().ApplyCorrectYeKe(); } GanjoorCommentSummaryViewModel[] rootComments = allComments.Where(c => c.InReplyToId == null).ToArray(); foreach (GanjoorCommentSummaryViewModel comment in rootComments) { _FindReplies(comment, allComments); } return new RServiceResult(rootComments); } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } private void _FindReplies(GanjoorCommentSummaryViewModel comment, GanjoorCommentSummaryViewModel[] allComments) { comment.Replies = allComments.Where(c => c.InReplyToId == comment.Id).ToArray(); foreach (GanjoorCommentSummaryViewModel reply in comment.Replies) { _FindReplies(reply, allComments); } } /// /// new comment /// /// /// /// /// /// /// public async Task> NewComment(Guid userId, string ip, int poemId, string content, int? inReplyTo) { try { if (string.IsNullOrEmpty(content)) { return new RServiceResult(null, "متن حاشیه خالی است."); } var userRes = await _appUserService.GetUserInformation(userId); if(string.IsNullOrEmpty(userRes.Result.NickName)) { return new RServiceResult(null, "لطفاً با مراجعه به پیشخان کاربری (دکمهٔ گوشهٔ پایین سمت راست) «نام مستعار» خود را مشخص کنید و سپس اقدام به ارسال حاشیه بفرمایید."); } content = content.ApplyCorrectYeKe(); GanjoorComment comment = new GanjoorComment() { UserId = userId, AuthorIpAddress = ip, CommentDate = DateTime.Now, HtmlComment = content, InReplyToId = inReplyTo, PoemId = poemId, Status = PublishStatus.Published, }; _context.GanjoorComments.Add(comment); await _context.SaveChangesAsync(); if(inReplyTo != null) { GanjoorComment refComment = await _context.GanjoorComments.Where(c => c.Id == (int)inReplyTo).SingleAsync(); if(refComment.UserId != null) { var poem = await _context.GanjoorPoems.Where(p => p.Id == comment.PoemId).SingleAsync(); await _notificationService.PushNotification((Guid)refComment.UserId, "پاسخ به حاشیهٔ شما", $"{userRes.Result.NickName} برای حاشیهٔ شما روی {poem.FullTitle} این پاسخ را نوشته است: {Environment.NewLine}" + $"{content}" + $"این متن حاشیهٔ خود شماست: {Environment.NewLine}" + $"{refComment.HtmlComment}" ); } } await CacheCleanForPageById(poemId); return new RServiceResult ( new GanjoorCommentSummaryViewModel() { Id = comment.Id, AuthorName = $"{userRes.Result.NickName}", AuthorUrl = comment.AuthorUrl, CommentDate = comment.CommentDate, HtmlComment = comment.HtmlComment, PublishStatus = comment.Status == PublishStatus.Awaiting ? "در انتظار تأیید" : "", InReplyToId = comment.InReplyToId, UserId = comment.UserId, Replies = new GanjoorCommentSummaryViewModel[] { } } ); } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// update user's own comment /// /// /// /// /// public async Task> EditMyComment(Guid userId, int commentId, string htmlComment) { try { GanjoorComment comment = await _context.GanjoorComments.Where(c => c.Id == commentId && c.UserId == userId).SingleOrDefaultAsync();//userId is not part of key but it helps making call secure if (comment == null) { return new RServiceResult(false); //not found } await CacheCleanForComment(commentId); htmlComment = htmlComment.ApplyCorrectYeKe(); comment.HtmlComment = htmlComment; _context.GanjoorComments.Update(comment); await _context.SaveChangesAsync(); return new RServiceResult(true); } catch (Exception exp) { return new RServiceResult(false, exp.ToString()); } } /// /// delete a reported or abusive comment /// /// /// /// public async Task> DeleteModerateComment(int commentId, string reason) { try { GanjoorComment comment = await _context.GanjoorComments.Where(c => c.Id == commentId).SingleOrDefaultAsync(); if (comment == null) { return new RServiceResult(false); //not found } await CacheCleanForComment(commentId); if (comment.UserId != null) { reason = string.IsNullOrEmpty(reason) ? "" : $"علت ارائه شده برای حذف یا متن گزارش کاربر شاکی: {Environment.NewLine}" + $"{reason} {Environment.NewLine}"; await _notificationService.PushNotification((Guid)comment.UserId, "حذف حاشیهٔ شما", $"حاشیهٔ شما به دلیل ناسازگاری با قوانین حاشیه‌گذاری گنجور و طبق گزارشات دیگر کاربران حذف شده است..{Environment.NewLine}" + $"{reason}" + $"این متن حاشیهٔ حذف شدهٔ شماست: {Environment.NewLine}" + $"{comment.HtmlComment}" ); } //if user has got replies, delete them and notify their owners of what happened var replies = await _FindReplies(comment); for (int i = replies.Count - 1; i >= 0; i--) { if (replies[i].UserId != null) { await _notificationService.PushNotification((Guid)replies[i].UserId, "حذف پاسخ شما به حاشیه", $"پاسخ شما به یکی از حاشیه‌های گنجور به دلیل حذف زنجیرهٔ حاشیه توسط یکی از حاشیه‌گذاران حذف شده است.{Environment.NewLine}" + $"این متن حاشیهٔ حذف شدهٔ شماست: {Environment.NewLine}" + $"{replies[i].HtmlComment}" ); } _context.GanjoorComments.Remove(replies[i]); } _context.GanjoorComments.Remove(comment); await _context.SaveChangesAsync(); return new RServiceResult(true); } catch (Exception exp) { return new RServiceResult(false, exp.ToString()); } } /// /// delete user own comment /// /// /// /// public async Task> DeleteMyComment(Guid userId, int commentId) { try { GanjoorComment comment = await _context.GanjoorComments.Where(c => c.Id == commentId && c.UserId == userId).SingleOrDefaultAsync();//userId is not part of key but it helps making call secure if (comment == null) { return new RServiceResult(false); //not found } await CacheCleanForComment(commentId); //if user has got replies, delete them and notify their owners of what happened var replies = await _FindReplies(comment); for (int i = replies.Count - 1; i >= 0; i--) { if (replies[i].UserId != null && replies[i].UserId != userId) { await _notificationService.PushNotification((Guid)replies[i].UserId, "حذف پاسخ شما به حاشیه", $"پاسخ شما به یکی از حاشیه‌های گنجور به دلیل حذف زنجیرهٔ حاشیه توسط یکی از حاشیه‌گذاران حذف شده است.{Environment.NewLine}" + $"این متن حاشیهٔ حذف شدهٔ شماست: {Environment.NewLine}" + $"{replies[i].HtmlComment}" ); } _context.GanjoorComments.Remove(replies[i]); } _context.GanjoorComments.Remove(comment); await _context.SaveChangesAsync(); return new RServiceResult(true); } catch (Exception exp) { return new RServiceResult(false, exp.ToString()); } } private async Task> _FindReplies(GanjoorComment comment) { List replies = await _context.GanjoorComments.Where(c => c.InReplyToId == comment.Id).AsNoTracking().ToListAsync(); List replyToReplies = new List(); foreach (GanjoorComment reply in replies) { replyToReplies.AddRange(await _FindReplies(reply)); } if(replyToReplies.Count > 0) { replies.AddRange(replyToReplies); } return replies; } /// /// get recent comments /// /// /// /// /// public async Task> GetRecentComments(PagingParameterModel paging, Guid filterUserId, bool onlyPublished) { try { var source = from comment in _context.GanjoorComments.Include(c => c.Poem).Include(c => c.User).Include(c => c.InReplyTo).ThenInclude(r => r.User) where ((comment.Status == PublishStatus.Published) || !onlyPublished) && ((filterUserId == Guid.Empty) || (filterUserId != Guid.Empty && comment.UserId == filterUserId)) orderby comment.CommentDate descending select new GanjoorCommentFullViewModel() { Id = comment.Id, AuthorName = comment.User == null ? comment.AuthorName : $"{comment.User.NickName}", AuthorUrl = comment.AuthorUrl, CommentDate = comment.CommentDate, HtmlComment = comment.HtmlComment, InReplayTo = comment.InReplyTo == null ? null : new GanjoorCommentSummaryViewModel() { Id = comment.InReplyTo.Id, AuthorName = comment.InReplyTo.User == null ? comment.InReplyTo.AuthorName : $"{comment.InReplyTo.User.NickName}", AuthorUrl = comment.InReplyTo.AuthorUrl, CommentDate = comment.InReplyTo.CommentDate, HtmlComment = comment.InReplyTo.HtmlComment, PublishStatus = "" }, Poem = new GanjoorPoemSummaryViewModel() { Id = comment.Poem.Id, Title = comment.Poem.FullTitle, UrlSlug = comment.Poem.FullUrl, Excerpt = "" } }; (PaginationMetadata PagingMeta, GanjoorCommentFullViewModel[] Items) paginatedResult = await QueryablePaginator.Paginate(source, paging); foreach (GanjoorCommentFullViewModel comment in paginatedResult.Items) { comment.AuthorName = comment.AuthorName.ToPersianNumbers().ApplyCorrectYeKe(); } return new RServiceResult<(PaginationMetadata PagingMeta, GanjoorCommentFullViewModel[] Items)>(paginatedResult); } catch (Exception exp) { return new RServiceResult<(PaginationMetadata PagingMeta, GanjoorCommentFullViewModel[] Items)>((PagingMeta: null, Items: null), exp.ToString()); } } /// /// report a comment /// /// /// /// id of report record public async Task> ReportComment(Guid userId, GanjoorPostReportCommentViewModel report) { try { GanjoorCommentAbuseReport r = new GanjoorCommentAbuseReport() { GanjoorCommentId = report.CommentId, ReportedById = userId, ReasonCode = report.ReasonCode, ReasonText = report.ReasonText, }; _context.GanjoorReportedComments.Add(r); await _context.SaveChangesAsync(); return new RServiceResult(r.GanjoorCommentId); } catch (Exception exp) { return new RServiceResult(0, exp.ToString()); } } /// /// delete a report /// /// /// public async Task> DeleteReport(int id) { try { GanjoorCommentAbuseReport report = await _context.GanjoorReportedComments.Where(r => r.Id == id).SingleOrDefaultAsync(); if(report == null) { return new RServiceResult(false); } _context.GanjoorReportedComments.Remove(report); await _context.SaveChangesAsync(); return new RServiceResult(true); } catch (Exception exp) { return new RServiceResult(false, exp.ToString()); } } /// /// Get list of reported comments /// /// /// public async Task> GetReportedComments(PagingParameterModel paging) { try { var source = from report in _context.GanjoorReportedComments join comment in _context.GanjoorComments.Include(c => c.Poem).Include(c => c.User).Include(c => c.InReplyTo).ThenInclude(r => r.User) on report.GanjoorCommentId equals comment.Id orderby report.Id descending select new GanjoorCommentAbuseReportViewModel() { Id = report.Id, ReasonCode = report.ReasonCode, ReasonText = report.ReasonText, Comment = new GanjoorCommentFullViewModel() { Id = comment.Id, AuthorName = comment.User == null ? comment.AuthorName : $"{comment.User.NickName}", AuthorUrl = comment.AuthorUrl, CommentDate = comment.CommentDate, HtmlComment = comment.HtmlComment, InReplayTo = comment.InReplyTo == null ? null : new GanjoorCommentSummaryViewModel() { Id = comment.InReplyTo.Id, AuthorName = comment.InReplyTo.User == null ? comment.InReplyTo.AuthorName : $"{comment.InReplyTo.User.NickName}", AuthorUrl = comment.InReplyTo.AuthorUrl, CommentDate = comment.InReplyTo.CommentDate, HtmlComment = comment.InReplyTo.HtmlComment, PublishStatus = "" }, Poem = new GanjoorPoemSummaryViewModel() { Id = comment.Poem.Id, Title = comment.Poem.FullTitle, UrlSlug = comment.Poem.FullUrl, Excerpt = "" } } }; (PaginationMetadata PagingMeta, GanjoorCommentAbuseReportViewModel[] Items) paginatedResult = await QueryablePaginator.Paginate(source, paging); foreach (GanjoorCommentAbuseReportViewModel report in paginatedResult.Items) { report.Comment.AuthorName = report.Comment.AuthorName.ToPersianNumbers().ApplyCorrectYeKe(); } return new RServiceResult<(PaginationMetadata PagingMeta, GanjoorCommentAbuseReportViewModel[] Items)>(paginatedResult); } catch (Exception exp) { return new RServiceResult<(PaginationMetadata PagingMeta, GanjoorCommentAbuseReportViewModel[] Items)>((PagingMeta: null, Items: null), exp.ToString()); } } /// /// get poem images by id (some fields are intentionally field with blank or null), /// EntityImageId : the most important data field, image url is https://ganjgah.ir/api/images/thumb/{EntityImageId}.jpg or https://ganjgah.ir/api/images/norm/{EntityImageId}.jpg /// /// /// public async Task> GetPoemImages(int id) { try { var museumSrc = from link in _context.GanjoorLinks.Include(l => l.Artifact).Include(l => l.Item).ThenInclude(i => i.Images) join poem in _context.GanjoorPoems on link.GanjoorPostId equals poem.Id where link.DisplayOnPage == true && link.ReviewResult == Models.GanjoorIntegration.ReviewResult.Approved && poem.Id == id orderby link.ReviewDate select new PoemRelatedImage() { PoemRelatedImageType = PoemRelatedImageType.MuseumLink, ThumbnailImageUrl = $"https://ganjgah.ir/api/images/thumb/{link.Item.Images.First().Id}.jpg", TargetPageUrl = $"https://museum.ganjoor.net/items/{link.Artifact.FriendlyUrl}/{link.Item.FriendlyUrl}", AltText = $"{link.Artifact.Name} » {link.Item.Name}", }; List museumImages = await museumSrc.ToListAsync(); var externalSrc = from link in _context.PinterestLinks join poem in _context.GanjoorPoems on link.GanjoorPostId equals poem.Id where link.ReviewResult == Models.GanjoorIntegration.ReviewResult.Approved && poem.Id == id orderby link.ReviewDate select new PoemRelatedImage() { PoemRelatedImageType = PoemRelatedImageType.ExternalLink, ThumbnailImageUrl = $"https://ganjgah.ir/api/images/thumb/{link.Item.Images.First().Id}.jpg", TargetPageUrl = link.PinterestUrl, AltText = link.AltText, }; museumImages.AddRange(await externalSrc.AsNoTracking().ToListAsync()); for (int i = 0; i < museumImages.Count; i++) { museumImages[i].ImageOrder = 0; } return new RServiceResult(museumImages.ToArray()); } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// Get Poem By Url /// /// /// /// /// /// /// /// /// /// /// /// public async Task> GetPoemByUrl(string url, bool catInfo = true, bool catPoems = false, bool rhymes = true, bool recitations = true, bool images = true, bool songs = true, bool comments = true, bool verseDetails = true, bool navigation = true) { try { // /hafez/ => /hafez : if (url.LastIndexOf('/') == url.Length - 1) { url = url.Substring(0, url.Length - 1); } var poem = await _context.GanjoorPoems.Where(p => p.FullUrl == url).SingleOrDefaultAsync(); if (poem == null) { return new RServiceResult(null); //not found } return await GetPoemById(poem.Id, catInfo, catPoems, rhymes, recitations, images, songs, comments, verseDetails, navigation); } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// Get Poem By Id /// /// /// /// /// /// /// /// /// /// /// /// public async Task> GetPoemById(int id, bool catInfo = true, bool catPoems = false, bool rhymes = true, bool recitations = true, bool images = true, bool songs = true, bool comments = true, bool verseDetails = true, bool navigation = true) { try { var cachKey = $"GetPoemById({id}, {catInfo}, {catPoems}, {rhymes}, {recitations}, {images}, {songs}, {comments}, {verseDetails}, {navigation})"; if(!_memoryCache.TryGetValue(cachKey, out GanjoorPoemCompleteViewModel poemViewModel)) { var poem = await _context.GanjoorPoems.Include(p => p.GanjoorMetre).Where(p => p.Id == id).AsNoTracking().SingleOrDefaultAsync(); if (poem == null) { return new RServiceResult(null); //not found } GanjoorPoetCompleteViewModel cat = null; if (catInfo) { var catRes = await GetCatById(poem.CatId, catPoems); if (!string.IsNullOrEmpty(catRes.ExceptionString)) { return new RServiceResult(null, catRes.ExceptionString); } cat = catRes.Result; } GanjoorPoemSummaryViewModel next = null; if (navigation) { int nextId = await _context.GanjoorPoems .Where(p => p.CatId == poem.CatId && p.Id > poem.Id) .AnyAsync() ? await _context.GanjoorPoems .Where(p => p.CatId == poem.CatId && p.Id > poem.Id) .MinAsync(p => p.Id) : 0; if (nextId != 0) { next = await _context.GanjoorPoems.Where(p => p.Id == nextId).Select ( p => new GanjoorPoemSummaryViewModel() { Id = p.Id, Title = p.Title, UrlSlug = p.UrlSlug, Excerpt = _context.GanjoorVerses.Where(v => v.PoemId == p.Id && v.VOrder == 1).FirstOrDefault().Text } ).AsNoTracking().SingleAsync(); } } GanjoorPoemSummaryViewModel previous = null; if (navigation) { int preId = await _context.GanjoorPoems .Where(p => p.CatId == poem.CatId && p.Id < poem.Id) .AnyAsync() ? await _context.GanjoorPoems .Where(p => p.CatId == poem.CatId && p.Id < poem.Id) .MaxAsync(p => p.Id) : 0; if (preId != 0) { previous = await _context.GanjoorPoems.Where(p => p.Id == preId).Select ( p => new GanjoorPoemSummaryViewModel() { Id = p.Id, Title = p.Title, UrlSlug = p.UrlSlug, Excerpt = _context.GanjoorVerses.Where(v => v.PoemId == p.Id && v.VOrder == 1).FirstOrDefault().Text } ).AsNoTracking().SingleAsync(); } } PublicRecitationViewModel[] rc = null; if (recitations) { var rcRes = await GetPoemRecitations(id); if (!string.IsNullOrEmpty(rcRes.ExceptionString)) return new RServiceResult(null, rcRes.ExceptionString); rc = rcRes.Result; } PoemRelatedImage[] imgs = null; if (images) { var imgsRes = await GetPoemImages(id); if (!string.IsNullOrEmpty(imgsRes.ExceptionString)) return new RServiceResult(null, imgsRes.ExceptionString); imgs = imgsRes.Result; } GanjoorVerseViewModel[] verses = null; if (verseDetails) { verses = await _context.GanjoorVerses .Where(v => v.PoemId == id) .OrderBy(v => v.VOrder) .Select ( v => new GanjoorVerseViewModel() { Id = v.Id, VOrder = v.VOrder, VersePosition = v.VersePosition, Text = v.Text } ).AsNoTracking().ToArrayAsync(); }; PoemMusicTrackViewModel[] tracks = null; if (songs) { var songsRes = await GetPoemSongs(id, true, PoemMusicTrackType.All); if (!string.IsNullOrEmpty(songsRes.ExceptionString)) return new RServiceResult(null, songsRes.ExceptionString); tracks = songsRes.Result; } GanjoorCommentSummaryViewModel[] poemComments = null; if (comments) { var commentsRes = await GetPoemComments(id, Guid.Empty); if (!string.IsNullOrEmpty(commentsRes.ExceptionString)) return new RServiceResult(null, commentsRes.ExceptionString); poemComments = commentsRes.Result; } poemViewModel = new GanjoorPoemCompleteViewModel() { Id = poem.Id, Title = poem.Title, FullTitle = poem.FullTitle, FullUrl = poem.FullUrl, UrlSlug = poem.UrlSlug, HtmlText = poem.HtmlText, PlainText = poem.PlainText, GanjoorMetre = poem.GanjoorMetre, RhymeLetters = poem.RhymeLetters, SourceName = poem.SourceName, SourceUrlSlug = poem.SourceUrlSlug, OldTag = poem.OldTag, OldTagPageUrl = poem.OldTagPageUrl, Category = cat, Next = next, Previous = previous, Recitations = rc, Images = imgs, Verses = verses, Songs = tracks, Comments = poemComments }; _memoryCache.Set(cachKey, poemViewModel); } return new RServiceResult ( poemViewModel ); } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// get poem related songs /// /// /// /// /// public async Task> GetPoemSongs(int id, bool approved, PoemMusicTrackType trackType) { try { return new RServiceResult ( await _context.GanjoorPoemMusicTracks .Where ( t => t.PoemId == id && t.Approved == approved && t.Rejected == false && (trackType == PoemMusicTrackType.All || t.TrackType == trackType) ) .OrderBy(t => t.SongOrder) .Select ( t => new PoemMusicTrackViewModel() { Id = t.Id, PoemId = t.PoemId, TrackType = t.TrackType, ArtistName = t.ArtistName, ArtistUrl = t.ArtistUrl, AlbumName = t.AlbumName, AlbumUrl = t.AlbumUrl, TrackName = t.TrackName, TrackUrl = t.TrackUrl, Description = t.Description, BrokenLink = t.BrokenLink, GolhaTrackId = t.GolhaTrackId == null ? 0 : (int)t.GolhaTrackId, Approved = t.Approved, Rejected = t.Rejected, RejectionCause = t.RejectionCause } ).AsNoTracking().ToArrayAsync() ); } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// suggest song /// /// /// /// public async Task> SuggestSong(Guid userId, PoemMusicTrackViewModel song) { try { song.Approved = false; song.Rejected = false; song.RejectionCause = ""; song.BrokenLink = false; if (song.TrackType == PoemMusicTrackType.Golha) { var golhaTrack = await _context.GolhaTracks.Include(g => g.GolhaProgram).ThenInclude(p => p.GolhaCollection).Where(g => g.Id == song.GolhaTrackId).FirstOrDefaultAsync(); if (golhaTrack == null) { return new RServiceResult(null, "مشخصات قطعهٔ گلها درست نیست."); } var alreadySuggestedSong = await _context.GanjoorPoemMusicTracks.Where(t => t.PoemId == song.PoemId && t.TrackType == song.TrackType && t.GolhaTrackId == song.GolhaTrackId).FirstOrDefaultAsync(); if (alreadySuggestedSong != null) { return new RServiceResult(null, "این آهنگ پیشتر برای این شعر پیشنهاد داده شده است."); } song.ArtistName = ""; song.ArtistUrl = ""; song.AlbumName = $"{golhaTrack.GolhaProgram.GolhaCollection.Name} » شمارهٔ {golhaTrack.GolhaProgram.Title.ToPersianNumbers().ApplyCorrectYeKe()}"; song.AlbumUrl = ""; song.TrackName = $"{golhaTrack.Timing.ToPersianNumbers().ApplyCorrectYeKe()} {golhaTrack.Title}"; song.TrackUrl = golhaTrack.GolhaProgram.Url; } else { var alreadySuggestedSong = await _context.GanjoorPoemMusicTracks.Where(t => t.PoemId == song.PoemId && t.TrackType == song.TrackType && t.TrackUrl == song.TrackUrl).FirstOrDefaultAsync(); if (alreadySuggestedSong != null) { return new RServiceResult(null, "این آهنگ پیشتر برای این شعر پیشنهاد داده شده است."); } } var sug = new PoemMusicTrack() { TrackType = song.TrackType, PoemId = song.PoemId, ArtistName = song.ArtistName, ArtistUrl = song.ArtistUrl, AlbumName = song.AlbumName, AlbumUrl = song.AlbumUrl, TrackName = song.TrackName, TrackUrl = song.TrackUrl, SuggestedById = userId, Description = song.Description, GolhaTrackId = song.TrackType == PoemMusicTrackType.Golha ? song.GolhaTrackId : (int?)null, Approved = false, Rejected = false, RejectionCause = "" }; GanjoorSinger singer = await _context.GanjoorSingers.Where(s => s.Url == song.ArtistUrl).FirstOrDefaultAsync(); if (singer != null) { sug.SingerId = singer.Id; } _context.GanjoorPoemMusicTracks.Add ( sug ); await _context.SaveChangesAsync(); sug.SongOrder = sug.Id; _context.GanjoorPoemMusicTracks.Update(sug); await _context.SaveChangesAsync(); song.Id = sug.Id; return new RServiceResult(song); } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// get unreviewed count /// /// /// public async Task> GetUnreviewedSongsCount(Guid suggestedById) { try { return new RServiceResult(await _context.GanjoorPoemMusicTracks .Where(p => p.Approved == false && p.Rejected == false && (suggestedById == Guid.Empty || p.SuggestedById == suggestedById)) .CountAsync()); } catch (Exception exp) { return new RServiceResult(0, exp.ToString()); } } /// /// next unreviewed track /// /// /// /// public async Task> GetNextUnreviewedSong(int skip, Guid suggestedById) { try { var song = await _context.GanjoorPoemMusicTracks .Where(p => p.Approved == false && p.Rejected == false && (suggestedById == Guid.Empty || p.SuggestedById == suggestedById)) .OrderBy(p => p.Id).Skip(skip).AsNoTracking().FirstOrDefaultAsync(); if (song != null) { return new RServiceResult ( new PoemMusicTrackViewModel() { Id = song.Id, TrackType = song.TrackType, PoemId = song.PoemId, ArtistName = song.ArtistName, ArtistUrl = song.ArtistUrl, AlbumName = song.AlbumName, AlbumUrl = song.AlbumUrl, TrackName = song.TrackName, TrackUrl = song.TrackUrl, Description = song.Description, GolhaTrackId = song.TrackType == PoemMusicTrackType.Golha ? (int)song.GolhaTrackId : 0, BrokenLink = song.BrokenLink, Approved = song.Approved, Rejected = song.Rejected, RejectionCause = song.RejectionCause } ); } return new RServiceResult(null); //not found } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// review song /// /// /// public async Task> ReviewSong(PoemMusicTrackViewModel song) { try { if (song.Approved && song.Rejected) return new RServiceResult(null, "song.Approved && song.Rejected"); var track = await _context.GanjoorPoemMusicTracks.Where(t => t.Id == song.Id).SingleOrDefaultAsync(); track.TrackType = song.TrackType; track.ArtistName = song.ArtistName; track.ArtistUrl = song.ArtistUrl; track.AlbumName = song.AlbumName; track.AlbumUrl = song.AlbumUrl; track.TrackName = song.TrackName; track.TrackUrl = song.TrackUrl; if (!track.Approved && song.Approved) { track.ApprovalDate = DateTime.Now; } track.Approved = song.Approved; track.Rejected = song.Rejected; track.RejectionCause = song.RejectionCause; track.BrokenLink = song.BrokenLink; GanjoorSinger singer = await _context.GanjoorSingers.Where(s => s.Url == track.ArtistUrl).FirstOrDefaultAsync(); if (singer != null) { track.SingerId = singer.Id; } _context.GanjoorPoemMusicTracks.Update(track); await _context.SaveChangesAsync(); if(track.Approved) { await CacheCleanForPageById(track.PoemId); } return new RServiceResult(song); } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// direct insert song /// /// /// public async Task> DirectInsertSong(PoemMusicTrackViewModel song) { try { var poem = await _context.GanjoorPoems.Where(p => p.Id == song.PoemId).SingleOrDefaultAsync(); if (poem == null) return new RServiceResult(null, "poem == null"); if ( string.IsNullOrEmpty(song.ArtistName) || string.IsNullOrEmpty(song.ArtistUrl) || string.IsNullOrEmpty(song.AlbumName) || string.IsNullOrEmpty(song.AlbumUrl) || string.IsNullOrEmpty(song.TrackName) || string.IsNullOrEmpty(song.TrackUrl) || song.TrackType != PoemMusicTrackType.BeepTunesOrKhosousi ) { return new RServiceResult(null, "data validation err"); } var duplicated = await _context.GanjoorPoemMusicTracks.Where(m => m.PoemId == song.PoemId && m.TrackUrl == song.TrackUrl).FirstOrDefaultAsync(); if (duplicated != null) { return new RServiceResult(null, "duplicated song url for this poem"); } PoemMusicTrack track = new PoemMusicTrack(); track.PoemId = song.PoemId; track.TrackType = song.TrackType; track.ArtistName = song.ArtistName; track.ArtistUrl = song.ArtistUrl; track.AlbumName = song.AlbumName; track.AlbumUrl = song.AlbumUrl; track.TrackName = song.TrackName; track.TrackUrl = song.TrackUrl; track.ApprovalDate = DateTime.Now; track.Approved = true; track.Rejected = false; track.BrokenLink = song.BrokenLink; GanjoorSinger singer = await _context.GanjoorSingers.Where(s => s.Url == track.ArtistUrl).FirstOrDefaultAsync(); if (singer != null) { track.SingerId = singer.Id; } _context.GanjoorPoemMusicTracks.Add(track); await _context.SaveChangesAsync(); track.SongOrder = track.Id; song.Id = track.Id; _context.GanjoorPoemMusicTracks.Update(track); await _context.SaveChangesAsync(); await CacheCleanForPageById(track.PoemId); return new RServiceResult(song); } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// random poem id from hafez sonnets and old c.ganjoor.net service /// /// private int _GetRandomPoemId(int poetId, int loopBreaker = 0) { if (loopBreaker > 10) return 0; Random r = new Random(DateTime.Now.Millisecond); switch (poetId) { case 2://حافظ { //this is magic number based method! int startPoemId = 2130; int endPoemId = 2624 + 1; //one is added for مژده ای دل که مسیحا نفسی می‌آید int poemId = r.Next(startPoemId, endPoemId); if (poemId == endPoemId) { poemId = 33179;//مژده ای دل که مسیحا نفسی می‌آید } return poemId; } case 3://خیام return r.Next(1119, 1296); case 26://ابوسعید return r.Next(20509, 21232); case 22://صائب return r.Next(52198, 59193); case 7://سعدی return r.Next(9323, 9959); case 28://بابا طاهر return r.Next(21309, 21674); case 5://مولانا return r.Next(2625, 5853); case 19://اوحدی return r.Next(16955, 17839); case 35://شهریار return r.Next(27065, 27224); case 20://خواجو return r.Next(18288, 19219); case 32://فروغی return r.Next(22996, 23511); case 21://عراقی return r.Next(19222, 19526); case 40://سلمان return r.Next(38411, 39320); case 29://محتشم return r.Next(21744, 22338); case 34://امیرخسرو return r.Next(60582, 62578); case 31://سیف return r.Next(62837, 63418); case 33://عبید return r.Next(23551, 23656); case 25://هاتف return r.Next(20275, 20364); case 41://رهی return r.Next(39441, 39546); } int[] poetIdArray = new int[] { 2, 3, 26, 22, 7, 28, 5, 19, 35, 20, 23, 21, 40, 29, 34, 31, 33, 25, 41 }; return _GetRandomPoemId(poetIdArray[r.Next(0, poetIdArray.Length - 1)], loopBreaker++); } /// /// get a random poem from hafez /// /// /// /// public async Task> Faal(int poetId = 2, bool recitation = true) { try { int poemId = _GetRandomPoemId(poetId); var poem = await _context.GanjoorPoems.Where(p => p.Id == poemId).AsNoTracking().SingleOrDefaultAsync(); PublicRecitationViewModel[] recitations = poem == null || !recitation ? new PublicRecitationViewModel[] { } : (await GetPoemRecitations(poemId)).Result; int loopPreventer = 0; while (poem == null || (recitation && recitations.Length == 0)) { poem = await _context.GanjoorPoems.Where(p => p.Id == poemId).AsNoTracking().SingleOrDefaultAsync(); recitations = poem == null ? new PublicRecitationViewModel[] { } : (await GetPoemRecitations(poemId)).Result; loopPreventer++; if (loopPreventer > 5) { return new RServiceResult(null); } } return await GetPoemById(poemId, false, false, false, recitation, false, false, false, true /*verse details*/, false); } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// older search method /// /// /// /// /// public async Task> GetVersesByQuery(string query, int poetId, PagingParameterModel paging) { try { var versesQ = _context.GanjoorVerses.AsNoTracking().Include(v => v.Poem).ThenInclude(p => p.Cat) .Where(v => v.Text.Contains(query)); if (poetId != 0) { versesQ = versesQ.Where(v => v.Poem.Cat.PoetId == poetId); } var source = versesQ.Select( v => new GanjoorSearchVerseViewModel { Id = v.Id, VOrder = v.VOrder, VersePosition = v.VersePosition, Text = v.Text, PoemId = v.PoemId, PoetId = v.Poem.Cat.PoetId, PoemFullTitle = v.Poem.FullTitle, PoemFullUrl = v.Poem.FullUrl, } ); (PaginationMetadata PagingMeta, GanjoorSearchVerseViewModel[] Items) paginatedResult = await QueryablePaginator.Paginate(source, paging); return new RServiceResult<(PaginationMetadata PagingMeta, GanjoorSearchVerseViewModel[])>(paginatedResult); } catch (Exception exp) { return new RServiceResult<(PaginationMetadata PagingMeta, GanjoorSearchVerseViewModel[] Items)>((PagingMeta: null, Items: null), exp.ToString()); } } /// /// Get Similar Poems accroding to prosody and rhyme informations /// /// /// /// /// /// public async Task> GetSimilarPoems(PagingParameterModel paging, string metre, string rhyme, int? poetId) { try { if (string.IsNullOrEmpty(rhyme)) rhyme = ""; var source = _context.GanjoorPoems.Include(p => p.Cat).ThenInclude(c => c.Poet).Include(p => p.GanjoorMetre) .Where(p => (poetId == null || p.Cat.PoetId == poetId) && (p.GanjoorMetre.Rhythm == metre) && (rhyme == "" || p.RhymeLetters == rhyme) ) .OrderBy(p => p.CatId).ThenBy(p => p.Id) .Select ( poem => new GanjoorPoemCompleteViewModel() { Id = poem.Id, Title = poem.Title, FullTitle = poem.FullTitle, FullUrl = poem.FullUrl, UrlSlug = poem.UrlSlug, HtmlText = poem.HtmlText, PlainText = poem.PlainText, GanjoorMetre = poem.GanjoorMetre, RhymeLetters = poem.RhymeLetters, Category = new GanjoorPoetCompleteViewModel() { Poet = new GanjoorPoetViewModel() { Id = poem.Cat.Poet.Id, } }, } ).AsNoTracking(); (PaginationMetadata PagingMeta, GanjoorPoemCompleteViewModel[] Items) paginatedResult = await QueryablePaginator.Paginate(source, paging); Dictionary cachedPoets = new Dictionary(); foreach (var item in paginatedResult.Items) { if(cachedPoets.TryGetValue(item.Category.Poet.Id, out GanjoorPoetCompleteViewModel poet)) { item.Category = poet; } else { poet = (await GetPoetById(item.Category.Poet.Id)).Result; cachedPoets.Add(item.Category.Poet.Id, poet); item.Category = poet; } } return new RServiceResult<(PaginationMetadata PagingMeta, GanjoorPoemCompleteViewModel[] Items)>(paginatedResult); } catch(Exception exp) { return new RServiceResult<(PaginationMetadata PagingMeta, GanjoorPoemCompleteViewModel[] Items)>((null, null), exp.ToString()); } } private async Task _populateCategoryChildren(int catId, List catListId) { var catRes = await GetCatById(catId, false); foreach(var c in catRes.Result.Cat.Children) { catListId.Add(c.Id); await _populateCategoryChildren(c.Id, catListId); } } /// /// Search /// You need to run this scripts manually on the database before using this method: /// /// CREATE FULLTEXT CATALOG [GanjoorPoemPlainTextCatalog] WITH ACCENT_SENSITIVITY = OFF /// ALTER TABLE [dbo].[GanjoorPoems] ADD CONSTRAINT [PK_GanjoorPoems] PRIMARY KEY CLUSTERED ///( /// [Id] ASC ///) WITH(PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON[PRIMARY] /// /// /// /// /// /// public async Task> Search(PagingParameterModel paging, string term, int? poetId, int? catId) { try { term = term.Trim().ApplyCorrectYeKe(); if (string.IsNullOrEmpty(term)) { return new RServiceResult<(PaginationMetadata PagingMeta, GanjoorPoemCompleteViewModel[] Items)>((null, null), "خطای جستجوی عبارت خالی"); } term = term.Replace("‌", " ");//replace zwnj with space string searchConditions; if(term.IndexOf('"') == 0 && term.LastIndexOf('"') == (term.Length - 1)) { searchConditions = term; } else { string[] words =term.Split(' ', StringSplitOptions.RemoveEmptyEntries); searchConditions = ""; string emptyOrAnd = ""; foreach (string word in words) { searchConditions += $" {emptyOrAnd} \"{word}\" "; emptyOrAnd = " AND "; } } if(poetId != null && catId == null) { var poetRes = await GetPoetById((int)poetId); if (!string.IsNullOrEmpty(poetRes.ExceptionString)) return new RServiceResult<(PaginationMetadata PagingMeta, GanjoorPoemCompleteViewModel[] Items)>((null, null), poetRes.ExceptionString); catId = poetRes.Result.Cat.Id; } List catIdList = new List(); if(catId != null) { catIdList.Add((int)catId); await _populateCategoryChildren((int)catId, catIdList); } var source = _context.GanjoorPoems .Where(p => (catId == null || catIdList.Contains(p.CatId)) && EF.Functions.Contains(p.PlainText, searchConditions) ) .Include(p => p.Cat) .OrderBy(p => p.CatId).ThenBy(p => p.Id) .Select ( poem => new GanjoorPoemCompleteViewModel() { Id = poem.Id, Title = poem.Title, FullTitle = poem.FullTitle, FullUrl = poem.FullUrl, UrlSlug = poem.UrlSlug, HtmlText = poem.HtmlText, PlainText = poem.PlainText, GanjoorMetre = poem.GanjoorMetre, RhymeLetters = poem.RhymeLetters, Category = new GanjoorPoetCompleteViewModel() { Poet = new GanjoorPoetViewModel() { Id = poem.Cat.Poet.Id, } }, } ).AsNoTracking(); (PaginationMetadata PagingMeta, GanjoorPoemCompleteViewModel[] Items) paginatedResult = await QueryablePaginator.Paginate(source, paging); Dictionary cachedPoets = new Dictionary(); foreach (var item in paginatedResult.Items) { if (cachedPoets.TryGetValue(item.Category.Poet.Id, out GanjoorPoetCompleteViewModel poet)) { item.Category = poet; } else { poet = (await GetPoetById(item.Category.Poet.Id)).Result; cachedPoets.Add(item.Category.Poet.Id, poet); item.Category = poet; } } return new RServiceResult<(PaginationMetadata PagingMeta, GanjoorPoemCompleteViewModel[] Items)>(paginatedResult); } catch (Exception exp) { return new RServiceResult<(PaginationMetadata PagingMeta, GanjoorPoemCompleteViewModel[] Items)>((null, null), exp.ToString()); } } /// /// modify page /// /// /// /// /// public async Task> ModifyPage(int id, Guid editingUserId, GanjoorModifyPageViewModel pageData) { try { var dbPage = await _context.GanjoorPages.Where(p => p.Id == id).SingleOrDefaultAsync(); if (dbPage == null) return new RServiceResult(null);//not found GanjoorPageSnapshot snapshot = new GanjoorPageSnapshot() { GanjoorPageId = id, MadeObsoleteByUserId = editingUserId, RecordDate = DateTime.Now, Note = pageData.Note, Title = dbPage.Title, UrlSlug = dbPage.UrlSlug, HtmlText = dbPage.HtmlText, }; GanjoorPoem dbPoem = null; if (dbPage.GanjoorPageType == GanjoorPageType.PoemPage) { dbPoem = await _context.GanjoorPoems.Include(p => p.GanjoorMetre).Where(p => p.Id == id).SingleOrDefaultAsync(); snapshot.SourceName = dbPoem.SourceName; snapshot.SourceUrlSlug = dbPoem.SourceUrlSlug; snapshot.Rhythm = dbPoem.GanjoorMetre == null ? null : dbPoem.GanjoorMetre.Rhythm; snapshot.RhymeLetters = dbPoem.RhymeLetters; snapshot.OldTag = dbPoem.OldTag; snapshot.OldTagPageUrl = dbPoem.OldTagPageUrl; } _context.GanjoorPageSnapshots.Add(snapshot); await _context.SaveChangesAsync(); dbPage.Title = pageData.Title; dbPage.UrlSlug = pageData.UrlSlug; dbPage.HtmlText = pageData.HtmlText; _context.GanjoorPages.Update(dbPage); if(dbPoem != null) { dbPoem.SourceName = pageData.SourceName; dbPoem.SourceUrlSlug = pageData.SourceUrlSlug; if(string.IsNullOrEmpty(pageData.Rhythm)) { dbPoem.GanjoorMetreId = null; } else { var metre = await _context.GanjoorMetres.Where(m => m.Rhythm == pageData.Rhythm).SingleOrDefaultAsync(); if (metre == null) { metre = new GanjoorMetre() { Rhythm = pageData.Rhythm, VerseCount = 0 }; _context.GanjoorMetres.Add(metre); await _context.SaveChangesAsync(); } dbPoem.GanjoorMetreId = metre.Id; } dbPoem.RhymeLetters = pageData.RhymeLetters; dbPoem.OldTag = pageData.OldTag; dbPoem.OldTagPageUrl = pageData.OldTagPageUrl; dbPoem.HtmlText = pageData.HtmlText; List verses = _extractVersesFromPoemHtmlText(id, pageData.HtmlText); string plainText = ""; foreach (GanjoorVerse verse in verses) { plainText += $"{verse.Text.Replace("‌", " ")} ";//replace zwnj with space } dbPoem.PlainText = plainText.Trim(); _context.GanjoorPoems.Update(dbPoem); var oldVerses = await _context.GanjoorVerses.Where(v => v.PoemId == id).ToListAsync(); _context.GanjoorVerses.RemoveRange(oldVerses); _context.GanjoorVerses.AddRange(verses); } await _context.SaveChangesAsync(); CacheCleanForPageByUrl(dbPage.FullUrl); return await GetPageByUrl(dbPage.FullUrl); } catch(Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// returns metre list (ordered by Rhythm) /// /// public async Task> GetGanjoorMetres() { try { return new RServiceResult(await _context.GanjoorMetres.OrderBy(m => m.Rhythm).AsNoTracking().ToArrayAsync()); } catch(Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// examine site pages for broken links /// /// public RServiceResult HealthCheckContents() { try { _backgroundTaskQueue.QueueBackgroundWorkItem ( async token => { using (RMuseumDbContext context = new RMuseumDbContext(new DbContextOptions())) //this is long running job, so _context might be already been freed/collected by GC { LongRunningJobProgressServiceEF jobProgressServiceEF = new LongRunningJobProgressServiceEF(context); var job = (await jobProgressServiceEF.NewJob("HealthCheckContents", "Query data")).Result; try { var pages = await context.GanjoorPages.ToArrayAsync(); await jobProgressServiceEF.UpdateJob(job.Id, 0, $"Examining Pages"); var previousErrors = await context.GanjoorHealthCheckErrors.ToArrayAsync(); context.RemoveRange(previousErrors); await context.SaveChangesAsync(); int percent = 0; for (int i = 0; i < pages.Length; i++) { if (i * 100 / pages.Length > percent) { percent++; await jobProgressServiceEF.UpdateJob(job.Id, percent); } var hrefs = pages[i].HtmlText.Split(new[] { "href=\"" }, StringSplitOptions.RemoveEmptyEntries).Where(o => o.StartsWith("http")).Select(o => o.Substring(0, o.IndexOf("\""))); foreach (string url in hrefs) { if (url == "https://ganjoor.net" || url == "https://ganjoor.net/" || url.IndexOf("https://ganjoor.net/vazn/?") == 0 || url.IndexOf("https://ganjoor.net/simi/?v") == 0) continue; if (url.IndexOf("http://ganjoor.net") == 0) { context.GanjoorHealthCheckErrors.Add ( new GanjoorHealthCheckError() { ReferrerPageUrl = pages[i].FullUrl, TargetUrl = url, BrokenLink = false, MulipleTargets = false } ); await context.SaveChangesAsync(); } else if (url.IndexOf("https://ganjoor.net") == 0) { var testUrl = url.Substring("https://ganjoor.net".Length); if (testUrl[testUrl.Length - 1] == '/') testUrl = testUrl.Substring(0, testUrl.Length - 1); var pageCount = await context.GanjoorPages.Where(p => p.FullUrl == testUrl).CountAsync(); if (pageCount != 1) { context.GanjoorHealthCheckErrors.Add ( new GanjoorHealthCheckError() { ReferrerPageUrl = pages[i].FullUrl, TargetUrl = url, BrokenLink = pageCount == 0, MulipleTargets = pageCount != 0 } ); await context.SaveChangesAsync(); } } } } await jobProgressServiceEF.UpdateJob(job.Id, 100, "", true); } catch(Exception exp) { await jobProgressServiceEF.UpdateJob(job.Id, 100, "", false, exp.ToString()); } } } ); return new RServiceResult(true); } catch(Exception exp) { return new RServiceResult(false, exp.ToString()); } } /// /// Database Context /// protected readonly RMuseumDbContext _context; /// /// Configuration /// protected IConfiguration Configuration { get; } /// /// Background Task Queue Instance /// protected readonly IBackgroundTaskQueue _backgroundTaskQueue; /// /// IAppUserService instance /// protected IAppUserService _appUserService; /// /// Messaging service /// protected readonly IRNotificationService _notificationService; /// /// Image File Service /// protected readonly IImageFileService _imageFileService; /// /// IMemoryCache /// protected readonly IMemoryCache _memoryCache; /// /// constructor /// /// /// /// /// /// /// /// public GanjoorService(RMuseumDbContext context, IConfiguration configuration, IBackgroundTaskQueue backgroundTaskQueue, IAppUserService appUserService, IRNotificationService notificationService, IImageFileService imageFileService, IMemoryCache memoryCache) { _context = context; _backgroundTaskQueue = backgroundTaskQueue; _appUserService = appUserService; _notificationService = notificationService; _imageFileService = imageFileService; _memoryCache = memoryCache; Configuration = configuration; } } }