using FluentFTP; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using RMuseum.DbContext; using RMuseum.Models.Artifact; using RMuseum.Models.Auth.Memory; using RMuseum.Models.Ganjoor; using RMuseum.Models.Ganjoor.ViewModels; using RSecurityBackend.Models.Generic; using RSecurityBackend.Models.Notification; using RSecurityBackend.Services; using System; using System.IO; using System.Linq; using System.Threading.Tasks; namespace RMuseum.Services.Implementation { /// /// poet photo suggestion service /// public class PoetPhotoSuggestionService : IPoetPhotoSuggestionService { /// /// return list of suggested photos for a poet /// /// /// public async Task> GetPoetSuggestedPhotosAsync(int poetId) { return new RServiceResult ( await _context.GanjoorPoetSuggestedPictures.AsNoTracking().Include(r => r.Picture) .Where ( r => r.Published == true && r.PoetId == poetId ) .OrderBy(r => r.PicOrder) .Select ( r => new GanjoorPoetSuggestedPictureViewModel() { Id = r.Id, PoetId = r.PoetId, Title = r.Picture.Title, Description = r.Picture.Description, PicOrder = r.PicOrder, Published = r.Published, ChosenOne = r.ChosenOne, SuggestedById = r.SuggestedById, ImageUrl = $"api/rimages/{r.PictureId}.jpg", ExternalNormalSizeImageUrl = r.Picture.ExternalNormalSizeImageUrl.Replace("/orig/", "/norm/") } ) .ToArrayAsync() ); } /// /// returns a single suggested photo /// /// /// public async Task> GetPoetSuggestedPhotoByIdAsync(int id) { return new RServiceResult ( await _context.GanjoorPoetSuggestedPictures.AsNoTracking().Include(r => r.Picture) .Where ( r => r.Id == id ) .Select ( r => new GanjoorPoetSuggestedPictureViewModel() { Id = r.Id, PoetId = r.PoetId, Title = r.Picture.Title, Description = r.Picture.Description, PicOrder = r.PicOrder, Published = r.Published, ChosenOne = r.ChosenOne, SuggestedById = r.SuggestedById, ImageUrl = $"api/rimages/{r.PictureId}.jpg", ExternalNormalSizeImageUrl = r.Picture.ExternalNormalSizeImageUrl.Replace("/orig/", "/norm/") } ) .FirstOrDefaultAsync() ); } /// /// suggest a new photo for a poet /// /// /// /// /// /// /// /// /// public async Task> SuggestPhotoForPoet(int poetId, Guid userId, Stream imageStream, string fileName, string title, string description, string srcUrl) { RServiceResult imageRes = await _pictureFileService.Add(title, description, 0, null, srcUrl, imageStream, fileName, "PoetsPhotoSuggestions"); if (!string.IsNullOrEmpty(imageRes.ExceptionString)) { return new RServiceResult(null, imageRes.ExceptionString); } try { var image = imageRes.Result; int picOrder = 1 + (await _context.GanjoorPoetSuggestedPictures.Where(p => p.PoetId == poetId).CountAsync()); GanjoorPoetSuggestedPicture picture = new GanjoorPoetSuggestedPicture() { PoetId = poetId, PicOrder = picOrder, Picture = image, SuggestedById = userId, Published = false, ChosenOne = false }; _context.GanjoorPoetSuggestedPictures.Add(picture); await _context.SaveChangesAsync(); var moderators = await _appUserService.GetUsersHavingPermission(RMuseumSecurableItem.GanjoorEntityShortName, RMuseumSecurableItem.ModeratePoetPhotos); if (string.IsNullOrEmpty(moderators.ExceptionString)) //if not, do nothing! { var poet = await _context.GanjoorPoets.AsNoTracking().Where(p => p.Id == poetId).SingleAsync(); foreach (var moderator in moderators.Result) { await _notificationService.PushNotification ( (Guid)moderator.Id, "ثبت تصویر پیشنهادی جدید برای سخنور", $"درخواستی برای ثبت تصویر پیشنهادی جدیدی برای «{poet.Nickname}» ثبت شده است. در صورت تمایل به بررسی، بخش مربوط به سخنور را اینجا ببینید.{ Environment.NewLine}" + $"توجه فرمایید که اگر کاربر دیگری که دارای مجوز بررسی تصاویر است پیش از شما به آن رسیدگی کرده باشد آن را در صف نخواهید دید.", NotificationType.ActionRequired ); } } return new RServiceResult ( new GanjoorPoetSuggestedPictureViewModel() { Id = picture.Id, PoetId = poetId, Title = image.Title, Description = image.Description, PicOrder = picture.PicOrder, Published = picture.Published, ChosenOne = picture.ChosenOne, SuggestedById = picture.SuggestedById, ImageUrl = $"api/rimages/{picture.PictureId}.jpg", ExternalNormalSizeImageUrl = $"https://api.ganjoor.net/api/rimages/{picture.PictureId}.jpg" } ); } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// next unpublished suggested photo for poets /// /// /// public async Task> GetNextUnmoderatedPoetSuggestedPhotoAsync(int skip) { try { return new RServiceResult ( await _context.GanjoorPoetSuggestedPictures.AsNoTracking().Include(r => r.Picture) .Where ( r => r.Published == false ) .Skip(skip) .Select ( r => new GanjoorPoetSuggestedPictureViewModel() { Id = r.Id, PoetId = r.PoetId, Title = r.Picture.Title, Description = r.Picture.Description, PicOrder = r.PicOrder, Published = r.Published, ChosenOne = r.ChosenOne, SuggestedById = r.SuggestedById, ImageUrl = $"api/rimages/{r.PictureId}.jpg", ExternalNormalSizeImageUrl = $"https://api.ganjoor.net/api/rimages/{r.PictureId}.jpg" } ).FirstOrDefaultAsync() ); } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// unpublished suggested photos count for poets /// /// public async Task> GetNextUnmoderatedPoetSuggestedPhotosCountAsync() { try { return new RServiceResult ( await _context.GanjoorPoetSuggestedPictures .Where ( r => r.Published == false ) .CountAsync() ); } catch (Exception exp) { return new RServiceResult(0, exp.ToString()); } } /// /// modify a suggested photo for poets /// /// /// public async Task> ModifyPoetSuggestedPhotoAsync(GanjoorPoetSuggestedPictureViewModel model) { try { var dbModel = await _context.GanjoorPoetSuggestedPictures.Include(p => p.Picture).Where(s => s.Id == model.Id).SingleAsync(); if (model.Published && bool.Parse(Configuration.GetSection("ExternalFTPServer")["UploadEnabled"])) { var ftpClient = new AsyncFtpClient ( Configuration.GetSection("ExternalFTPServer")["Host"], Configuration.GetSection("ExternalFTPServer")["Username"], Configuration.GetSection("ExternalFTPServer")["Password"] ); ftpClient.ValidateCertificate += FtpClient_ValidateCertificate; await ftpClient.AutoConnect(); ftpClient.Config.RetryAttempts = 3; foreach (var imageSizeString in new string[] { "orig", "norm", "thumb" }) { var localFilePath = _pictureFileService.GetImagePath(dbModel.Picture, imageSizeString).Result; if (imageSizeString == "norm") { dbModel.Picture.ExternalNormalSizeImageUrl = $"{Configuration.GetSection("ExternalFTPServer")["RootUrl"]}/PoetsPhotoSuggestions/{imageSizeString}/{Path.GetFileName(localFilePath)}"; } var remoteFilePath = $"{Configuration.GetSection("ExternalFTPServer")["RootPath"]}/images/PoetsPhotoSuggestions/{imageSizeString}/{Path.GetFileName(localFilePath)}"; await ftpClient.UploadFile(localFilePath, remoteFilePath); } await ftpClient.Disconnect(); } if (model.Published && bool.Parse(Configuration.GetSection("BackupFTPServer")["UploadEnabled"])) { var ftpClient = new AsyncFtpClient ( Configuration.GetSection("BackupFTPServer")["Host"], Configuration.GetSection("BackupFTPServer")["Username"], Configuration.GetSection("BackupFTPServer")["Password"] ); ftpClient.ValidateCertificate += FtpClient_ValidateCertificate; await ftpClient.AutoConnect(); ftpClient.Config.RetryAttempts = 3; foreach (var imageSizeString in new string[] { "orig", "norm", "thumb" }) { var localFilePath = _pictureFileService.GetImagePath(dbModel.Picture, imageSizeString).Result; if (imageSizeString == "norm") { dbModel.Picture.ExternalNormalSizeImageUrl = $"{Configuration.GetSection("BackupFTPServer")["RootUrl"]}/PoetsPhotoSuggestions/{imageSizeString}/{Path.GetFileName(localFilePath)}"; } var remoteFilePath = $"{Configuration.GetSection("BackupFTPServer")["RootPath"]}/images/PoetsPhotoSuggestions/{imageSizeString}/{Path.GetFileName(localFilePath)}"; await ftpClient.UploadFile(localFilePath, remoteFilePath); } await ftpClient.Disconnect(); } bool publishIsChanged = model.Published != dbModel.Published; if(publishIsChanged) { dbModel.PublicationDate = DateTime.Now; } bool newlyChosenOne = model.ChosenOne && !dbModel.ChosenOne; if (newlyChosenOne) dbModel.PicOrder = 1; else dbModel.PicOrder = model.PicOrder; dbModel.Picture.Title = model.Title; dbModel.Picture.Description = model.Description; dbModel.ChosenOne = model.ChosenOne; dbModel.Published = model.Published; _context.Update(dbModel); if (newlyChosenOne) { var oldChosenOnes = await _context.GanjoorPoetSuggestedPictures.Where(p => p.Id != model.Id && p.PoetId == model.PoetId && p.ChosenOne == true).ToListAsync(); foreach (var photo in oldChosenOnes) { photo.ChosenOne = false; } _context.UpdateRange(oldChosenOnes); } await _context.SaveChangesAsync(); if (newlyChosenOne) { var others = await _context.GanjoorPoetSuggestedPictures.Where(p => p.Id != model.Id && p.PoetId == model.PoetId).ToListAsync(); foreach (var photo in others) { photo.PicOrder = photo.PicOrder + 1; } if (others.Count > 0) { _context.UpdateRange(others); await _context.SaveChangesAsync(); } } if (publishIsChanged && model.Published && dbModel.SuggestedById != null) { var userRes = await _appUserService.GetUserInformation((Guid)dbModel.SuggestedById); var poet = await _context.GanjoorPoets.AsNoTracking().Where(p => p.Id == dbModel.PoetId).SingleAsync(); await _notificationService.PushNotification((Guid)dbModel.SuggestedById, $"انتشار تصویر پیشنهادی شما برای {poet.Nickname}", $"با سپاس! پیشنهاد شما برای تصویر {poet.Nickname} در فهرست تصاویر قابل انتخاب برای سخنور قابل مشاهده است." ); } if (newlyChosenOne && dbModel.SuggestedById != null) { var userRes = await _appUserService.GetUserInformation((Guid)dbModel.SuggestedById); var poet = await _context.GanjoorPoets.AsNoTracking().Where(p => p.Id == dbModel.PoetId).SingleAsync(); await _notificationService.PushNotification((Guid)dbModel.SuggestedById, $"انتخاب تصویر پیشنهادی شما برای {poet.Nickname} به عنوان تصویر اصلی در گنجور", $"با سپاس! پیشنهاد شما برای تصویر {poet.Nickname} هم‌اکنون تصویر اصلی او در گنجور است." ); } return new RServiceResult(true); } catch (Exception exp) { return new RServiceResult(false, exp.ToString()); } } private void FtpClient_ValidateCertificate(FluentFTP.Client.BaseClient.BaseFtpClient control, FtpSslValidationEventArgs e) { e.Accept = true; } /// /// delete a suggested photo for poets /// /// /// /// /// public async Task> RejectPoetSuggestedPhotosAsync(int id, Guid deleteUserId, string rejectionCause) { try { var dbModel = await _context.GanjoorPoetSuggestedPictures.Where(s => s.Id == id).SingleAsync(); if (!dbModel.Published && dbModel.SuggestedById != null) { var userRes = await _appUserService.GetUserInformation((Guid)dbModel.SuggestedById); var poet = await _context.GanjoorPoets.AsNoTracking().Where(p => p.Id == dbModel.PoetId).SingleAsync(); string causePhrase = string.IsNullOrEmpty(rejectionCause) ? "" : $" به دلیل {rejectionCause} "; await _notificationService.PushNotification((Guid)dbModel.SuggestedById, $"عدم پذیرش تصویر ارسالی شما برای {poet.Nickname}", $"متأسفانه تصویر پیشنهادی شما برای مشخصات {poet.Nickname}{causePhrase} مورد پذیرش قرار نگرفت", NotificationType.Warning ); } _context.Remove(dbModel); await _context.SaveChangesAsync(); return new RServiceResult(true); } catch (Exception exp) { return new RServiceResult(false, exp.ToString()); } } /// /// delete published suggested photo /// /// /// public async Task> DeletePoetSuggestedPhotoAsync(int id) { try { var dbModel = await _context.GanjoorPoetSuggestedPictures.Where(s => s.Id == id).SingleAsync(); if(!dbModel.Published) { return new RServiceResult(false, "برای رد تصاویر تأیید نشده از تابع reject استفاده کنید."); } _context.Remove(dbModel); await _context.SaveChangesAsync(); return new RServiceResult(true); } catch (Exception exp) { return new RServiceResult(false, exp.ToString()); } } /// /// Database Context /// protected readonly RMuseumDbContext _context; /// /// picture file service /// protected readonly IPictureFileService _pictureFileService; /// /// Messaging service /// protected readonly IRNotificationService _notificationService; /// /// IAppUserService instance /// protected readonly IAppUserService _appUserService; /// /// Configuration /// protected IConfiguration Configuration { get; } /// /// constructor /// /// /// /// /// /// public PoetPhotoSuggestionService(RMuseumDbContext context, IPictureFileService pictureFileService, IRNotificationService notificationService, IAppUserService appUserService, IConfiguration configuration) { _context = context; _pictureFileService = pictureFileService; _notificationService = notificationService; _appUserService = appUserService; Configuration = configuration; } } }