using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Configuration; using RMuseum.DbContext; using RMuseum.Models.Diwan; using RMuseum.Models.Diwan.ViewModels; using RMuseum.Models.DiwanAudio; using RMuseum.Models.DiwanAudio.ViewModels; using RMuseum.Models.Note; using RSecurityBackend.Models.Auth.Db; using RSecurityBackend.Models.Auth.ViewModels; using RSecurityBackend.Models.Generic; using RSecurityBackend.Services; using RSecurityBackend.Services.Implementation; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace RMuseum.Services.Implementation { /// /// Diwan User Service /// public class DiwanAppUserService : AppUserService { public DiwanAppUserService( RMuseumDbContext context, UserManager userManager, SignInManager signInManager, RoleManager roleManager, ISecretGenerator secretGenerator, IImageFileService imageFileService, IUserRoleService userRoleService, IConfiguration configuration, IMemoryCache memoryCache, IEmailSender emailSender ) : base(context, userManager, signInManager, roleManager, secretGenerator, imageFileService, userRoleService, configuration, emailSender) { _memoryCache = memoryCache; } /// /// IMemoryCache /// protected readonly IMemoryCache _memoryCache; /// /// Diwan Service /// protected IDiwanService _diwanService { get; set; } /// /// Sign Up Email Subject /// /// /// subject /// /// /// public override string GetEmailSubject(RVerifyQueueType op, string secretCode) { string opString = op == RVerifyQueueType.SignUp ? "نام‌نویسی" : op == RVerifyQueueType.ForgotPassword ? "بازیابی کلمهٔ عبور" : "حذف حساب کاربری"; return $"{secretCode} کد {opString} شما در گنجور"; } /// /// Sign Up Email Html Content /// /// /// /// /// html content public override string GetEmailHtmlContent(RVerifyQueueType op, string secretCode, string signupCallbackUrl) { string opString = op == RVerifyQueueType.SignUp ? "نام‌نویسی" : op == RVerifyQueueType.ForgotPassword ? "بازیابی کلمهٔ عبور" : "حذف حساب کاربری"; string ifNot = op == RVerifyQueueType.SignUp ? "اگر در گنجور نام‌نویسی نکرده‌اید لطفاً این نامه را نادیده بگیرید." : op == RVerifyQueueType.ForgotPassword ? "اگر در گنجور فراموشی گذرواژه را نزده‌اید یا گذرواژه‌تان را به خاطر آوردید لطفاً این نامه را نادیده بگیرید." : op == RVerifyQueueType.UserSelfDelete ? "اگر در گنجور حذف حساب کاربری را نزده‌اید یا از حذف حساب کاربریتان منصرف شده‌اید لطفاً این نامه را نادیده بگیرید." : ""; string content = "" + "" + "" + "" + (op == RVerifyQueueType.KickOutUser ? $"حذف حساب کاربری شما در گنجور" : $" کد {opString} شما در گنجور: {secretCode}") + "" + "" + "" + "" + "" + "" + "" + "" + "" + "
" + "\"گنجور\"" + "
" + ( op == RVerifyQueueType.KickOutUser ? $"

کاربر گرامی، متأسفیم که به اطلاع برسانیم که به دلیل نقض قوانین استفاده از گنجور و به طور مشخص {secretCode} حساب کاربری شما به همراه حاشیه‌ها، خوانش‌ها و سایر اطلاعات خصوصیتان از گنجور حذف شده است. امیدواریم در آینده در صورت تمایل به استفاده از گنجور در چارچوب‌های قابل پذیرش برای ما با حساب کاربری جدیدی پذیرای شما باشیم. با این ایمیل امکان نام‌نویسی مجدد نخواهید داشت.

" : string.IsNullOrEmpty(signupCallbackUrl) ? $"

لطفاً جهت تکمیل {opString} در گنجور کد {secretCode} را به عنوان رمز دریافتی در صفحهٔ {opString} وارد کنید.

" : $"

لطفاً جهت تکمیل {opString} در گنجور اینجا کلیک کنید یا اگر صفحهٔ {opString} هنوز باز است کد {secretCode} را در آن وارد کنید.

" ) + "
" + $"

{ifNot} به این نامه پاسخ ندهید، کسی پاسخگوی شما نخواهد بود.

" + "
" + "" + "" ; return content; } /// /// Start signup process using email /// /// /// /// /// /// public override async Task> SignUp(string email, string clientIPAddress, string clientAppName, string langauge) { RServiceResult res = await base.SignUp(email, clientIPAddress, clientAppName, langauge); if(res.Result == null) { if(res.ExceptionString == "شما قبلا نام‌نویسی کرده‌اید.") { return new RServiceResult(null, "شما قبلا نام‌نویسی کرده‌اید. توجه بفرمایید که کاربران گنجینهٔ گنجور و پیشخان خوانشگران یکسانند و می‌توانید با همان نام کاربری اینجا وارد شوید."); } } return res; } /// /// finalize signup and assign his or him comments to him or her /// /// /// /// /// /// /// /// public override async Task> FinalizeSignUp(string email, string secret, string password, string firstName, string surName, string phoneNumber) { RServiceResult res = await base.FinalizeSignUp(email, secret, password, firstName, surName, phoneNumber); if(res.Result) { try { RMuseumDbContext context = _context as RMuseumDbContext; var user = (await FindUserByEmail(email)).Result; if(user.EmailConfirmed) { var comments = await context.DiwanComments.Where(c => c.AuthorEmail == email.ToLower()).ToListAsync(); if (comments.Count > 0) { foreach (var comment in comments) { comment.UserId = user.Id; } _context.UpdateRange(comments); await _context.SaveChangesAsync(); } } } catch { return new RServiceResult(true); //ignore this error! because signup was succesfull } } return res; } /// /// modify existing user /*update related entities cache*/ /// /// /// /// public override async Task> ModifyUser(Guid userId, RegisterRAppUser updateUserInfo) { try { RAppUser unmodifiedUserInfo = await _userManager.FindByIdAsync(userId.ToString()); if (unmodifiedUserInfo == null) { return new RServiceResult(false, "کاربر مورد نظر یافت نشد"); } string nickName = updateUserInfo.NickName; if (string.IsNullOrEmpty(nickName)) { return new RServiceResult(false, "نام مستعار نمی‌تواند خالی باشد."); } nickName = nickName.Trim(); RServiceResult res = await base.ModifyUser(userId, updateUserInfo); if (res.Result) { try { if (nickName != updateUserInfo.NickName) { RMuseumDbContext context = _context as RMuseumDbContext; var poemIdSet = await context.DiwanComments.AsNoTracking().Where(c => c.UserId == userId).Select(c => c.PoemId).ToListAsync(); foreach (var poemId in poemIdSet) { //await _diwanService.CacheCleanForPageById(poemId); /*had error in service initializtion, so done it in the dirty way*/ var dbPage = await context.DiwanPages.Where(p => p.Id == poemId).AsNoTracking().SingleOrDefaultAsync(); if (dbPage != null) { //CacheCleanForPageByUrl(dbPage.FullUrl); var url = dbPage.FullUrl; var cachKey = $"DiwanService::GetPageByUrl::{url}"; if (_memoryCache.TryGetValue(cachKey, out DiwanPageCompleteViewModel page)) { _memoryCache.Remove(cachKey); var poemCachKey = $"GetPoemById({page.Id}, {true}, {false}, {true}, {true}, {true}, {true}, {true}, {true}, {true})"; if (_memoryCache.TryGetValue(poemCachKey, out DiwanPoemCompleteViewModel p)) { _memoryCache.Remove(poemCachKey); } } } } } } catch { return new RServiceResult(true); //ignore this error! because main operation was successfull! } } return res; } catch(Exception exp) { return new RServiceResult(false, exp.ToString()); } } /// /// remove user data /// /// /// public override async Task> RemoveUserData(Guid userId) { RMuseumDbContext context = _context as RMuseumDbContext; string systemEmail = $"{Configuration.GetSection("Diwan")["SystemEmail"]}"; var systemUserId = (Guid)(await FindUserByEmail(systemEmail)).Result.Id; if(systemUserId == userId) { return new RServiceResult(false, "تلاش برای حذف کاربر سیستمی"); } string deletedUserEmail = $"{Configuration.GetSection("Diwan")["DeleteUserEmail"]}"; var deletedUserId = (Guid)(await FindUserByEmail(deletedUserEmail)).Result.Id; if (deletedUserId == userId) { return new RServiceResult(false, "تلاش برای حذف کاربر سیستمی کاربر حذف شده"); } var reviewedRecitations = await context.Recitations.Where(r => r.ReviewerId == userId).ToListAsync(); foreach(var reviewedRecitation in reviewedRecitations) reviewedRecitation.ReviewerId = deletedUserId; context.UpdateRange(reviewedRecitations); await context.SaveChangesAsync(); //some tracking data related bugs makes it necessary to call this especially for same table data processing var suggestedCorrections = await context.DiwanPoemCorrections.Where(c => c.UserId == userId).ToListAsync(); foreach (var suggestedCorrection in suggestedCorrections) suggestedCorrection.UserId = deletedUserId; context.UpdateRange(suggestedCorrections); await context.SaveChangesAsync(); var reviewedCorrections = await context.DiwanPoemCorrections.Where(c => c.ReviewerUserId == userId).ToListAsync(); foreach (var reviewedCorrection in reviewedCorrections) reviewedCorrection.ReviewerUserId = deletedUserId; context.UpdateRange(reviewedCorrections); await context.SaveChangesAsync(); var suggestedSectionCorrections = await context.DiwanPoemSectionCorrections.Where(c => c.UserId == userId).ToListAsync(); foreach (var suggestedCorrection in suggestedSectionCorrections) suggestedCorrection.UserId = deletedUserId; context.UpdateRange(suggestedSectionCorrections); await context.SaveChangesAsync(); var reviewedSectionCorrections = await context.DiwanPoemSectionCorrections.Where(c => c.ReviewerUserId == userId).ToListAsync(); foreach (var reviewedCorrection in reviewedSectionCorrections) reviewedCorrection.ReviewerUserId = deletedUserId; context.UpdateRange(reviewedSectionCorrections); await context.SaveChangesAsync(); var suggestedCatCorrections = await context.DiwanCatCorrections.Where(c => c.UserId == userId).ToListAsync(); foreach (var suggestedCatCorrection in suggestedCatCorrections) suggestedCatCorrection.UserId = deletedUserId; context.UpdateRange(suggestedCatCorrections); await context.SaveChangesAsync(); var reviewedCatCorrections = await context.DiwanCatCorrections.Where(c => c.ReviewerUserId == userId).ToListAsync(); foreach (var reviewedCatCorrection in reviewedCatCorrections) reviewedCatCorrection.ReviewerUserId = deletedUserId; context.UpdateRange(reviewedCatCorrections); await context.SaveChangesAsync(); var reportedComments = await context.DiwanReportedComments.Where(r => r.ReportedById == userId).ToListAsync(); foreach (var reportedComment in reportedComments) reportedComment.ReportedById = deletedUserId; context.UpdateRange(reportedComments); await context.SaveChangesAsync(); var diwanLinks = await context.DiwanLinks.Where(l => l.SuggestedById == userId).ToListAsync(); foreach (var diwanLink in diwanLinks) diwanLink.SuggestedById = deletedUserId; context.UpdateRange(diwanLinks); await context.SaveChangesAsync(); var reviewedDiwanLinks = await context.DiwanLinks.Where(l => l.ReviewerId == userId).ToListAsync(); foreach (var reviewedDiwanLink in reviewedDiwanLinks) reviewedDiwanLink.ReviewerId = deletedUserId; context.UpdateRange(reviewedDiwanLinks); await context.SaveChangesAsync(); var pinLinks = await context.PinterestLinks.Where(l => l.SuggestedById == userId).ToListAsync(); foreach (var pinLink in pinLinks) pinLink.SuggestedById = deletedUserId; context.UpdateRange(pinLinks); await context.SaveChangesAsync(); var reviewedPinLinks = await context.PinterestLinks.Where(l => l.ReviewerId == userId).ToListAsync(); foreach (var reviewedPinLink in reviewedPinLinks) reviewedPinLink.ReviewerId = deletedUserId; context.UpdateRange(reviewedPinLinks); await context.SaveChangesAsync(); var poemMusicTracks = await context.DiwanPoemMusicTracks.Where(m => m.SuggestedById == userId).ToListAsync(); foreach (var poemMusicTrack in poemMusicTracks) poemMusicTrack.SuggestedById = deletedUserId; context.UpdateRange(poemMusicTracks); await context.SaveChangesAsync(); var snapshots = await context.DiwanPageSnapshots.Where(s => s.MadeObsoleteByUserId == userId).ToListAsync(); foreach (var snapshot in snapshots) snapshot.MadeObsoleteByUserId = deletedUserId; context.UpdateRange(snapshots); await context.SaveChangesAsync(); var suggestedPoetNotes = await context.DiwanPoetSuggestedSpecLines.Where(s => s.SuggestedById == userId).ToListAsync(); foreach (var suggestedPoetNote in suggestedPoetNotes) suggestedPoetNote.SuggestedById = deletedUserId; context.UpdateRange(suggestedPoetNotes); await context.SaveChangesAsync(); var suggestedPoetPhotos = await context.DiwanPoetSuggestedPictures.Where(s => s.SuggestedById == userId).ToListAsync(); foreach (var suggestedPoetPhoto in suggestedPoetPhotos) suggestedPoetPhoto.SuggestedById = deletedUserId; context.UpdateRange(suggestedPoetPhotos); await context.SaveChangesAsync(); var suggestQuotes = await context.DiwanQuotedPoems.Where(s => s.SuggestedById == userId).ToListAsync(); foreach(var suggestedQuote in suggestedPoetPhotos) suggestedQuote.SuggestedById = deletedUserId; context.UpdateRange(suggestedPoetPhotos); await context.SaveChangesAsync(); var visits = await context.DiwanUserPoemVisits.Where(v => v.UserId == userId).ToListAsync(); context.RemoveRange(visits); await context.SaveChangesAsync(); var bookmarks = await context.UserBookmarks.Where(b => b.RAppUserId == userId).ToListAsync(); context.RemoveRange(bookmarks); await context.SaveChangesAsync(); var uploadSessions = await context.UploadSessions.Where(s => s.UseId == userId).ToListAsync(); context.RemoveRange(uploadSessions); await context.SaveChangesAsync(); var recitations = await context.Recitations.Where(r => r.OwnerId == userId).ToListAsync(); context.RemoveRange(recitations); await context.SaveChangesAsync(); var diwanBookmarks = await context.DiwanUserBookmarks.Where(b => b.UserId == userId).ToListAsync(); context.RemoveRange(diwanBookmarks); await context.SaveChangesAsync(); var reportedRecitaions = await context.RecitationErrorReports.Where(r => r.ReporterId == userId).ToListAsync(); context.RemoveRange(reportedRecitaions); await context.SaveChangesAsync(); var comments = await context.DiwanComments.Where(c => c.UserId == userId).ToListAsync(); foreach (var comment in comments) { //await _diwanService.DeleteMyComment(userId, comment.Id);/*had error in service initializtion, so done it in the dirty way*/ await _DeleteComment(context, comment.Id); } var userNotes = await context.UserNotes.Where(c => c.RAppUserId == userId).ToListAsync(); foreach (var note in userNotes) { await _DeleteUserNotesAsync(context, note.Id); } var recitationsVotes = await context.RecitationUserUpVotes.Where(c => c.UserId == userId).ToListAsync(); foreach (var vote in recitationsVotes) { int poemId = await context.Recitations.AsNoTracking().Where(r => r.Id == vote.RecitationId).Select(r => r.DiwanPostId).SingleAsync(); context.Remove(vote); await context.SaveChangesAsync(); await _ReOrderPoemRecitationsAsync(context, poemId); } return await base.RemoveUserData(userId);//notifications are deleted here, some of these operations might produce new notifications } private async Task> _DeleteComment(RMuseumDbContext context, int commentId) { DiwanComment comment = await context.DiwanComments.Where(c => c.Id == commentId).SingleOrDefaultAsync(); if (comment == null) { return new RServiceResult(false); //not found } foreach(var reply in await _FindReplies(context, comment)) { await _DeleteComment(context, reply.Id); } context.DiwanComments.Remove(comment); await _context.SaveChangesAsync(); return new RServiceResult(true); } private async Task> _FindReplies(RMuseumDbContext context, DiwanComment comment) { return await context.DiwanComments.Where(c => c.InReplyToId == comment.Id).AsNoTracking().ToListAsync(); } private async Task> _DeleteUserNotesAsync(RMuseumDbContext context, Guid commentId) { RUserNote comment = await context.UserNotes.Where(c => c.Id == commentId).SingleOrDefaultAsync(); if (comment == null) { return new RServiceResult(false); //not found } foreach (var reply in await _FindNoteRepliesAsync(context, comment)) { await _DeleteUserNotesAsync(context, reply.Id); } context.Remove(comment); await _context.SaveChangesAsync(); return new RServiceResult(true); } private async Task> _FindNoteRepliesAsync(RMuseumDbContext context, RUserNote comment) { return await context.UserNotes.Where(c => c.ReferenceNoteId == comment.Id).AsNoTracking().ToListAsync(); } private async Task _ReOrderPoemRecitationsAsync(RMuseumDbContext context, int poemId, bool update = true) { var recitations = await context.Recitations .Where(r => r.ReviewStatus == AudioReviewStatus.Approved && r.DiwanPostId == poemId) .OrderBy(r => r.Id) //this causes the oldest recirations to become the first one .ToListAsync(); List scores = new List(); for (var i = 0; i < recitations.Count; i++) { var recitation = recitations[i]; RecitationOrderingViewModel score = new RecitationOrderingViewModel() { RecitationId = recitation.Id, EarlynessAdvantage = recitations.Count - 1 - i, InitialScore = recitations[i].InitialScore, UpVotes = await context.RecitationUserUpVotes.AsNoTracking().Where(r => r.RecitationId == recitation.Id && r.UserId != recitation.OwnerId) .CountAsync(), Mistakes = await context.RecitationApprovedMistakes.AsNoTracking().Where(m => m.RecitationId == recitation.Id).SumAsync(m => m.NumberOfLinesAffected) }; score.TotalScores = score.EarlynessAdvantage + score.InitialScore + score.UpVotes - (5 * score.Mistakes); //audio order is used as a temporary variable in the following line and soon is getting replaced by computed value recitation.AudioOrder = score.TotalScores; scores.Add(score); } recitations.Sort((a, b) => b.AudioOrder.CompareTo(a.AudioOrder)); for (var i = 0; i < recitations.Count; i++) { recitations[i].AudioOrder = i + 1; scores.Where(s => s.RecitationId == recitations[i].Id).Single().ComputedOrder = i + 1; if (update) { context.Update(recitations[i]); } } if (update) await context.SaveChangesAsync(); } } }