using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Newtonsoft.Json.Linq; using RMuseum.DbContext; using RMuseum.Models.Artifact; using RMuseum.Models.Artifact.ViewModels; using RMuseum.Models.Bookmark; using RMuseum.Models.Bookmark.ViewModels; using RMuseum.Models.DiwanIntegration; using RMuseum.Models.DiwanIntegration.ViewModels; using RMuseum.Models.ImportJob; using RMuseum.Models.Note; using RMuseum.Models.Note.ViewModels; 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.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using DNTPersianUtils.Core; using FluentFTP; using RSecurityBackend.Models.Notification; using RMuseum.Models.Diwan; using RMuseum.Models.Auth.Memory; namespace RMuseum.Services.Implementation { /// /// IArtifactService implementation /// public partial class ArtifactService : IArtifactService { /// /// get all artifacts (including CoverImage info but not items or tagibutes info) /// /// /// /// public async Task> GetAll(PagingParameterModel paging, PublishStatus[] statusArray) { var source = _context.Artifacts .Include(a => a.CoverImage) .Where(a => statusArray.Contains(a.Status)) .OrderByDescending(t => t.DateTime) .AsQueryable(); (PaginationMetadata PagingMeta, RArtifactMasterRecord[] Items) paginatedResult = await QueryablePaginator.Paginate(source, paging); return new RServiceResult<(PaginationMetadata PagingMeta, RArtifactMasterRecord[] Items)>(paginatedResult); } /// /// get tagged publish artifacts (including CoverImage info but not items or tagibutes info) /// /// /// /// /// public async Task> GetByTagValue(string tagUrl, string valueUrl, PublishStatus[] statusArray) { RTag tag = await _context.Tags .Where(a => a.FriendlyUrl == tagUrl) .SingleOrDefaultAsync(); if (tag == null) return new RServiceResult(new RArtifactMasterRecord[] { }); RArtifactMasterRecord[] taggedItems = await _context.Artifacts.Include(a => a.Tags) .Include(a => a.CoverImage) .Where(a => statusArray.Contains(a.Status) && a.Tags != null && a.Tags.Any(v => v.RTagId == tag.Id && v.FriendlyUrl == valueUrl)) .OrderByDescending(t => t.DateTime) .AsNoTracking() .ToArrayAsync(); foreach (RArtifactMasterRecord taggedItem in taggedItems) taggedItem.Tags = null; return new RServiceResult(taggedItems); } /// /// gets specified artifact info (including CoverImage + images + tagibutes) /// /// /// /// public async Task> GetByFriendlyUrl(string friendlyUrl, PublishStatus[] statusArray) { RArtifactMasterRecord artifact = await _context.Artifacts .Include(a => a.CoverImage) .Include(a => a.Items).ThenInclude(i => i.Images) .Include(a => a.Items).ThenInclude(i => i.Tags).ThenInclude(t => t.RTag) .Include(a => a.Tags).ThenInclude(t => t.RTag) .Where(a => statusArray.Contains(a.Status) && a.FriendlyUrl == friendlyUrl) .AsNoTracking() .SingleOrDefaultAsync(); if (artifact != null) { return new RServiceResult(RArtifactMasterRecord.ToViewModel(artifact)); } return new RServiceResult(null); } /// /// get specific artifact info with limited number of images /// /// /// /// /// public async Task> GetByFriendlyUrlLimitedItemsAsync(string friendlyUrl, PublishStatus[] statusArray, int imageCount) { try { RArtifactMasterRecord artifact = await _context.Artifacts .Include(a => a.CoverImage) .Include(a => a.Tags).ThenInclude(t => t.RTag) .Where(a => statusArray.Contains(a.Status) && a.FriendlyUrl == friendlyUrl) .AsNoTracking() .SingleOrDefaultAsync(); if (artifact != null) { artifact.Items = await _context.Items.AsNoTracking() .Include(i => i.Images) .Include(i => i.Tags).ThenInclude(t => t.RTag) .Where(i => i.RArtifactMasterRecordId == artifact.Id) .OrderBy(i => i.Order) .Take(imageCount) .ToListAsync(); } return new RServiceResult(RArtifactMasterRecord.ToViewModel(artifact)); } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// get artifact images according to start and count params /// /// /// /// /// public async Task> GetArtifactItemsAsync(Guid id, int start, int count) { try { return new RServiceResult ( await _context.Items.AsNoTracking() .Include(i => i.Images) .Include(i => i.Tags).ThenInclude(t => t.RTag) .Where(i => i.RArtifactMasterRecordId == id) .OrderBy(i => i.Order) .Skip(start) .Take(count) .ToArrayAsync() ); } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// gets specified artifact info (including CoverImage + images + tagibutes) /// /// /// /// /// /// public async Task> GetByFriendlyUrlFilterItemsByTag(string friendlyUrl, PublishStatus[] statusArray, string tagFriendlyUrl, string tagValueFriendlyUrl) { RTag rTag = await _context.Tags.Where(t => t.FriendlyUrl == tagFriendlyUrl).SingleOrDefaultAsync(); if (rTag == null) return new RServiceResult(null, "Tag not found!"); RArtifactMasterRecord artifact = await _context.Artifacts .Include(a => a.CoverImage) .Include(a => a.Items).ThenInclude(i => i.Images) .Include(a => a.Items).ThenInclude(i => i.Tags) .Include(a => a.Tags).ThenInclude(t => t.RTag) .Where(a => statusArray.Contains(a.Status) && a.FriendlyUrl == friendlyUrl) .AsNoTracking() .SingleOrDefaultAsync(); if (artifact != null) { List filteredItems = new List(); foreach (RArtifactItemRecord item in artifact.Items) { if (item.Tags.Any(v => v.RTagId == rTag.Id && (string.IsNullOrEmpty(tagValueFriendlyUrl) || v.FriendlyUrl == tagValueFriendlyUrl))) { item.Tags = null; filteredItems.Add(item); } } artifact.Items = filteredItems; return new RServiceResult(RArtifactMasterRecord.ToViewModel(artifact)); } return new RServiceResult(null); } /// /// edit master record /// /// /// /// /// public async Task> EditMasterRecord(RArtifactMasterRecord edited, bool canChangeStatusToAwaiting, bool canPublish) { if (string.IsNullOrEmpty(edited.Name)) { return new RServiceResult(null, "Name could not be empty."); } RArtifactMasterRecord item = await _context.Artifacts .Where(a => a.Id == edited.Id) .SingleOrDefaultAsync(); if (item != null) { if (item.Status != edited.Status) { if (!canChangeStatusToAwaiting) { return new RServiceResult(null, "User should be able to change status to Awaiting to complete this operation."); } if ( ! ( (item.Status == PublishStatus.Draft && edited.Status == PublishStatus.Awaiting) || (item.Status == PublishStatus.Awaiting && edited.Status == PublishStatus.Draft) ) ) { if (!canPublish) { return new RServiceResult(null, "User should have Publish permission to complete this operation."); } } } item.FriendlyUrl = edited.FriendlyUrl; item.Status = edited.Status; item.Name = edited.Name; item.NameInEnglish = edited.Name; item.Description = edited.Description; item.DescriptionInEnglish = edited.DescriptionInEnglish; item.LastModified = DateTime.Now; _context.Update(item); await _context.SaveChangesAsync(); } return new RServiceResult(item); } /// /// Set Artifact Cover Item Index /// /// /// /// public async Task> SetArtifactCoverItemIndex(Guid artifactId, int itemIndex) { RArtifactMasterRecord artifact = await _context .Artifacts.Where(a => a.Id == artifactId) .Include(a => a.Items).ThenInclude(i => i.Images) .SingleOrDefaultAsync(); if (artifact == null) return new RServiceResult(false, "Artifact not found."); if (itemIndex == artifact.CoverItemIndex) return new RServiceResult(true); if (itemIndex < 0 || itemIndex >= artifact.Items.Count()) return new RServiceResult(false, "Item not found."); artifact.CoverItemIndex = itemIndex; artifact.CoverImage = RPictureFile.Duplicate(artifact.Items.ToArray()[itemIndex].Images.First()); artifact.LastModified = DateTime.Now; _context.Artifacts.Update(artifact); await _context.SaveChangesAsync(); return new RServiceResult(true); } /// /// get tag bundle by frindly url /// /// /// public async Task> GetTagBundleByFiendlyUrl(string friendlyUrl) { try { if (string.IsNullOrEmpty(friendlyUrl)) return new RServiceResult(null, "friendlyUrl is null"); RTag tag = await _context.Tags .Where(a => a.FriendlyUrl == friendlyUrl) .AsNoTracking() .SingleOrDefaultAsync(); if (tag == null) { return new RServiceResult(null, "tag is null"); } if (tag != null) { RTagBundleViewModel viewModel = new RTagBundleViewModel() { Id = tag.Id, Name = tag.Name, PluralName = tag.PluralName, FriendlyUrl = friendlyUrl }; List values = new List( await _context.TagValues .Where(value => value.RTagId == tag.Id && !string.IsNullOrEmpty(value.FriendlyUrl)) .GroupBy(value => new { value.Value, value.FriendlyUrl, value.RTagId }) .Select ( g => new RTagBundleValueViewModel() { FriendlyUrl = g.Key.FriendlyUrl, Name = g.Key.Value, Count = g.Count(), } ) .OrderByDescending(g => g.Count) .ToArrayAsync() ); foreach (var val in values) { var firstItem = _context.Artifacts.AsNoTracking().Include(a => a.Tags).Include(a => a.CoverImage).Where(a => a.Status == PublishStatus.Published && a.Tags.Any(t => t.RTagId == tag.Id && t.Value == val.Name)).FirstOrDefault(); if (firstItem != null) { val.ImageId = firstItem.CoverImageId; val.ExternalNormalSizeImageUrl = firstItem.CoverImage == null ? "" : firstItem.CoverImage.ExternalNormalSizeImageUrl; } else val.Count = 0; } viewModel.Values = values.Where(v => v.Count > 0).ToArray(); return new RServiceResult(viewModel); } return new RServiceResult(null); } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// get max lastmodified artifact date for caching purposes /// /// public async Task> GetMaxArtifactLastModified() { return new RServiceResult(await _context.Artifacts.MaxAsync(a => a.LastModified)); } /// /// get all tags /// /// /// public async Task> GetAllTags(PagingParameterModel paging) { var source = _context.Tags .OrderByDescending(t => t.Order) .AsQueryable(); (PaginationMetadata PagingMeta, RTag[] Items) paginatedResult = await QueryablePaginator.Paginate(source, paging); return new RServiceResult<(PaginationMetadata PagingMeta, RTag[] Items)>(paginatedResult); } /// /// get tag bu friendly url /// /// /// public async Task> GetTagByFriendlyUrl(string friendlyUrl) { return new RServiceResult(await _context.Tags.Where(t => t.FriendlyUrl == friendlyUrl).SingleOrDefaultAsync()); } /// /// get tag value by frindly url /// /// /// /// public async Task> GetTagValueByFriendlyUrl(Guid tagId, string friendlyUrl) { RTagValue value = await _context.TagValues.Where(t => t.RTagId == tagId && t.FriendlyUrl == friendlyUrl).FirstOrDefaultAsync(); if (value != null) return new RServiceResult(value); return new RServiceResult(null, "Tag value not found"); } /// /// add tag /// /// public async Task> AddTag(string tagName) { if (string.IsNullOrEmpty(tagName)) { return new RServiceResult(null, "Name could not be empty."); } RTag existingTag = await _context.Tags .Where(a => a.Name == tagName || a.NameInEnglish == tagName) .SingleOrDefaultAsync(); if (existingTag != null) { return new RServiceResult(null, "Duplicated Name or English Name for tag."); } int maxOrder = await _context.Tags.CountAsync() == 0 ? 0 : await _context.Tags.MaxAsync(a => a.Order); RTag type = new RTag() { Name = tagName, NameInEnglish = tagName, PluralName = $"{tagName}s", PluralNameInEnglish = $"{tagName}s", Order = maxOrder + 1, Status = PublishStatus.Published, GlobalValue = true }; await _context.Tags.AddAsync(type); await _context.SaveChangesAsync(); return new RServiceResult(type); } /// /// edit tag /// /// /// public async Task> EditTag(RTag edited) { if (string.IsNullOrEmpty(edited.Name)) { return new RServiceResult(null, "Name could not be empty."); } RTag tag = await _context.Tags .Where(a => a.Id == edited.Id) .SingleOrDefaultAsync(); if (tag != null) { tag.Name = edited.Name; tag.Order = edited.Order; tag.FriendlyUrl = edited.FriendlyUrl; tag.TagType = edited.TagType; tag.Status = edited.Status; tag.NameInEnglish = edited.NameInEnglish; tag.GlobalValue = edited.GlobalValue; tag.PluralName = edited.PluralName; tag.PluralNameInEnglish = edited.PluralNameInEnglish; _context.Update(tag); RArtifactMasterRecord[] taggedItems = await _context.Artifacts.Include(a => a.Tags) .Where(a => a.Tags != null && a.Tags.Any(v => v.RTagId == tag.Id)) .ToArrayAsync(); foreach (RArtifactMasterRecord taggedItem in taggedItems) { taggedItem.LastModified = DateTime.Now; _context.Update(taggedItem); } RArtifactItemRecord[] taggedPages = await _context.Items.Include(a => a.Tags) .Where(a => a.Tags != null && a.Tags.Any(v => v.RTagId == tag.Id)) .ToArrayAsync(); foreach (RArtifactItemRecord taggedItem in taggedPages) { taggedItem.LastModified = DateTime.Now; _context.Update(taggedItem); } await UpdateTaggedItems(tag); await _context.SaveChangesAsync(); } return new RServiceResult(tag); } /// /// changes LastModified field value for tagged artifacts and items /// /// /// private async Task UpdateTaggedItems(RTag tag) { RArtifactMasterRecord[] taggedItems = await _context.Artifacts.Include(a => a.Tags) .Where(a => a.Tags != null && a.Tags.Any(v => v.RTagId == tag.Id)) .ToArrayAsync(); foreach (RArtifactMasterRecord taggedItem in taggedItems) { taggedItem.LastModified = DateTime.Now; _context.Update(taggedItem); } RArtifactItemRecord[] taggedPages = await _context.Items.Include(a => a.Tags) .Where(a => a.Tags != null && a.Tags.Any(v => v.RTagId == tag.Id)) .ToArrayAsync(); foreach (RArtifactItemRecord taggedItem in taggedPages) { taggedItem.LastModified = DateTime.Now; _context.Update(taggedItem); } } /// /// changes order of tags based on their position in artifacts /// /// /// /// true => up, false => down /// the other tag which its Order has been changed public async Task> EditTagOrderBasedOnArtifact(Guid tagId, Guid artifactId, bool up) { RArtifactMasterRecord artifact = await _context.Artifacts .Include(a => a.Tags).ThenInclude(t => t.RTag) .Where(a => a.Id == artifactId) .AsNoTracking() .SingleOrDefaultAsync(); if (artifact == null) { return new RServiceResult(null, "artifact not found"); } RArtifactMasterRecordViewModel viewModel = RArtifactMasterRecord.ToViewModel(artifact); //tags are sorted in this method int tagOrder = viewModel.ArtifactTags.Where(tag => tag.Id == tagId).FirstOrDefault().Order; RArtifactTagViewModel otherTagViewModel; if (up) { otherTagViewModel = viewModel.ArtifactTags.Where(tag => tag.Order < tagOrder).OrderByDescending(tag => tag.Order).FirstOrDefault(); } else { otherTagViewModel = viewModel.ArtifactTags.Where(tag => tag.Order > tagOrder).OrderBy(tag => tag.Order).FirstOrDefault(); } if (otherTagViewModel == null) { return new RServiceResult(null, "Invalid movement"); } RTag rTag1 = await _context.Tags.Where(tag => tag.Id == tagId).FirstOrDefaultAsync(); RTag rTag2 = await _context.Tags.Where(tag => tag.Id == otherTagViewModel.Id).FirstOrDefaultAsync(); rTag1.Order = otherTagViewModel.Order; rTag2.Order = tagOrder; _context.Tags.UpdateRange(new RTag[] { rTag1, rTag2 }); await UpdateTaggedItems(rTag1); await UpdateTaggedItems(rTag2); await _context.SaveChangesAsync(); return new RServiceResult((Guid?)rTag2.Id); } /// /// changes order of tags based on their position in artifact items /// /// /// /// true => up, false => down /// the other tag which its Order has been changed public async Task> EditTagOrderBasedOnItem(Guid tagId, Guid itemId, bool up) { RArtifactItemRecord item = await _context.Items .Include(a => a.Tags).ThenInclude(t => t.RTag) .Where(a => a.Id == itemId) .AsNoTracking() .SingleOrDefaultAsync(); if (item == null) { return new RServiceResult(null, "item not found"); } RArtifactItemRecordViewModel viewModel = new RArtifactItemRecordViewModel(); //tags are sorted in this method viewModel.Item = item; int tagOrder = viewModel.FormattedTags.Where(tag => tag.Id == tagId).FirstOrDefault().Order; RArtifactTagViewModel otherTagViewModel; if (up) { otherTagViewModel = viewModel.FormattedTags.Where(tag => tag.Order < tagOrder).OrderByDescending(tag => tag.Order).FirstOrDefault(); } else { otherTagViewModel = viewModel.FormattedTags.Where(tag => tag.Order > tagOrder).OrderBy(tag => tag.Order).FirstOrDefault(); } if (otherTagViewModel == null) { return new RServiceResult(null, "Invalid movement"); } RTag rTag1 = await _context.Tags.Where(tag => tag.Id == tagId).FirstOrDefaultAsync(); RTag rTag2 = await _context.Tags.Where(tag => tag.Id == otherTagViewModel.Id).FirstOrDefaultAsync(); rTag1.Order = otherTagViewModel.Order; rTag2.Order = tagOrder; _context.Tags.UpdateRange(new RTag[] { rTag1, rTag2 }); await UpdateTaggedItems(rTag1); await UpdateTaggedItems(rTag2); await _context.SaveChangesAsync(); return new RServiceResult((Guid?)rTag2.Id); } /// /// get tag value bundle by frindly url /// /// /// /// public async Task> GetTagValueBundleByFiendlyUrl(string friendlyUrl, string valueUrl) { RTag tag = await _context.Tags .Where(a => a.FriendlyUrl == friendlyUrl) .AsNoTracking() .SingleOrDefaultAsync(); if (tag != null) { RArtifactTagViewModel viewModel = new RArtifactTagViewModel() { Id = tag.Id, Order = tag.Order, TagType = tag.TagType, FriendlyUrl = tag.FriendlyUrl, Status = tag.Status, Name = tag.Name, NameInEnglish = tag.NameInEnglish, GlobalValue = tag.GlobalValue, PluralName = tag.PluralName, PluralNameInEnglish = tag.PluralNameInEnglish }; viewModel.Values = new RTagValue[] { await _context.TagValues .Where(value => value.RTagId == tag.Id && value.FriendlyUrl == valueUrl) .FirstOrDefaultAsync() }; return new RServiceResult(viewModel); } return new RServiceResult(null); } /// /// add artifact tag value /// /// /// /// public async Task> TagArtifact(Guid artifactId, RTag rTag) { RTag type = await _context.Tags.Where(a => a.Id == rTag.Id).SingleOrDefaultAsync(); RArtifactMasterRecord item = await _context.Artifacts.Include(i => i.Tags).Where(i => i.Id == artifactId).SingleOrDefaultAsync(); int order = item.Tags.Where(t => t.RTagId == type.Id).Count() == 0 ? 1 : item.Tags.Where(t => t.RTagId == type.Id).OrderByDescending(t => t.Order).FirstOrDefault().Order + 1; RTagValue tag = new RTagValue() { Order = order, Value = "", ValueInEnglish = "", ValueSupplement = "", RTag = type, Status = PublishStatus.Published }; item.Tags.Add(tag); item.LastModified = DateTime.Now; _context.Artifacts.Update(item); await _context.SaveChangesAsync(); return new RServiceResult(tag); } /// /// remove artfiact tag value /// /// /// /// public async Task> UnTagArtifact(Guid artifactId, Guid tagValueId) { RArtifactMasterRecord item = await _context.Artifacts.Include(i => i.Tags).Where(i => i.Id == artifactId).SingleOrDefaultAsync(); item.Tags.Remove(item.Tags.Where(t => t.Id == tagValueId).SingleOrDefault()); item.LastModified = DateTime.Now; _context.Artifacts.Update(item); await _context.SaveChangesAsync(); return new RServiceResult(true); } /// /// edit artifact tag value /// /// /// /// apply on all same value tags /// public async Task> EditTagValue(Guid artifactId, RTagValue edited, bool global) { if (string.IsNullOrEmpty(edited.Value)) { return new RServiceResult(null, "Value could not be empty."); } RArtifactMasterRecord artifact = await _context.Artifacts .Include(a => a.Tags) .Where(a => a.Id == artifactId) .SingleOrDefaultAsync(); if (artifact == null) return new RServiceResult(null); RTagValue tag = artifact.Tags.Where(a => a.Id == edited.Id) .SingleOrDefault(); if (tag != null) { tag.Order = edited.Order; tag.ValueSupplement = edited.ValueSupplement; _context.Update(tag); if (global) { RTagValue[] sameValueTags = await _context.TagValues.Where(v => v.Value == tag.Value && v.RTagId == tag.RTagId).ToArrayAsync(); foreach (RTagValue sameValueTag in sameValueTags) { sameValueTag.Value = edited.Value; sameValueTag.ValueInEnglish = edited.ValueInEnglish; sameValueTag.Status = edited.Status; sameValueTag.FriendlyUrl = edited.FriendlyUrl; _context.Update(sameValueTag); RArtifactMasterRecord correspondingArtifact = await _context.Artifacts.Include(a => a.Tags).Where(a => a.Tags.Contains(sameValueTag)).SingleOrDefaultAsync(); if (correspondingArtifact != null) { correspondingArtifact.LastModified = DateTime.Now; _context.Update(correspondingArtifact); } RArtifactItemRecord correspondingItem = await _context.Items.Include(a => a.Tags).Where(a => a.Tags.Contains(sameValueTag)).SingleOrDefaultAsync(); if (correspondingItem != null) { correspondingItem.LastModified = DateTime.Now; _context.Update(correspondingItem); } } await _context.SaveChangesAsync(); } else { tag.Value = edited.Value; tag.ValueInEnglish = edited.ValueInEnglish; tag.Order = edited.Order; tag.FriendlyUrl = edited.FriendlyUrl; tag.Status = edited.Status; tag.ValueSupplement = edited.ValueSupplement; _context.Update(tag); artifact.LastModified = DateTime.Now; _context.Update(artifact); await _context.SaveChangesAsync(); } } return new RServiceResult(tag); } /// /// add item tag value /// /// /// /// public async Task> TagItem(Guid itemId, RTag rTag) { RTag type = await _context.Tags.Where(a => a.Id == rTag.Id).SingleOrDefaultAsync(); RArtifactItemRecord item = await _context.Items.Include(i => i.Tags).Where(i => i.Id == itemId).SingleOrDefaultAsync(); int order = item.Tags.Where(t => t.RTagId == type.Id).Count() == 0 ? 1 : item.Tags.Where(t => t.RTagId == type.Id).OrderByDescending(t => t.Order).FirstOrDefault().Order + 1; RTagValue tag = new RTagValue() { Order = order, Value = "", ValueInEnglish = "", ValueSupplement = "", RTag = type, Status = PublishStatus.Published }; item.Tags.Add(tag); item.LastModified = DateTime.Now; _context.Items.Update(item); await _context.SaveChangesAsync(); return new RServiceResult(tag); } /// /// remove item tag value /// /// /// /// public async Task> UnTagItem(Guid itemId, Guid tagValueId) { RArtifactItemRecord item = await _context.Items.Include(i => i.Tags).Where(i => i.Id == itemId).SingleOrDefaultAsync(); item.Tags.Remove(item.Tags.Where(t => t.Id == tagValueId).SingleOrDefault()); item.LastModified = DateTime.Now; _context.Items.Update(item); await _context.SaveChangesAsync(); return new RServiceResult(true); } /// /// edit item tagibute value /// /// /// /// apply on all same value tags /// public async Task> EditItemTagValue(Guid itemtId, RTagValue edited, bool global) { if (string.IsNullOrEmpty(edited.Value)) { return new RServiceResult(null, "Value could not be empty."); } RArtifactItemRecord item = await _context.Items .Include(a => a.Tags) .Where(a => a.Id == itemtId) .SingleOrDefaultAsync(); if (item == null) return new RServiceResult(null); RTagValue tag = item.Tags.Where(a => a.Id == edited.Id) .SingleOrDefault(); if (tag != null) { tag.Order = edited.Order; tag.ValueSupplement = edited.ValueSupplement; _context.Update(tag); if (global) { RTagValue[] sameValueTags = await _context.TagValues.Where(v => v.Value == tag.Value && v.RTagId == tag.RTagId).ToArrayAsync(); foreach (RTagValue sameValueTag in sameValueTags) { sameValueTag.Value = edited.Value; sameValueTag.ValueInEnglish = edited.ValueInEnglish; sameValueTag.Status = edited.Status; sameValueTag.FriendlyUrl = edited.FriendlyUrl; _context.Update(sameValueTag); RArtifactMasterRecord correspondingArtifact = await _context.Artifacts.Include(a => a.Tags).Where(a => a.Tags.Contains(sameValueTag)).SingleOrDefaultAsync(); if (correspondingArtifact != null) { correspondingArtifact.LastModified = DateTime.Now; _context.Update(correspondingArtifact); } RArtifactItemRecord correspondingItem = await _context.Items.Include(a => a.Tags).Where(a => a.Tags.Contains(sameValueTag)).SingleOrDefaultAsync(); if (correspondingItem != null) { correspondingItem.LastModified = DateTime.Now; _context.Update(correspondingItem); } } await _context.SaveChangesAsync(); } else { tag.Value = edited.Value; tag.ValueInEnglish = edited.ValueInEnglish; tag.Order = edited.Order; tag.FriendlyUrl = edited.FriendlyUrl; tag.Status = edited.Status; tag.ValueSupplement = edited.ValueSupplement; _context.Update(tag); item.LastModified = DateTime.Now; _context.Update(item); await _context.SaveChangesAsync(); } } return new RServiceResult(tag); } /// /// changes order of tag values based on their position in an artifact /// /// /// /// /// true => up, false => down /// the other tag value which its Order has been changed public async Task> EditTagValueOrder(Guid artifactId, Guid tagId, Guid valueId, bool up) { RArtifactMasterRecord artifact = await _context.Artifacts .Include(a => a.Tags).ThenInclude(t => t.RTag) .Where(a => a.Id == artifactId) .SingleOrDefaultAsync(); if (artifact == null) { return new RServiceResult(null, "artifact not found"); } RArtifactMasterRecordViewModel viewModel = RArtifactMasterRecord.ToViewModel(artifact); //tags are sorted in this method RArtifactTagViewModel tag = viewModel.ArtifactTags.Where(t => t.Id == tagId).FirstOrDefault(); RTagValue value1 = tag.Values.Where(v => v.Id == valueId).FirstOrDefault(); RTagValue value2; if (up) { value2 = tag.Values.Where(v => v.Order < value1.Order).OrderByDescending(v => v.Order).FirstOrDefault(); } else { value2 = tag.Values.Where(v => v.Order > value1.Order).OrderBy(v => v.Order).FirstOrDefault(); } if (value2 == null) { return new RServiceResult(null, "Invalid movement"); } int nOrder = value1.Order; value1.Order = value2.Order; value2.Order = nOrder; _context.TagValues.UpdateRange(new RTagValue[] { value1, value2 }); artifact.LastModified = DateTime.Now; _context.Artifacts.Update(artifact); await _context.SaveChangesAsync(); return new RServiceResult((Guid?)value2.Id); } /// /// changes order of tag values based on their position in an item /// /// /// /// /// true => up, false => down /// the other tag value which its Order has been changed public async Task> EditItemTagValueOrder(Guid itemId, Guid tagId, Guid valueId, bool up) { RArtifactItemRecord item = await _context.Items .Include(a => a.Tags).ThenInclude(t => t.RTag) .Where(a => a.Id == itemId) .SingleOrDefaultAsync(); if (item == null) { return new RServiceResult(null, "item not found"); } RArtifactItemRecordViewModel viewModel = new RArtifactItemRecordViewModel(); //tags are sorted in this method viewModel.Item = item; RArtifactTagViewModel tag = viewModel.FormattedTags.Where(t => t.Id == tagId).FirstOrDefault(); RTagValue value1 = tag.Values.Where(v => v.Id == valueId).FirstOrDefault(); RTagValue value2; if (up) { value2 = tag.Values.Where(v => v.Order < value1.Order).OrderByDescending(v => v.Order).FirstOrDefault(); } else { value2 = tag.Values.Where(v => v.Order > value1.Order).OrderBy(v => v.Order).FirstOrDefault(); } if (value2 == null) { return new RServiceResult(null, "Invalid movement"); } int nOrder = value1.Order; value1.Order = value2.Order; value2.Order = nOrder; _context.TagValues.UpdateRange(new RTagValue[] { value1, value2 }); item.LastModified = DateTime.Now; _context.Items.Update(item); await _context.SaveChangesAsync(); return new RServiceResult((Guid?)value2.Id); } /// /// gets specified artifact item info (including images + tagibutes) /// /// /// /// /// public async Task> GetArtifactItemByFrienlyUrl(string artifactUrl, string itemUrl, PublishStatus[] statusArray) { RArtifactMasterRecord parent = await _context.Artifacts .Include(a => a.CoverImage) .Where(a => statusArray.Contains(a.Status) && a.FriendlyUrl == artifactUrl) .AsNoTracking() .SingleOrDefaultAsync(); if (parent == null) return new RServiceResult(null); RArtifactItemRecord item = await _context.Items.AsNoTracking().Include(i => i.Images).Include(i => i.Tags).ThenInclude(t => t.RTag). Where(i => i.RArtifactMasterRecordId == parent.Id && i.FriendlyUrl == itemUrl).SingleAsync(); if (item == null) return new RServiceResult(null); if (item.Images != null) item.Images = item.Images.OrderBy(i => i.Order).ToArray(); if (item.Tags != null) item.Tags = item.Tags.OrderBy(a => a.RTag.Order).ToArray(); RArtifactItemRecord nextItem = await _context.Items.AsNoTracking().Include(i => i.Images).Where(i => i.RArtifactMasterRecordId == parent.Id && i.Order > item.Order).OrderBy(i => i.Order).FirstOrDefaultAsync(); RArtifactItemRecord prevItem = await _context.Items.AsNoTracking().Include(i => i.Images).Where(i => i.RArtifactMasterRecordId == parent.Id && i.Order < item.Order).OrderByDescending(i => i.Order).FirstOrDefaultAsync(); RArtifactItemRecordViewModel res = new RArtifactItemRecordViewModel() { Item = item, ParentFriendlyUrl = parent.FriendlyUrl, ParentName = parent.Name, ParentImageId = parent.CoverImageId, ParentExternalNormalSizeImageUrl = parent.CoverImage.ExternalNormalSizeImageUrl, ParentItemCount = parent.ItemCount, NextItemFriendlyUrl = nextItem == null ? "" : nextItem.FriendlyUrl, NextItemImageId = nextItem == null ? null : nextItem.Images.First().Id, NextItemExternalNormalSizeImageUrl = nextItem == null ? null : nextItem.Images.First().ExternalNormalSizeImageUrl, PreviousItemFriendlyUrl = prevItem == null ? "" : prevItem.FriendlyUrl, PrevItemImageId = prevItem == null ? null : prevItem.Images.First().Id, PrevItemExternalNormalSizeImageUrl = prevItem == null ? null : prevItem.Images.First().ExternalNormalSizeImageUrl, }; return new RServiceResult(res); } /// /// update artifact item /// /// /// public async Task> UpdateArtifactItemRecordAsync(RArtifactItemRecord item) { try { var dbItem = await _context.Items.Where(i => i.Id == item.Id).SingleAsync(); _context.Entry(dbItem).CurrentValues.SetValues(item); _context.Update(dbItem); await _context.SaveChangesAsync(); return new RServiceResult(true); } catch (Exception exp) { return new RServiceResult(false, exp.ToString()); } } /// /// add new artifact /// /// /// /// /// /// /// /// /// /// public async Task> Add ( string name, string description, string srcurl, string pictitle, string picdescription, IFormFile file, string picsrcurl, Stream stream ) { RServiceResult picture = await _pictureFileService.Add(pictitle, picdescription, 1, file, picsrcurl, stream, "", ""); if (picture.Result == null) { return new RServiceResult(null, $"_pictureFileService.Add : {picture.ExceptionString}"); } RArtifactItemRecord item = new RArtifactItemRecord() { Order = 1, CoverImageIndex = 0, Images = new RPictureFile[] { picture.Result } }; RArtifactMasterRecord artifact = new RArtifactMasterRecord(name, description) { Name = name, NameInEnglish = name, Description = description, DescriptionInEnglish = description, Status = PublishStatus.Draft, DateTime = DateTime.Now, LastModified = DateTime.Now, CoverItemIndex = 0, Items = new RArtifactItemRecord[] { item }, ItemCount = 1, CoverImage = picture.Result }; await _context.Artifacts.AddAsync(artifact); await _context.SaveChangesAsync(); return new RServiceResult(artifact); } /// /// import from external resources /// /// pdf/loc/princeton/harvard/qajarwomen/hathitrust/penn/cam/bl/folder/walters/cbl/append/iiif /// 119/foldername /// golestan-baysonghori/artifact id /// /// /// public async Task> Import(string srcType, string resourceNumber, string friendlyUrl, string resourcePrefix, bool skipUpload) { return /*srcType == "pdf" ? await StartImportingLocalPDFFile(resourceNumber, friendlyUrl, resourcePrefix, skipUpload) :*/ srcType == "princeton" ? await StartImportingFromPrinceton(resourceNumber, friendlyUrl, skipUpload) : srcType == "harvard" ? await StartImportingFromHarvard(resourceNumber, friendlyUrl, skipUpload) : srcType == "qajarwomen" ? await StartImportingFromHarvardDirectly(resourceNumber, friendlyUrl, resourcePrefix, skipUpload) : srcType == "hathitrust" ? await StartImportingFromHathiTrust(resourceNumber, friendlyUrl, skipUpload) : srcType == "penn" ? await StartImportingFromPenLibraries(resourceNumber, friendlyUrl, skipUpload) : srcType == "cam" ? await StartImportingFromCambridge(resourceNumber, friendlyUrl, skipUpload) : srcType == "bl" ? await StartImportingFromBritishLibrary(resourceNumber, friendlyUrl, skipUpload) : srcType == "folder" ? await StartImportingFromServerFolder(resourceNumber, friendlyUrl, resourcePrefix, skipUpload) : srcType == "append" ? await StartAppendingFromServerFolder(resourceNumber, Guid.Parse(friendlyUrl), skipUpload) : srcType == "walters" ? await StartImportingFromWalters(resourceNumber, friendlyUrl, skipUpload) : srcType == "cbl" ? await StartImportingFromChesterBeatty(resourceNumber, friendlyUrl, skipUpload) : srcType == "iiif" ? await StartImportingFromIIIF(resourceNumber, friendlyUrl, skipUpload) : await StartImportingFromTheLibraryOfCongress(resourceNumber, friendlyUrl, resourcePrefix, skipUpload); } /// /// upload artifact to external server /// /// /// /// public RServiceResult StartUploadingArtifactToExternalServer(string friendlyUrl, bool skipUpload) { 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($"StartUploadingArtifactToExternalServer : {friendlyUrl}", "Query data")).Result; try { var book = await context.Artifacts.Include(a => a.Items).ThenInclude(i => i.Images).Include(a => a.CoverImage).Where(a => a.FriendlyUrl == friendlyUrl).SingleAsync(); await jobProgressServiceEF.UpdateJob(job.Id, 2, $"بارگذاری {book.Name}"); var resUpload = await _UploadArtifactToExternalServer(book, context, skipUpload); if (!string.IsNullOrEmpty(resUpload.ExceptionString)) { await jobProgressServiceEF.UpdateJob(job.Id, 100, "", false, resUpload.ExceptionString); } if (!resUpload.Result) { await jobProgressServiceEF.UpdateJob(job.Id, 100, "", false, "_UploadArtifactToExternalServer returned false"); } await jobProgressServiceEF.UpdateJob(job.Id, 100, succeeded: 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()); } } /// /// upload artifact to external server /// /// /// /// /// private async Task> _UploadArtifactToExternalServer(RArtifactMasterRecord book, RMuseumDbContext context, bool skipUpload) { LongRunningJobProgressServiceEF jobProgressServiceEF = new LongRunningJobProgressServiceEF(context); var job = (await jobProgressServiceEF.NewJob("_UploadArtifactToExternalServer", $"Uploading {book.Name}")).Result; try { if (bool.Parse(Configuration.GetSection("ExternalFTPServer")["UploadEnabled"])) { var ftpClient = new AsyncFtpClient ( Configuration.GetSection("ExternalFTPServer")["Host"], Configuration.GetSection("ExternalFTPServer")["Username"], Configuration.GetSection("ExternalFTPServer")["Password"] ); if (!skipUpload) { ftpClient.ValidateCertificate += FtpClient_ValidateCertificate; await ftpClient.AutoConnect(); ftpClient.Config.RetryAttempts = 3; } foreach (var imageSizeString in new string[] { "orig", "norm", "thumb" }) { var localFilePath = _pictureFileService.GetImagePath(book.CoverImage, imageSizeString).Result; if (imageSizeString == "norm") { book.CoverImage.ExternalNormalSizeImageUrl = $"{Configuration.GetSection("ExternalFTPServer")["RootUrl"]}/{book.CoverImage.FolderName}/{imageSizeString}/{Path.GetFileName(localFilePath)}"; context.Update(book.CoverImage); } if (!skipUpload) { await jobProgressServiceEF.UpdateJob(job.Id, 0, localFilePath); var remoteFilePath = $"{Configuration.GetSection("ExternalFTPServer")["RootPath"]}/images/{book.CoverImage.FolderName}/{imageSizeString}/{Path.GetFileName(localFilePath)}"; await ftpClient.UploadFile(localFilePath, remoteFilePath, createRemoteDir: true); } } foreach (var item in book.Items) { foreach (var image in item.Images) { foreach (var imageSizeString in new string[] { "orig", "norm", "thumb" }) { var localFilePath = _pictureFileService.GetImagePath(image, imageSizeString).Result; if (imageSizeString == "norm") { image.ExternalNormalSizeImageUrl = $"{Configuration.GetSection("ExternalFTPServer")["RootUrl"]}/{image.FolderName}/{imageSizeString}/{Path.GetFileName(localFilePath)}"; context.Update(image); } if (!skipUpload) { await jobProgressServiceEF.UpdateJob(job.Id, 0, localFilePath); var remoteFilePath = $"{Configuration.GetSection("ExternalFTPServer")["RootPath"]}/images/{book.CoverImage.FolderName}/{imageSizeString}/{Path.GetFileName(localFilePath)}"; await ftpClient.UploadFile(localFilePath, remoteFilePath, createRemoteDir: true); } } } } if (!skipUpload) { await ftpClient.Disconnect(); } await jobProgressServiceEF.UpdateJob(job.Id, 100, "", true); await context.SaveChangesAsync();//redundant } } catch (Exception exp) { await jobProgressServiceEF.UpdateJob(job.Id, 100, "", false, exp.ToString()); return new RServiceResult(false, exp.ToString()); } try { if (bool.Parse(Configuration.GetSection("BackupFTPServer")["UploadEnabled"])) { var ftpClient = new AsyncFtpClient ( Configuration.GetSection("BackupFTPServer")["Host"], Configuration.GetSection("BackupFTPServer")["Username"], Configuration.GetSection("BackupFTPServer")["Password"] ); if (!skipUpload) { ftpClient.ValidateCertificate += FtpClient_ValidateCertificate; await ftpClient.AutoConnect(); ftpClient.Config.RetryAttempts = 3; } foreach (var imageSizeString in new string[] { "orig", "norm", "thumb" }) { var localFilePath = _pictureFileService.GetImagePath(book.CoverImage, imageSizeString).Result; if (imageSizeString == "norm") { book.CoverImage.ExternalNormalSizeImageUrl = $"{Configuration.GetSection("BackupFTPServer")["RootUrl"]}/{book.CoverImage.FolderName}/{imageSizeString}/{Path.GetFileName(localFilePath)}"; context.Update(book.CoverImage); } if (!skipUpload) { await jobProgressServiceEF.UpdateJob(job.Id, 0, localFilePath); var remoteFilePath = $"{Configuration.GetSection("BackupFTPServer")["RootPath"]}/images/{book.CoverImage.FolderName}/{imageSizeString}/{Path.GetFileName(localFilePath)}"; await ftpClient.UploadFile(localFilePath, remoteFilePath, createRemoteDir: true); } } foreach (var item in book.Items) { foreach (var image in item.Images) { foreach (var imageSizeString in new string[] { "orig", "norm", "thumb" }) { var localFilePath = _pictureFileService.GetImagePath(image, imageSizeString).Result; if (imageSizeString == "norm") { image.ExternalNormalSizeImageUrl = $"{Configuration.GetSection("BackupFTPServer")["RootUrl"]}/{image.FolderName}/{imageSizeString}/{Path.GetFileName(localFilePath)}"; context.Update(image); } if (!skipUpload) { await jobProgressServiceEF.UpdateJob(job.Id, 0, localFilePath); var remoteFilePath = $"{Configuration.GetSection("BackupFTPServer")["RootPath"]}/images/{book.CoverImage.FolderName}/{imageSizeString}/{Path.GetFileName(localFilePath)}"; await ftpClient.UploadFile(localFilePath, remoteFilePath, createRemoteDir: true); } } } } if (!skipUpload) { await ftpClient.Disconnect(); } await jobProgressServiceEF.UpdateJob(job.Id, 100, "", true); await context.SaveChangesAsync();//redundant } } catch (Exception exp) { await jobProgressServiceEF.UpdateJob(job.Id, 100, "", false, exp.ToString()); return new RServiceResult(false, exp.ToString()); } return new RServiceResult(true); } /// /// reschedule jobs /// /// /// /// public async Task> RescheduleJobs(JobType jobType, bool skipUpload) { ImportJob[] jobs = await _context.ImportJobs.Where(j => j.Status != ImportJobStatus.Succeeded && j.JobType == jobType).OrderByDescending(j => j.ProgressPercent).ToArrayAsync(); List scheduled = new List(); foreach (ImportJob job in jobs) { if (job.Status != ImportJobStatus.Failed) { job.Status = ImportJobStatus.Aborted; job.EndTime = DateTime.Now; job.Exception = "Aborted by RescheduleJobs"; _context.Update(job); await _context.SaveChangesAsync(); } if ( await _context.ImportJobs.Where(j => j.ResourceNumber == job.ResourceNumber && j.Status == ImportJobStatus.Succeeded).SingleOrDefaultAsync() != null ) continue; if (scheduled.IndexOf(job.ResourceNumber) == -1) { scheduled.Add(job.ResourceNumber); RServiceResult rescheduled = /*job.JobType == JobType.Pdf ? await StartImportingLocalPDFFile(job.ResourceNumber, job.FriendlyUrl, job.SrcUrl, skipUpload) :*/ job.JobType == JobType.Princeton ? await StartImportingFromPrinceton(job.ResourceNumber, job.FriendlyUrl, skipUpload) : job.JobType == JobType.Harvard ? await StartImportingFromHarvard(job.ResourceNumber, job.FriendlyUrl, skipUpload) : job.JobType == JobType.HarvardDirect ? await StartImportingFromHarvardDirectly(job.ResourceNumber, job.FriendlyUrl, job.SrcUrl, skipUpload) : job.JobType == JobType.HathiTrust ? await StartImportingFromHathiTrust(job.ResourceNumber, job.FriendlyUrl, skipUpload) : job.JobType == JobType.PennLibraries ? await StartImportingFromPenLibraries(job.ResourceNumber, job.FriendlyUrl, skipUpload) : job.JobType == JobType.Cambridge ? await StartImportingFromCambridge(job.ResourceNumber, job.FriendlyUrl, skipUpload) : job.JobType == JobType.BritishLibrary ? await StartImportingFromBritishLibrary(job.ResourceNumber, job.FriendlyUrl, skipUpload) : job.JobType == JobType.ServerFolder ? await StartImportingFromServerFolder(job.ResourceNumber, job.FriendlyUrl, job.SrcUrl, skipUpload) : job.JobType == JobType.Walters ? await StartImportingFromWalters(job.ResourceNumber, job.FriendlyUrl, skipUpload) : job.JobType == JobType.ChesterBeatty ? await StartImportingFromChesterBeatty(job.ResourceNumber, job.FriendlyUrl, skipUpload) : job.JobType == JobType.IIIF ? await StartImportingFromIIIF(job.ResourceNumber, job.FriendlyUrl, skipUpload) : new RServiceResult(false, "StartImportingFromTheLibraryOfCongress NOT SUPPORTED"); if (rescheduled.Result) { _context.ImportJobs.Remove(job); await _context.SaveChangesAsync(); } } } return new RServiceResult(true); } /// /// an incomplete prototype for removing artifacts /// /// /// /// public async Task> RemoveArtifact(Guid artifactId, bool checkJobs) { try { RArtifactMasterRecord record = await _context.Artifacts .Include(a => a.Items).ThenInclude(i => i.Images) .Include(a => a.Items).ThenInclude(i => i.Tags) .Include(a => a.Tags) .Where(a => a.Id == artifactId) .SingleOrDefaultAsync(); if (record == null) { return new RServiceResult(false, "Artifact not found."); } if (record.Status == PublishStatus.Published) { return new RServiceResult(false, "Can not delete published artifact"); } if (await _context.UserBookmarks.Where(b => b.RArtifactMasterRecordId == artifactId).AnyAsync()) { var bookmarks = await _context.UserBookmarks.Where(b => b.RArtifactMasterRecordId == artifactId).ToListAsync(); _context.RemoveRange(bookmarks); } if (await _context.UserNotes.Where(n => n.RArtifactMasterRecordId == artifactId).AnyAsync()) { var notes = await _context.UserNotes.Where(n => n.RArtifactMasterRecordId == artifactId).ToListAsync(); _context.RemoveRange(notes); } if (await _context.DiwanLinks.Where(l => l.ArtifactId == artifactId).AnyAsync()) { var links = await _context.DiwanLinks.Where(l => l.ArtifactId == artifactId).ToListAsync(); _context.RemoveRange(links); } if (checkJobs) { var jobs = await _context.ImportJobs.Where(j => j.ArtifactId == artifactId).ToArrayAsync(); if (jobs.Length > 0) { _context.ImportJobs.RemoveRange(jobs); } } var pins = await _context.PinterestLinks.Where(j => j.ArtifactId == artifactId).ToArrayAsync(); if (pins.Length > 0) { _context.PinterestLinks.RemoveRange(pins); } string artifactFolder = ""; if (record.Items.Count > 0) { var firstImage = record.Items.First(); if (firstImage.Images.Count > 0) { artifactFolder = Path.Combine(_pictureFileService.ImageStoragePath, firstImage.Images.First().FolderName); } } foreach (RArtifactItemRecord item in record.Items) { var bookmarks = await _context.UserBookmarks.Where(b => b.RArtifactItemRecordId == item.Id).ToListAsync(); _context.RemoveRange(bookmarks); var notes = await _context.UserNotes.Where(n => n.RArtifactItemRecordId == artifactId).ToListAsync(); _context.RemoveRange(notes); _context.PictureFiles.RemoveRange(item.Images); _context.TagValues.RemoveRange(item.Tags); } _context.Items.RemoveRange(record.Items); _context.TagValues.RemoveRange(record.Tags); _context.Artifacts.Remove(record); await _context.SaveChangesAsync(); if (!string.IsNullOrEmpty(artifactFolder)) { try { Directory.Delete(artifactFolder, true); } catch { //ignore errprs } } } catch (Exception exp) { return new RServiceResult(false, exp.ToString()); } return new RServiceResult(true); } private async Task HandleSimpleValue(RMuseumDbContext context, JObject parsed, List meta, string path, string aName) { try { string string_value = parsed.SelectToken(path).Value(); if (!string.IsNullOrWhiteSpace(string_value)) { meta.Add ( await TagHandler.PrepareAttribute(context, aName, string_value, 1) ); } return string_value; } catch { return ""; } } private async Task HandleListValue(RMuseumDbContext context, JObject parsed, List meta, string path, string aName) { try { string value = ""; List list = parsed.SelectToken(path).ToObject>(); if (list != null && list.Count > 0) { value = list[0]; meta.Add ( await TagHandler.PrepareAttribute(context, aName, list[0], 1) ); for (int i = 1; i < list.Count; i++) { value += $"
{Environment.NewLine}{list[i]}"; meta.Add ( await TagHandler.PrepareAttribute(context, aName, list[i], i + 1) ); } } return value; } catch { return ""; } } private async Task HandleChildrenValue(RMuseumDbContext context, JObject parsed, List meta, string path, string aName) { try { string value = ""; var children = parsed.SelectToken(path).Children().ToList(); if (children.Count > 0) { value = children[0].First.Path.Replace($"{path}[0].", "").Replace($"{path}[0]", ""); meta.Add ( await TagHandler.PrepareAttribute(context, aName, value, 1) ); for (int i = 1; i < children.Count; i++) { value += $"
{Environment.NewLine}{children[0].First.Path.Replace($"{path}[{i}].", "")}"; meta.Add ( await TagHandler.PrepareAttribute(context, aName, children[0].First.Path.Replace($"{path}[{i}].", ""), i + 1) ); } } return value; } catch { return ""; } } /// /// import jobs /// /// /// public async Task> GetImportJobs(PagingParameterModel paging) { var source = _context.ImportJobs .Select(j => new ImportJob() { Id = j.Id, Artifact = j.Artifact, ArtifactId = j.ArtifactId, EndTime = j.EndTime, Exception = j.Exception, ProgressPercent = j.ProgressPercent, QueueTime = j.QueueTime, FriendlyUrl = j.FriendlyUrl, JobType = j.JobType, ResourceNumber = j.ResourceNumber, SrcContent = "--omitted--", SrcUrl = j.SrcUrl, StartTime = j.StartTime, Status = j.Status }) .OrderByDescending(t => t.QueueTime) .AsQueryable(); (PaginationMetadata PagingMeta, ImportJob[] Items) paginatedResult = await QueryablePaginator.Paginate(source, paging); return new RServiceResult<(PaginationMetadata PagingMeta, ImportJob[] Items)>(paginatedResult); } /// /// Bookmark Artifact /// /// /// /// /// public async Task> BookmarkArtifact(Guid artifactId, Guid userId, RBookmarkType type) { if ((await _context.UserBookmarks.Where(b => b.RAppUserId == userId && b.RArtifactMasterRecordId == artifactId && b.RBookmarkType == type).SingleOrDefaultAsync()) != null) { return new RServiceResult(null, "Artifact is already bookmarked/faved."); } RUserBookmark bookmark = new RUserBookmark() { RAppUserId = userId, RArtifactMasterRecordId = artifactId, RBookmarkType = type, DateTime = DateTime.Now, Note = "" }; _context.UserBookmarks.Add(bookmark); await _context.SaveChangesAsync(); return new RServiceResult(bookmark); } /// /// get artifact user bookmarks /// /// /// /// public async Task> GetArtifactUserBookmarks(Guid artifactId, Guid userId) { RUserBookmark[] bookmarks = await _context.UserBookmarks.Where(b => b.RArtifactMasterRecordId == artifactId && b.RAppUserId == userId).ToArrayAsync(); return new RServiceResult(bookmarks); } /// /// Bookmark Item /// /// /// /// /// public async Task> BookmarkItem(Guid itemId, Guid userId, RBookmarkType type) { if ((await _context.UserBookmarks.Where(b => b.RAppUserId == userId && b.RArtifactItemRecordId == itemId && b.RBookmarkType == type).SingleOrDefaultAsync()) != null) { return new RServiceResult(null, "Item is already bookmarked/faved."); } RUserBookmark bookmark = new RUserBookmark() { RAppUserId = userId, RArtifactItemRecordId = itemId, RBookmarkType = type, DateTime = DateTime.Now, Note = "" }; _context.UserBookmarks.Add(bookmark); await _context.SaveChangesAsync(); return new RServiceResult(bookmark); } /// /// get item user bookmarks /// /// /// /// public async Task> GeItemUserBookmarks(Guid itemId, Guid userId) { RUserBookmark[] bookmarks = await _context.UserBookmarks.Where(b => b.RArtifactItemRecordId == itemId && b.RAppUserId == userId).ToArrayAsync(); return new RServiceResult(bookmarks); } /// /// update bookmark note /// /// /// /// public async Task> UpdateUserBookmark(Guid bookmarkId, string note) { RUserBookmark bookmark = await _context.UserBookmarks.Where(b => b.Id == bookmarkId).SingleOrDefaultAsync(); if (bookmark == null) { return new RServiceResult(false, "bookmark not found"); } bookmark.Note = note; _context.UserBookmarks.Update(bookmark); await _context.SaveChangesAsync(); return new RServiceResult(true); } /// /// delete user bookmark /// /// /// /// public async Task> DeleteUserBookmark(Guid bookmarkId) { RUserBookmark bookmark = await _context.UserBookmarks.Where(b => b.Id == bookmarkId).SingleOrDefaultAsync(); if (bookmark == null) { return new RServiceResult(false, "bookmark not found"); } _context.UserBookmarks.Remove(bookmark); await _context.SaveChangesAsync(); return new RServiceResult(true); } /// /// get user bookmarks (artifacts and items) /// /// /// /// /// /// public async Task> GetBookmarks(PagingParameterModel paging, Guid userId, RBookmarkType type, PublishStatus[] statusArray) { var source = _context.UserBookmarks .Include(b => b.RArtifactMasterRecord).ThenInclude(a => a.CoverImage) .Include(b => b.RArtifactItemRecord).ThenInclude(b => b.Images) .Where(b => b.RAppUserId == userId && b.RBookmarkType == type) .OrderByDescending(b => b.DateTime) .AsQueryable(); (PaginationMetadata PagingMeta, RUserBookmark[] Bookmarks) paginatedResult1 = await QueryablePaginator.Paginate(source, paging); List finalList = new List(); foreach (RUserBookmark bookmark in paginatedResult1.Bookmarks) { RUserBookmarkViewModel model = new RUserBookmarkViewModel() { Id = bookmark.Id, RAppUserId = bookmark.RAppUserId, RArtifactMasterRecord = bookmark.RArtifactMasterRecord, RArtifactItemRecord = null,//this should be filled by an external call DateTime = bookmark.DateTime, RBookmarkType = bookmark.RBookmarkType, Note = bookmark.Note }; if (bookmark.RArtifactMasterRecord != null) { if (!statusArray.Contains(bookmark.RArtifactMasterRecord.Status)) continue; //this may result in paging bugs } if (bookmark.RArtifactItemRecord != null) { RArtifactMasterRecord parent = await _context.Artifacts .Where(a => statusArray.Contains(a.Status) && a.Id == bookmark.RArtifactItemRecord.RArtifactMasterRecordId) .AsNoTracking() .SingleOrDefaultAsync(); if (parent == null) continue; model.RArtifactItemRecord = new RArtifactItemRecordViewModel() { Item = bookmark.RArtifactItemRecord }; model.RArtifactItemRecord.ParentFriendlyUrl = parent.FriendlyUrl; model.RArtifactItemRecord.ParentName = parent.Name; } finalList.Add(model); } return new RServiceResult<(PaginationMetadata PagingMeta, RUserBookmarkViewModel[] Bookmarks)>((paginatedResult1.PagingMeta, finalList.ToArray())); } /// /// Add Note to Artifact /// /// /// /// /// /// /// public async Task> AddUserNoteToArtifact(Guid artifactId, Guid userId, RNoteType type, string noteContents, Guid? referenceNoteId) { RServiceResult userInfo = await _userService.GetUserInformation(userId); if (!string.IsNullOrEmpty(userInfo.ExceptionString)) return new RServiceResult(null, userInfo.ExceptionString); RUserNote note = new RUserNote() { RAppUserId = userId, RArtifactMasterRecordId = artifactId, NoteType = type, HtmlContent = noteContents, DateTime = DateTime.Now, LastModified = DateTime.Now, Modified = false, Status = PublishStatus.Published, ReferenceNoteId = referenceNoteId }; _context.UserNotes.Add(note); await _context.SaveChangesAsync(); //we need note.Id for the next step if (referenceNoteId != null) { RUserNote referenceNote = await _context.UserNotes.Where(n => n.Id == referenceNoteId).SingleOrDefaultAsync(); if (referenceNote == null) { return new RServiceResult(null, "Reference note not found!"); } if (referenceNote.RAppUserId != userId) { RArtifactMasterRecord artificat = await _context.Artifacts.Where(a => a.Id == artifactId).SingleOrDefaultAsync(); await _notificationService.PushNotification ( referenceNote.RAppUserId, $"پاسخگویی {userInfo.Result.NickName} به یادداشت شما دربارهٔ {artificat.Name}", $"برای مشاهدهٔ پاسخ ارائه شده اینجا را ببینید.
" + $"پاسخ داده شده:
" + $"
{note.HtmlContent}

" + $"یادداشت شما:
" + $"
{referenceNote.HtmlContent}
" ); } } return new RServiceResult ( new RUserNoteViewModel() { Id = note.Id, RAppUserId = note.RAppUserId, UserName = userInfo.Result.NickName, RUserImageId = userInfo.Result.RImageId, Modified = note.Modified, NoteType = note.NoteType, HtmlContent = note.HtmlContent, ReferenceNoteId = note.ReferenceNoteId, Status = note.Status, Notes = new RUserNoteViewModel[] { }, DateTime = RUserNoteViewModel.PrepareNoteDateTime(note.DateTime), LastModified = RUserNoteViewModel.PrepareNoteDateTime(note.LastModified) } ); } /// /// Add Note to Artifact Item /// /// /// /// /// /// /// public async Task> AddUserNoteToArtifactItem(Guid itemId, Guid userId, RNoteType type, string noteContents, Guid? referenceNoteId) { RServiceResult userInfo = await _userService.GetUserInformation(userId); if (!string.IsNullOrEmpty(userInfo.ExceptionString)) return new RServiceResult(null, userInfo.ExceptionString); RUserNote note = new RUserNote() { RAppUserId = userId, RArtifactItemRecordId = itemId, NoteType = type, HtmlContent = noteContents, DateTime = DateTime.Now, LastModified = DateTime.Now, Modified = false, Status = PublishStatus.Published, ReferenceNoteId = referenceNoteId }; _context.UserNotes.Add(note); await _context.SaveChangesAsync();//we need note.Id for the next step if (referenceNoteId != null) { RUserNote referenceNote = await _context.UserNotes.Where(n => n.Id == referenceNoteId).SingleOrDefaultAsync(); if (referenceNote == null) { return new RServiceResult(null, "Reference note not found!"); } if (referenceNote.RAppUserId != userId) { RArtifactItemRecord item = await _context.Items.Where(a => a.Id == itemId).SingleOrDefaultAsync(); RArtifactMasterRecord artificat = await _context.Artifacts.Where(a => a.Id == item.RArtifactMasterRecordId).SingleOrDefaultAsync(); await _notificationService.PushNotification ( referenceNote.RAppUserId, $"پاسخگویی {userInfo.Result.NickName} به یادداشت شما دربارهٔ {artificat.Name} « {item.Name}", $"برای مشاهدهٔ پاسخ ارائه شده اینجا را ببینید.
" + $"پاسخ داده شده:
" + $"
{note.HtmlContent}

" + $"یادداشت شما:
" + $"
{referenceNote.HtmlContent}
" ); } } return new RServiceResult ( new RUserNoteViewModel() { Id = note.Id, RAppUserId = note.RAppUserId, UserName = userInfo.Result.NickName, RUserImageId = userInfo.Result.RImageId, Modified = note.Modified, NoteType = note.NoteType, HtmlContent = note.HtmlContent, ReferenceNoteId = note.ReferenceNoteId, Status = note.Status, Notes = new RUserNoteViewModel[] { }, DateTime = RUserNoteViewModel.PrepareNoteDateTime(note.DateTime), LastModified = RUserNoteViewModel.PrepareNoteDateTime(note.LastModified) } ); } /// /// Edit Note /// /// /// /// /// public async Task> EditUserNote(Guid noteId, Guid? userId, string noteContents) { RUserNote note = await _context.UserNotes. Where(n => n.Id == noteId). SingleOrDefaultAsync(); if (note == null) { return new RServiceResult(null, "Note not found."); } if (userId != null) //sending null here means user is a moderator { if (userId != note.RAppUserId) { return new RServiceResult(null, "Note only can be edited by its owner"); } } RServiceResult userInfo = await _userService.GetUserInformation(note.RAppUserId); if (!string.IsNullOrEmpty(userInfo.ExceptionString)) return new RServiceResult(null, userInfo.ExceptionString); note.HtmlContent = noteContents; note.LastModified = DateTime.Now; _context.UserNotes.Update(note); await _context.SaveChangesAsync(); return new RServiceResult ( new RUserNoteViewModel() { Id = note.Id, RAppUserId = note.RAppUserId, UserName = userInfo.Result.NickName, RUserImageId = userInfo.Result.RImageId, Modified = note.Modified, NoteType = note.NoteType, HtmlContent = note.HtmlContent, ReferenceNoteId = note.ReferenceNoteId, Status = note.Status, Notes = new RUserNoteViewModel[] { }, DateTime = RUserNoteViewModel.PrepareNoteDateTime(note.DateTime), LastModified = RUserNoteViewModel.PrepareNoteDateTime(note.LastModified) } ); } /// /// delete note /// /// /// sending null here means user is a moderator or the note is being deleted in a recursive delete of referenced notes /// list of notes deleted public async Task> DeleteUserNote(Guid noteId, Guid? userId) { RUserNote note = await _context.UserNotes. Where(n => n.Id == noteId). SingleOrDefaultAsync(); if (note == null) { return new RServiceResult(null, "Note not found."); } if (userId != null) //sending null here means user is a moderator or the note is being deleted in a recursive delete of referenced notes { if (userId != note.RAppUserId) { return new RServiceResult(null, "Note only can be deleted by its owner"); } } List deletedNotesIdSet = new List(); RUserNote[] relatedNotes = await _context.UserNotes.Where(n => n.ReferenceNoteId == noteId).ToArrayAsync(); foreach (RUserNote relatedNote in relatedNotes) { deletedNotesIdSet.Add(relatedNote.Id); RServiceResult refDel = await DeleteUserNote(relatedNote.Id, null); if (!string.IsNullOrEmpty(refDel.ExceptionString)) return new RServiceResult(null, refDel.ExceptionString); } deletedNotesIdSet.Add(noteId); _context.UserNotes.Remove(note); await _context.SaveChangesAsync(); return new RServiceResult(deletedNotesIdSet.ToArray()); } /// /// get artifact private user notes /// /// /// /// public async Task> GetArtifactUserNotes(Guid artifactId, Guid userId) { RUserNote[] notes = await _context.UserNotes. Where(b => b.RArtifactMasterRecordId == artifactId && b.RAppUserId == userId && b.NoteType == RNoteType.Private). OrderBy(n => n.DateTime).ToArrayAsync(); RServiceResult userInfo = await _userService.GetUserInformation(userId); if (!string.IsNullOrEmpty(userInfo.ExceptionString)) return new RServiceResult(null, userInfo.ExceptionString); List res = new List(); foreach (RUserNote note in notes) res.Add ( new RUserNoteViewModel() { Id = note.Id, RAppUserId = note.RAppUserId, UserName = userInfo.Result.NickName, RUserImageId = userInfo.Result.RImageId, Modified = note.Modified, NoteType = note.NoteType, HtmlContent = note.HtmlContent, ReferenceNoteId = note.ReferenceNoteId, Status = note.Status, Notes = new RUserNoteViewModel[] { }, DateTime = RUserNoteViewModel.PrepareNoteDateTime(note.DateTime), LastModified = RUserNoteViewModel.PrepareNoteDateTime(note.LastModified) } ); return new RServiceResult(res.ToArray()); } /// /// recursvie call to build notes tree /// /// /// /// private async Task _GetArtifactPublicNotes(Guid artifactId, Guid? parentNoteId) { RUserNote[] notes = await _context.UserNotes. Where(b => b.RArtifactMasterRecordId == artifactId && b.NoteType == RNoteType.Public && b.ReferenceNoteId == parentNoteId) .Include(n => n.RAppUser) .OrderBy(n => n.DateTime).ToArrayAsync(); List res = new List(); foreach (RUserNote note in notes) { RAppUser appUser = note.RAppUser; var user = new PublicRAppUser() { Id = appUser.Id, Username = appUser.UserName, Email = appUser.Email, FirstName = appUser.FirstName, SurName = appUser.SurName, PhoneNumber = appUser.PhoneNumber, RImageId = appUser.RImageId, Status = appUser.Status, NickName = appUser.NickName, Website = appUser.Website, Bio = appUser.Bio, EmailConfirmed = appUser.EmailConfirmed }; RUserNoteViewModel viewModel = new RUserNoteViewModel() { Id = note.Id, RAppUserId = note.RAppUserId, UserName = user.NickName, RUserImageId = user.RImageId, Modified = note.Modified, NoteType = note.NoteType, HtmlContent = note.HtmlContent, ReferenceNoteId = note.ReferenceNoteId, Status = note.Status, Notes = new RUserNoteViewModel[] { }, DateTime = RUserNoteViewModel.PrepareNoteDateTime(note.DateTime), LastModified = RUserNoteViewModel.PrepareNoteDateTime(note.LastModified) }; viewModel.Notes = await _GetArtifactPublicNotes(artifactId, note.Id); res.Add(viewModel); } return res.ToArray(); } /// /// get artifact public user notes /// /// /// public async Task> GetArtifactPublicNotes(Guid artifactId) { return new RServiceResult(await _GetArtifactPublicNotes(artifactId, null)); } /// /// get item artifact private user notes /// /// /// /// public async Task> GetArtifactItemUserNotes(Guid itemId, Guid userId) { RUserNote[] notes = await _context.UserNotes. Where(b => b.RArtifactItemRecordId == itemId && b.RAppUserId == userId && b.NoteType == RNoteType.Private). OrderBy(n => n.DateTime).ToArrayAsync(); RServiceResult userInfo = await _userService.GetUserInformation(userId); if (!string.IsNullOrEmpty(userInfo.ExceptionString)) return new RServiceResult(null, userInfo.ExceptionString); List res = new List(); foreach (RUserNote note in notes) res.Add ( new RUserNoteViewModel() { Id = note.Id, RAppUserId = note.RAppUserId, UserName = userInfo.Result.NickName, RUserImageId = userInfo.Result.RImageId, Modified = note.Modified, NoteType = note.NoteType, HtmlContent = note.HtmlContent, ReferenceNoteId = note.ReferenceNoteId, Status = note.Status, Notes = new RUserNoteViewModel[] { }, DateTime = RUserNoteViewModel.PrepareNoteDateTime(note.DateTime), LastModified = RUserNoteViewModel.PrepareNoteDateTime(note.LastModified) } ); return new RServiceResult(res.ToArray()); } /// /// recursvie call to build notes tree for iten /// /// /// /// private async Task _GetArtifactItemPublicNotes(Guid itemId, Guid? parentNoteId) { RUserNote[] notes = await _context.UserNotes. Where(b => b.RArtifactItemRecordId == itemId && b.NoteType == RNoteType.Public && b.ReferenceNoteId == parentNoteId) .Include(n => n.RAppUser) .OrderBy(n => n.DateTime).ToArrayAsync(); List res = new List(); foreach (RUserNote note in notes) { RAppUser appUser = note.RAppUser; var user = new PublicRAppUser() { Id = appUser.Id, Username = appUser.UserName, Email = appUser.Email, FirstName = appUser.FirstName, SurName = appUser.SurName, PhoneNumber = appUser.PhoneNumber, RImageId = appUser.RImageId, Status = appUser.Status, NickName = appUser.NickName, Website = appUser.Website, Bio = appUser.Bio, EmailConfirmed = appUser.EmailConfirmed }; RUserNoteViewModel viewModel = new RUserNoteViewModel() { Id = note.Id, RAppUserId = note.RAppUserId, UserName = user.NickName, RUserImageId = user.RImageId, Modified = note.Modified, NoteType = note.NoteType, HtmlContent = note.HtmlContent, ReferenceNoteId = note.ReferenceNoteId, Status = note.Status, Notes = new RUserNoteViewModel[] { }, DateTime = RUserNoteViewModel.PrepareNoteDateTime(note.DateTime), LastModified = RUserNoteViewModel.PrepareNoteDateTime(note.LastModified) }; viewModel.Notes = await _GetArtifactItemPublicNotes(itemId, note.Id); res.Add(viewModel); } return res.ToArray(); } /// /// get item artifact item public user notes /// /// /// public async Task> GetArtifactItemPublicNotes(Guid itemId) { return new RServiceResult(await _GetArtifactItemPublicNotes(itemId, null)); } /// /// Get All USer Notes /// /// /// /// /// /// public async Task> GetUserNotes(Guid userId, PagingParameterModel paging, RNoteType type, PublishStatus[] statusArray) { var source = _context.UserNotes .Include(b => b.RArtifactMasterRecord).ThenInclude(a => a.CoverImage) .Include(b => b.RArtifactItemRecord).ThenInclude(b => b.Images) .Where(b => b.RAppUserId == userId && b.NoteType == type) .OrderByDescending(b => b.DateTime) .AsQueryable(); (PaginationMetadata PagingMeta, RUserNote[] Notes) paginatedResult1 = await QueryablePaginator.Paginate(source, paging); RServiceResult userInfo = await _userService.GetUserInformation(userId); if (!string.IsNullOrEmpty(userInfo.ExceptionString)) return new RServiceResult<(PaginationMetadata PagingMeta, RUserNoteViewModel[] Notes)>((PagingMeta: null, Notes: null), userInfo.ExceptionString); PublicRAppUser user = userInfo.Result; List finalList = new List(); foreach (RUserNote note in paginatedResult1.Notes) { RUserNoteViewModel model = new RUserNoteViewModel() { Id = note.Id, RAppUserId = note.RAppUserId, UserName = user.NickName, RUserImageId = user.RImageId, Modified = note.Modified, NoteType = note.NoteType, HtmlContent = note.HtmlContent, ReferenceNoteId = note.ReferenceNoteId, Status = note.Status, Notes = new RUserNoteViewModel[] { }, DateTime = RUserNoteViewModel.PrepareNoteDateTime(note.DateTime), LastModified = RUserNoteViewModel.PrepareNoteDateTime(note.LastModified) }; if (note.RArtifactMasterRecord != null) { if (!statusArray.Contains(note.RArtifactMasterRecord.Status)) continue; //this may result in paging bugs } if (note.RArtifactItemRecord != null) { RArtifactMasterRecord parent = await _context.Artifacts .Where(a => statusArray.Contains(a.Status) && a.Id == note.RArtifactItemRecord.RArtifactMasterRecordId) .AsNoTracking() .SingleOrDefaultAsync(); if (parent == null) continue; model.RelatedEntityName = note.RArtifactItemRecord.Name; model.RelatedEntityImageId = note.RArtifactItemRecord.Images.First().Id; model.RelatedEntityExternalNormalSizeImageUrl = note.RArtifactItemRecord.Images.First().ExternalNormalSizeImageUrl; model.RelatedEntityFriendlyUrl = parent.FriendlyUrl + "/" + note.RArtifactItemRecord.FriendlyUrl; model.RelatedItemParentName = parent.Name; } if (note.RArtifactMasterRecord != null) { model.RelatedEntityName = note.RArtifactMasterRecord.Name; model.RelatedEntityImageId = note.RArtifactMasterRecord.CoverImage.Id; model.RelatedEntityExternalNormalSizeImageUrl = note.RArtifactMasterRecord.CoverImage.ExternalNormalSizeImageUrl; model.RelatedEntityFriendlyUrl = note.RArtifactMasterRecord.FriendlyUrl; } finalList.Add(model); } return new RServiceResult<(PaginationMetadata PagingMeta, RUserNoteViewModel[] Notes)>((paginatedResult1.PagingMeta, finalList.ToArray())); } /// /// Get All Public Notes /// /// /// public async Task> GetAllPublicNotes(PagingParameterModel paging) { var source = _context.UserNotes .Include(b => b.RArtifactMasterRecord).ThenInclude(a => a.CoverImage) .Include(b => b.RArtifactItemRecord).ThenInclude(b => b.Images) .Include(n => n.RAppUser) .Where(b => b.NoteType == RNoteType.Public) .OrderByDescending(b => b.DateTime) .AsQueryable(); (PaginationMetadata PagingMeta, RUserNote[] Notes) paginatedResult1 = await QueryablePaginator.Paginate(source, paging); List finalList = new List(); foreach (RUserNote note in paginatedResult1.Notes) { RAppUser appUser = note.RAppUser; var user = new PublicRAppUser() { Id = appUser.Id, Username = appUser.UserName, Email = appUser.Email, FirstName = appUser.FirstName, SurName = appUser.SurName, PhoneNumber = appUser.PhoneNumber, RImageId = appUser.RImageId, Status = appUser.Status, NickName = appUser.NickName, Website = appUser.Website, Bio = appUser.Bio, EmailConfirmed = appUser.EmailConfirmed }; RUserNoteViewModel model = new RUserNoteViewModel() { Id = note.Id, RAppUserId = note.RAppUserId, UserName = user.NickName, RUserImageId = user.RImageId, Modified = note.Modified, NoteType = note.NoteType, HtmlContent = note.HtmlContent, ReferenceNoteId = note.ReferenceNoteId, Status = note.Status, Notes = new RUserNoteViewModel[] { }, DateTime = RUserNoteViewModel.PrepareNoteDateTime(note.DateTime), LastModified = RUserNoteViewModel.PrepareNoteDateTime(note.LastModified) }; if (note.RArtifactMasterRecord != null) { if (note.RArtifactMasterRecord.Status != PublishStatus.Published) continue; //this may result in paging bugs } if (note.RArtifactItemRecord != null) { RArtifactMasterRecord parent = await _context.Artifacts .Where(a => a.Status == PublishStatus.Published && a.Id == note.RArtifactItemRecord.RArtifactMasterRecordId) .AsNoTracking() .SingleOrDefaultAsync(); if (parent == null) continue; model.RelatedEntityName = note.RArtifactItemRecord.Name; model.RelatedEntityImageId = note.RArtifactItemRecord.Images.First().Id; model.RelatedEntityExternalNormalSizeImageUrl = note.RArtifactItemRecord.Images.First().ExternalNormalSizeImageUrl; model.RelatedEntityFriendlyUrl = parent.FriendlyUrl + "/" + note.RArtifactItemRecord.FriendlyUrl; model.RelatedItemParentName = parent.Name; } if (note.RArtifactMasterRecord != null) { model.RelatedEntityName = note.RArtifactMasterRecord.Name; model.RelatedEntityImageId = note.RArtifactMasterRecord.CoverImage.Id; model.RelatedEntityFriendlyUrl = note.RArtifactMasterRecord.FriendlyUrl; model.RelatedEntityExternalNormalSizeImageUrl = note.RArtifactMasterRecord.CoverImage.ExternalNormalSizeImageUrl; } finalList.Add(model); } return new RServiceResult<(PaginationMetadata PagingMeta, RUserNoteViewModel[] Notes)>((paginatedResult1.PagingMeta, finalList.ToArray())); } /// /// report a public note /// /// /// /// /// id of report record public async Task> ReportPublicNote(Guid reportUserId, Guid noteId, string reasonText) { try { RUserNoteAbuseReport r = new RUserNoteAbuseReport() { NoteId = noteId, ReporterId = reportUserId, ReasonText = reasonText, }; _context.ReportedUserNotes.Add(r); await _context.SaveChangesAsync(); return new RServiceResult(r.Id); } catch (Exception exp) { return new RServiceResult(Guid.Empty, exp.ToString()); } } /// /// delete a report for abuse in public user notes /// /// /// public async Task> DeclinePublicNoteReport(Guid id) { try { RUserNoteAbuseReport report = await _context.ReportedUserNotes.Where(r => r.Id == id).SingleOrDefaultAsync(); if (report == null) { return new RServiceResult(false); } _context.ReportedUserNotes.Remove(report); await _context.SaveChangesAsync(); return new RServiceResult(true); } catch (Exception exp) { return new RServiceResult(false, exp.ToString()); } } /// /// delete a reported user note (accept the complaint) /// /// /// public async Task> AcceptPublicNoteReport(Guid id) { try { RUserNoteAbuseReport report = await _context.ReportedUserNotes.AsNoTracking().Where(r => r.Id == id).SingleOrDefaultAsync(); if (report == null) { return new RServiceResult(false, "report not found"); } var reasonText = report.ReasonText; RUserNote note = await _context.UserNotes.Where(n => n.Id == report.NoteId).SingleOrDefaultAsync(); if (note == null) { return new RServiceResult(false, "note not found!"); } await _notificationService.PushNotification((Guid)note.RAppUserId, "حذف یادداشت عمومی شما", $"یادداشت عمومی شما به دلیل ناسازگاری با قوانین یادداشت‌گذاری عمومی در گنجینهٔ گنجور و طبق گزارشات دیگر کاربران حذف شده است.{Environment.NewLine}" + $"توجه فرمایید که یادداشتهای عمومی گنجینهٔ گنجور برای بحث در مورد نسخه‌ها در نظر گرفته شده‌اند و جای بحثهای محتوایی بی‌ربط به نسخهٔ خاص می‌تواند در گنجور باشد.{Environment.NewLine}" + $"{reasonText}" + $"این متن یادداشت حذف شدهٔ شماست: {Environment.NewLine}" + $"{note.HtmlContent}", NotificationType.Warning ); _context.UserNotes.Remove(note); await _context.SaveChangesAsync(); return new RServiceResult(true); } catch (Exception exp) { return new RServiceResult(false, exp.ToString()); } } /// /// Get a list of reported notes /// /// /// public async Task> GetReportedPublicNotes(PagingParameterModel paging) { var source = from report in _context.ReportedUserNotes join note in _context.UserNotes.Include(n => n.RAppUser) on report.NoteId equals note.Id select new RUserNoteAbuseReportViewModel() { Id = report.Id, ReasonText = report.ReasonText, Note = new RUserNoteViewModel() { Id = note.Id, RAppUserId = note.RAppUserId, UserName = note.RAppUser.NickName, RUserImageId = note.RAppUser.RImageId, Modified = note.Modified, NoteType = note.NoteType, HtmlContent = note.HtmlContent, ReferenceNoteId = note.ReferenceNoteId, Status = note.Status, Notes = new RUserNoteViewModel[] { }, DateTime = RUserNoteViewModel.PrepareNoteDateTime(note.DateTime), LastModified = RUserNoteViewModel.PrepareNoteDateTime(note.LastModified) } }; (PaginationMetadata PagingMeta, RUserNoteAbuseReportViewModel[] Items) paginatedResult = await QueryablePaginator.Paginate(source, paging); return new RServiceResult<(PaginationMetadata PagingMeta, RUserNoteAbuseReportViewModel[] Items)>(paginatedResult); } /// /// suggest diwan link /// /// /// /// public async Task> SuggestDiwanLink(Guid userId, LinkSuggestion link) { RArtifactMasterRecord artifact = await _context.Artifacts.AsNoTracking().Include(a => a.CoverImage).Where(a => a.FriendlyUrl == link.ArtifactFriendlyUrl).SingleOrDefaultAsync(); DiwanLink alreadySuggest = await _context.DiwanLinks. Where(l => l.DiwanPostId == link.DiwanPostId && l.ArtifactId == artifact.Id && l.ItemId == link.ItemId && l.ReviewResult != ReviewResult.Rejected) .SingleOrDefaultAsync(); if (alreadySuggest != null) return new RServiceResult(null, "این مورد پیشتر پیشنهاد شده است."); DiwanLink suggestion = new DiwanLink() { DiwanPostId = link.DiwanPostId, DiwanTitle = link.DiwanTitle, DiwanUrl = link.DiwanUrl, ArtifactId = artifact.Id, ItemId = link.ItemId, SuggestedById = userId, SuggestionDate = DateTime.Now, ReviewResult = ReviewResult.Awaiting }; _context.DiwanLinks.Add(suggestion); await _context.SaveChangesAsync(); string entityName, entityFriendlyUrl; Guid entityImageId; string externalNormalSizeImageUrl; if (suggestion.ItemId == null) { entityName = artifact.Name; entityFriendlyUrl = $"/items/{artifact.FriendlyUrl}"; entityImageId = artifact.CoverImageId; externalNormalSizeImageUrl = artifact.CoverImage.ExternalNormalSizeImageUrl; } else { RArtifactItemRecord item = await _context.Items.Include(i => i.Images).Where(i => i.Id == suggestion.ItemId).SingleOrDefaultAsync(); entityName = artifact.Name + " » " + item.Name; entityFriendlyUrl = $"/items/{artifact.FriendlyUrl}/{item.FriendlyUrl}"; entityImageId = item.Images.First().Id; externalNormalSizeImageUrl = item.Images.First().ExternalNormalSizeImageUrl; } var moderators = await _userService.GetUsersHavingPermission(RMuseumSecurableItem.ArtifactEntityShortName, RMuseumSecurableItem.ReviewDiwanLinksOperationShortName); if (string.IsNullOrEmpty(moderators.ExceptionString)) //if not, do nothing! { foreach (var moderator in moderators.Result) { if(moderator.Id != userId) { await _notificationService.PushNotification ( (Guid)moderator.Id, "پیشنهاد تصویر گنجینهٔ گنجور مرتبط با شعر", $"تصویری از گنجینهٔ گنجور برای ارتباط با اشعار ثبت شده است. لطفاً بخش بازبینی ارتباطات را بررسی فرمایید.{Environment.NewLine}" + $"توجه فرمایید که اگر کاربر دیگری که دارای مجوز بررسی تصاویر است پیش از شما به آن رسیدگی کرده باشد آن را در صف نخواهید دید.", NotificationType.ActionRequired ); } } } var user = (await _userService.GetUserInformation(userId)).Result; DiwanLinkViewModel viewModel = new DiwanLinkViewModel() { Id = suggestion.Id, DiwanPostId = suggestion.DiwanPostId, DiwanUrl = suggestion.DiwanUrl, DiwanTitle = suggestion.DiwanTitle, EntityName = entityName, EntityFriendlyUrl = entityFriendlyUrl, EntityImageId = entityImageId, ReviewResult = suggestion.ReviewResult, Synchronized = suggestion.Synchronized, SuggestedBy = new PublicRAppUser() { Id = user.Id, Username = user.Username, Email = user.Email, FirstName = user.FirstName, SurName = user.SurName, PhoneNumber = user.PhoneNumber, RImageId = user.RImageId, Status = user.Status, NickName = user.NickName, Website = user.Website, Bio = user.Bio, EmailConfirmed = user.EmailConfirmed }, IsTextOriginalSource = suggestion.IsTextOriginalSource, ExternalNormalSizeImageUrl = externalNormalSizeImageUrl, }; return new RServiceResult(viewModel); } /// /// get Unsynchronized image count /// /// public async Task> GetUnsynchronizedSuggestedLinksCount() { return new RServiceResult ( await _context.DiwanLinks.AsNoTracking() .Include(l => l.SuggestedBy) .Include(l => l.Artifact) .Include(l => l.Item).ThenInclude(i => i.Images) .Where(l => l.ReviewResult == ReviewResult.Approved && !l.Synchronized) .CountAsync() ); } /// /// finds what the method name suggests /// /// /// public async Task> GetNextUnsynchronizedSuggestedLinkWithAlreadySynchedOneForPoem(int skip) { DiwanLink link = await _context.DiwanLinks.AsNoTracking() .Include(l => l.SuggestedBy) .Include(l => l.Artifact).ThenInclude(l => l.CoverImage) .Include(l => l.Item).ThenInclude(i => i.Images) .Where(l => l.ReviewResult == ReviewResult.Approved && !l.Synchronized) .OrderBy(l => l.SuggestionDate) .Skip(skip) .FirstOrDefaultAsync(); if (link == null) return new RServiceResult(null); List result = new List(); result.Add ( new DiwanLinkViewModel() { Id = link.Id, DiwanPostId = link.DiwanPostId, DiwanUrl = link.DiwanUrl, DiwanTitle = link.DiwanTitle, EntityName = link.Item == null ? link.Artifact.Name : link.Artifact.Name + " » " + link.Item.Name, EntityFriendlyUrl = link.Item == null ? $"/items/{link.Artifact.FriendlyUrl}" : $"/items/{link.Artifact.FriendlyUrl}/{link.Item.FriendlyUrl}", EntityImageId = link.Item == null ? link.Artifact.CoverImageId : link.Item.Images.First().Id, ReviewResult = link.ReviewResult, Synchronized = link.Synchronized, SuggestedBy = new PublicRAppUser() { Id = link.SuggestedBy.Id, Username = link.SuggestedBy.UserName, Email = link.SuggestedBy.Email, FirstName = link.SuggestedBy.FirstName, SurName = link.SuggestedBy.SurName, PhoneNumber = link.SuggestedBy.PhoneNumber, RImageId = link.SuggestedBy.RImageId, Status = link.SuggestedBy.Status, NickName = link.SuggestedBy.NickName, Website = link.SuggestedBy.Website, Bio = link.SuggestedBy.Bio, EmailConfirmed = link.SuggestedBy.EmailConfirmed }, IsTextOriginalSource = link.IsTextOriginalSource, ExternalNormalSizeImageUrl = link.Item == null ? link.Artifact.CoverImage.ExternalNormalSizeImageUrl : link.Item.Images.First().ExternalNormalSizeImageUrl, } ); DiwanLink preLink = await _context.DiwanLinks.AsNoTracking() .Include(l => l.SuggestedBy) .Include(l => l.Artifact).ThenInclude(l => l.CoverImage) .Include(l => l.Item).ThenInclude(i => i.Images) .Where(l => l.ReviewResult == ReviewResult.Approved && l.DisplayOnPage && l.DiwanPostId == link.DiwanPostId && l.ArtifactId == link.ArtifactId) .OrderBy(l => l.SuggestionDate) .FirstOrDefaultAsync(); if (preLink != null) { result.Add ( new DiwanLinkViewModel() { Id = preLink.Id, DiwanPostId = preLink.DiwanPostId, DiwanUrl = preLink.DiwanUrl, DiwanTitle = preLink.DiwanTitle, EntityName = preLink.Item == null ? preLink.Artifact.Name : preLink.Artifact.Name + " » " + preLink.Item.Name, EntityFriendlyUrl = preLink.Item == null ? $"/items/{preLink.Artifact.FriendlyUrl}" : $"/items/{preLink.Artifact.FriendlyUrl}/{preLink.Item.FriendlyUrl}", EntityImageId = preLink.Item == null ? preLink.Artifact.CoverImageId : preLink.Item.Images.First().Id, ReviewResult = preLink.ReviewResult, Synchronized = preLink.Synchronized, SuggestedBy = new PublicRAppUser() { Id = preLink.SuggestedBy.Id, Username = preLink.SuggestedBy.UserName, Email = preLink.SuggestedBy.Email, FirstName = preLink.SuggestedBy.FirstName, SurName = preLink.SuggestedBy.SurName, PhoneNumber = preLink.SuggestedBy.PhoneNumber, RImageId = preLink.SuggestedBy.RImageId, Status = preLink.SuggestedBy.Status, NickName = preLink.SuggestedBy.NickName, Website = preLink.SuggestedBy.Website, Bio = preLink.SuggestedBy.Bio, EmailConfirmed = preLink.SuggestedBy.EmailConfirmed }, IsTextOriginalSource = preLink.IsTextOriginalSource, ExternalNormalSizeImageUrl = preLink.Item == null ? preLink.Artifact.CoverImage.ExternalNormalSizeImageUrl : preLink.Item.Images.First().ExternalNormalSizeImageUrl, } ); } return new RServiceResult(result.ToArray()); } /// /// get suggested diwan links /// /// /// /// public async Task> GetSuggestedLinks(ReviewResult status, bool notSynced) { DiwanLink[] links = await _context.DiwanLinks.AsNoTracking() .Include(l => l.SuggestedBy) .Include(l => l.Artifact).ThenInclude(a => a.CoverImage) .Include(l => l.Item).ThenInclude(i => i.Images) .Where(l => l.ReviewResult == status && (notSynced == false || !l.Synchronized)) .OrderBy(l => l.SuggestionDate) .ToArrayAsync(); List result = new List(); foreach (DiwanLink link in links) { result.Add ( new DiwanLinkViewModel() { Id = link.Id, DiwanPostId = link.DiwanPostId, DiwanUrl = link.DiwanUrl, DiwanTitle = link.DiwanTitle, EntityName = link.Item == null ? link.Artifact.Name : link.Artifact.Name + " » " + link.Item.Name, EntityFriendlyUrl = link.Item == null ? $"/items/{link.Artifact.FriendlyUrl}" : $"/items/{link.Artifact.FriendlyUrl}/{link.Item.FriendlyUrl}", EntityImageId = link.Item == null ? link.Artifact.CoverImageId : link.Item.Images.First().Id, ReviewResult = link.ReviewResult, Synchronized = link.Synchronized, SuggestedBy = new PublicRAppUser() { Id = link.SuggestedBy.Id, Username = link.SuggestedBy.UserName, Email = link.SuggestedBy.Email, FirstName = link.SuggestedBy.FirstName, SurName = link.SuggestedBy.SurName, PhoneNumber = link.SuggestedBy.PhoneNumber, RImageId = link.SuggestedBy.RImageId, Status = link.SuggestedBy.Status, NickName = link.SuggestedBy.NickName, Website = link.SuggestedBy.Website, Bio = link.SuggestedBy.Bio, EmailConfirmed = link.SuggestedBy.EmailConfirmed }, IsTextOriginalSource = link.IsTextOriginalSource, ExternalNormalSizeImageUrl = link.Item == null ? link.Artifact.CoverImage.ExternalNormalSizeImageUrl : link.Item.Images.First().ExternalNormalSizeImageUrl, } ); } return new RServiceResult(result.ToArray()); } /// /// Review Suggested Link /// /// /// /// /// public async Task> ReviewSuggestedLink(Guid linkId, Guid userId, ReviewResult result) { DiwanLink link = await _context.DiwanLinks .Include(l => l.Artifact).ThenInclude(a => a.Tags).ThenInclude(t => t.RTag) .Include(l => l.Item).ThenInclude(i => i.Tags).ThenInclude(t => t.RTag) .Where(l => l.Id == linkId) .SingleOrDefaultAsync(); var poem = (await _diwanService.GetPoemById(link.DiwanPostId)).Result;//if it fails here nothing is updated string titleInTOC = poem == null ? "" : poem.FullTitle; if (poem != null && poem.Verses.Length > 0) { link.DiwanTitle = poem.FullTitle; titleInTOC += $" - {poem.Verses[0].Text}"; } link.ReviewResult = result; link.ReviewerId = userId; link.ReviewDate = DateTime.Now; //check to see if any other image from this artifact has been added to this poem: if ( link.ReviewResult == ReviewResult.Approved && !await _context.DiwanLinks.Where(l => l.ArtifactId == link.ArtifactId && l.DiwanPostId == link.DiwanPostId && l.ReviewResult == ReviewResult.Approved && l.DisplayOnPage).AnyAsync() ) { link.DisplayOnPage = true; link.Synchronized = true; await _diwanService.CacheCleanForPageById(link.DiwanPostId); }//if not user must decide through UI for this link _context.DiwanLinks.Update(link); if (link.ReviewResult == ReviewResult.Approved) { var itemInfo = await _context.Items .Include(i => i.Tags) .ThenInclude(t => t.RTag) .Where(i => i.Id == link.ItemId).SingleAsync(); var sourceTag = itemInfo.Tags.Where(t => t.RTag.FriendlyUrl == "source").FirstOrDefault(); if (sourceTag != null) { if (!string.IsNullOrEmpty(sourceTag.ValueSupplement) && (sourceTag.ValueSupplement.IndexOf("http") == 0)) { link.OriginalSourceUrl = sourceTag.ValueSupplement; link.LinkToOriginalSource = false; } } RTagValue tag = await TagHandler.PrepareAttribute(_context, "Diwan Link", link.DiwanTitle, 1); tag.ValueSupplement = link.DiwanUrl; if (link.Item == null) { link.Artifact.Tags.Add(tag); _context.Artifacts.Update(link.Artifact); } else { link.Item.Tags.Add(tag); _context.Items.Update(link.Item); } //add TOC: RTagValue toc = await TagHandler.PrepareAttribute(_context, "Title in TOC", titleInTOC, 1); toc.ValueSupplement = "1";//font size if (link.Item == null) { if (link.Artifact.Tags.Where(t => t.RTag.Name == "Title in TOC" && t.Value == toc.Value).Count() == 0) { toc.Order = 1 + link.Artifact.Tags.Where(t => t.RTag.NameInEnglish == "Title in TOC").Count(); link.Artifact.Tags.Add(toc); _context.Artifacts.Update(link.Artifact); } } else { if (link.Item.Tags.Where(t => t.RTag.Name == "Title in TOC" && t.Value == toc.Value).Count() == 0) { toc.Order = 1 + link.Item.Tags.Where(t => t.RTag.NameInEnglish == "Title in TOC").Count(); link.Item.Tags.Add(toc); _context.Items.Update(link.Item); } } } await _context.SaveChangesAsync(); return new RServiceResult(true); } /// /// remove diwan link /// /// /// /// public async Task> RemoveDiwanLinkAsync(Guid id, bool removeItemLink) { try { DiwanLink link = await _context.DiwanLinks .Where(l => l.Id == id) .SingleAsync(); if(removeItemLink) { DiwanPoem poem = await _context.DiwanPoems.AsNoTracking().Where(p => p.Id == link.DiwanPostId).SingleOrDefaultAsync(); if (poem != null) { var item = await _context.Items.Include(i => i.Tags).Where(i => i.Id == link.ItemId).SingleAsync(); bool itemNeedsUpdate = false; RTag diwanlinkTagType = await _context.Tags.AsNoTracking().Where(a => a.NameInEnglish == "Diwan Link").SingleAsync(); if (item.Tags.Any(t => t.RTagId == diwanlinkTagType.Id && t.ValueSupplement == $"https://ganjoor.net{poem.FullUrl}")) { var tags = item.Tags.Where(t => t.RTagId == diwanlinkTagType.Id && t.ValueSupplement == $"https://ganjoor.net{poem.FullUrl}").ToList(); foreach (var tag in tags) { item.Tags.Remove(tag); } itemNeedsUpdate = true; } RTag diwanTOCTagType = await _context.Tags.AsNoTracking().Where(a => a.NameInEnglish == "Title in TOC").SingleAsync(); if (item.Tags.Any(t => t.RTagId == diwanTOCTagType.Id && t.Value == poem.FullTitle)) { var tags = item.Tags.Where(t => t.RTagId == diwanTOCTagType.Id && t.Value == poem.FullTitle).ToList(); foreach (var tag in tags) { item.Tags.Remove(tag); } itemNeedsUpdate = true; } if (itemNeedsUpdate) { _context.Update(item); } } } _context.Remove(link); await _context.SaveChangesAsync(); return new RServiceResult(true); } catch (Exception exp) { return new RServiceResult(false, exp.ToString()); } } /// /// Temporary api /// /// public async Task> AddTOCForSuggestedLinks() { DiwanLink[] links = await _context.DiwanLinks .Include(l => l.Artifact).ThenInclude(a => a.Tags).ThenInclude(t => t.RTag) .Include(l => l.Item).ThenInclude(i => i.Tags).ThenInclude(t => t.RTag) .Where(l => l.ReviewResult == ReviewResult.Approved) .ToArrayAsync(); List lst = new List(); foreach (DiwanLink link in links) { var poem = (await _diwanService.GetPoemById(link.DiwanPostId)).Result;//if it fails here nothing is updated string titleInTOC = poem.FullTitle; if (poem.Verses.Length > 0) { titleInTOC += $" - {poem.Verses[0].Text}"; } RTagValue tag = await TagHandler.PrepareAttribute(_context, "Title in TOC", titleInTOC, 1); tag.ValueSupplement = "1";//font size if (link.Item == null) { if (link.Artifact.Tags.Where(t => t.RTag.Name == "Title in TOC" && t.Value == tag.Value).Count() == 0) { tag.Order = 1 + link.Artifact.Tags.Where(t => t.RTag.NameInEnglish == "Title in TOC").Count(); link.Artifact.Tags.Add(tag); _context.Artifacts.Update(link.Artifact); } } else { if (link.Item.Tags.Where(t => t.RTag.Name == "Title in TOC" && t.Value == tag.Value).Count() == 0) { tag.Order = 1 + link.Item.Tags.Where(t => t.RTag.NameInEnglish == "Title in TOC").Count(); link.Item.Tags.Add(tag); _context.Items.Update(link.Item); } } } await _context.SaveChangesAsync(); return new RServiceResult(lst.ToArray()); } /// /// Synchronize suggested link /// /// /// /// public async Task> SynchronizeSuggestedLink(Guid linkId, bool displayOnPage) { DiwanLink link = await _context.DiwanLinks .Where(l => l.Id == linkId) .SingleOrDefaultAsync(); link.Synchronized = true; link.DisplayOnPage = displayOnPage; _context.DiwanLinks.Update(link); await _context.SaveChangesAsync(); await _diwanService.CacheCleanForPageById(link.DiwanPostId); return new RServiceResult(true); } /// /// get suggested pinterest links /// /// /// /// public async Task> GetSuggestedPinterestLinks(ReviewResult status, bool notSynced) { PinterestLink[] links = await _context.PinterestLinks .Include(l => l.Artifact) .Include(l => l.Item).ThenInclude(i => i.Images) .Where(l => l.ReviewResult == status && (notSynced == false || !l.Synchronized)) .OrderBy(l => l.SuggestionDate) .ToArrayAsync(); List result = new List(); foreach (PinterestLink link in links) { result.Add ( new PinterestLinkViewModel() { Id = link.Id, DiwanPostId = link.DiwanPostId, DiwanUrl = link.DiwanUrl, DiwanTitle = link.DiwanTitle, AltText = link.AltText, LinkType = link.LinkType, PinterestImageUrl = link.PinterestImageUrl, PinterestUrl = link.PinterestUrl, SuggestionDate = link.SuggestionDate, ReviewerId = link.ReviewerId, ReviewDate = link.ReviewDate, ReviewResult = link.ReviewResult, ReviewDesc = link.ReviewDesc, ArtifactId = link.ArtifactId, ItemId = link.ItemId, Synchronized = link.Synchronized, EntityName = link.Artifact == null ? null : link.Artifact.Name + " » " + link.Item.Name, EntityFriendlyUrl = link.Artifact == null ? null : $"/items/{link.Artifact.FriendlyUrl}/{link.Item.FriendlyUrl}", EntityImageId = link.Item == null ? Guid.Empty : link.Item.Images.First().Id } ); } return new RServiceResult(result.ToArray()); } /// /// suggest pinterest link /// /// /// /// public async Task> SuggestPinterestLink(Guid userId, PinterestSuggestion suggestion) { PinterestLink existingLink = await _context.PinterestLinks.Where ( p => p.DiwanPostId == suggestion.DiwanPostId && p.PinterestUrl == suggestion.PinterestUrl && (p.ReviewResult == ReviewResult.Approved || p.ReviewResult == ReviewResult.Awaiting) ).FirstOrDefaultAsync(); if (existingLink != null) return new RServiceResult(null, "این مورد پیشتر پیشنهاد شده است."); PinterestLink link = new PinterestLink() { DiwanPostId = suggestion.DiwanPostId, DiwanTitle = suggestion.DiwanTitle, DiwanUrl = suggestion.DiwanUrl, AltText = suggestion.AltText, LinkType = suggestion.LinkType, PinterestUrl = suggestion.PinterestUrl, PinterestImageUrl = suggestion.PinterestImageUrl, ReviewResult = ReviewResult.Awaiting, SuggestionDate = DateTime.Now, SuggestedById = userId, Synchronized = false, MatchPercent = 0, }; _context.PinterestLinks.Add(link); await _context.SaveChangesAsync(); var moderators = await _userService.GetUsersHavingPermission(RMuseumSecurableItem.ArtifactEntityShortName, RMuseumSecurableItem.ReviewDiwanLinksOperationShortName); if (string.IsNullOrEmpty(moderators.ExceptionString)) //if not, do nothing! { foreach (var moderator in moderators.Result) { await _notificationService.PushNotification ( (Guid)moderator.Id, "پیشنهاد تصویر خارجی مرتبط با شعر", $"تصویری خارج از گنجور برای ارتباط با اشعار ثبت شده است. لطفاً بخش بازبینی تصاویر را بررسی فرمایید.{Environment.NewLine}" + $"توجه فرمایید که اگر کاربر دیگری که دارای مجوز بررسی تصاویر است پیش از شما به آن رسیدگی کرده باشد آن را در صف نخواهید دید.", NotificationType.ActionRequired ); } } return new RServiceResult ( new PinterestLinkViewModel() { Id = link.Id, DiwanPostId = link.DiwanPostId, DiwanUrl = link.DiwanUrl, DiwanTitle = link.DiwanTitle, AltText = link.AltText, LinkType = link.LinkType, PinterestImageUrl = link.PinterestImageUrl, PinterestUrl = link.PinterestUrl, SuggestionDate = link.SuggestionDate, ReviewerId = link.ReviewerId, ReviewDate = link.ReviewDate, ReviewResult = link.ReviewResult, ReviewDesc = link.ReviewDesc, ArtifactId = link.ArtifactId, ItemId = link.ItemId, Synchronized = link.Synchronized, EntityName = null, EntityFriendlyUrl = null, EntityImageId = Guid.Empty } ); } /// /// Review Suggested Pinterest Link /// /// /// /// /// /// /// /// public async Task> ReviewSuggestedPinterestLink(Guid linkId, Guid userId, string altText, ReviewResult result, string reviewDesc, string imageUrl) { try { PinterestLink link = await _context.PinterestLinks .Where(l => l.Id == linkId) .SingleOrDefaultAsync(); link.ReviewResult = result; link.ReviewerId = userId; link.ReviewDate = DateTime.Now; if (!string.IsNullOrEmpty(altText)) link.AltText = altText; if (!string.IsNullOrEmpty(imageUrl)) link.PinterestImageUrl = imageUrl; link.ReviewDesc = reviewDesc; if (link.ReviewResult == ReviewResult.Approved) { PinterestLink relatedArtifactLink = await _context.PinterestLinks .Where(p => p.PinterestUrl == link.PinterestUrl && p.PinterestImageUrl == link.PinterestImageUrl && link.Id != p.Id && p.ReviewResult == ReviewResult.Approved) .FirstOrDefaultAsync(); string titleInToc = link.AltText; if (titleInToc.IndexOfAny(new char[] { '\n', '\r' }) != -1) { string[] lines = titleInToc.Replace('\n', '\r').Split('\r', StringSplitOptions.RemoveEmptyEntries); titleInToc = lines[0]; } if (relatedArtifactLink != null) { link.ArtifactId = relatedArtifactLink.ArtifactId; link.ItemId = relatedArtifactLink.ItemId; //TODO: append tags to this existing artifact and its items } else { using (var client = new HttpClient()) { var imageResult = await client.GetAsync(link.PinterestImageUrl); int _ImportRetryCount = 5; int _ImportRetryInitialSleep = 500; int retryCount = 0; while (retryCount < _ImportRetryCount && !imageResult.IsSuccessStatusCode && imageResult.StatusCode == HttpStatusCode.ServiceUnavailable) { imageResult.Dispose(); Thread.Sleep(_ImportRetryInitialSleep * (retryCount + 1)); imageResult = await client.GetAsync(link.PinterestImageUrl); retryCount++; } if (imageResult.IsSuccessStatusCode) { using (Stream imageStream = await imageResult.Content.ReadAsStreamAsync()) { string friendlyUrl = Guid.NewGuid().ToString(); string fileName = friendlyUrl + ".jpg"; while ((await _context.PictureFiles.Where(p => p.OriginalFileName == fileName).FirstOrDefaultAsync()) != null) { fileName = Guid.NewGuid() + "-" + friendlyUrl + ".jpg"; } RServiceResult picture = await _pictureFileService.Add(link.DiwanTitle, link.AltText, 1, null, link.PinterestUrl, imageStream, fileName, "Pinterest"); if (picture.Result == null) { return new RServiceResult(false, $"_pictureFileService.Add : {picture.ExceptionString}"); } if (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(picture.Result, imageSizeString).Result; if (imageSizeString == "norm") { picture.Result.ExternalNormalSizeImageUrl = $"{Configuration.GetSection("ExternalFTPServer")["RootUrl"]}/Pinterest/{imageSizeString}/{Path.GetFileName(localFilePath)}"; } var remoteFilePath = $"{Configuration.GetSection("ExternalFTPServer")["RootPath"]}/images/Pinterest/{imageSizeString}/{Path.GetFileName(localFilePath)}"; await ftpClient.UploadFile(localFilePath, remoteFilePath); } await ftpClient.Disconnect(); } if (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(picture.Result, imageSizeString).Result; if (imageSizeString == "norm") { picture.Result.ExternalNormalSizeImageUrl = $"{Configuration.GetSection("BackupFTPServer")["RootUrl"]}/Pinterest/{imageSizeString}/{Path.GetFileName(localFilePath)}"; } var remoteFilePath = $"{Configuration.GetSection("BackupFTPServer")["RootPath"]}/images/Pinterest/{imageSizeString}/{Path.GetFileName(localFilePath)}"; await ftpClient.UploadFile(localFilePath, remoteFilePath); } await ftpClient.Disconnect(); } RArtifactMasterRecord book = new RArtifactMasterRecord(titleInToc, titleInToc) { Status = PublishStatus.Published, DateTime = DateTime.Now, LastModified = DateTime.Now, CoverItemIndex = 0, FriendlyUrl = friendlyUrl }; List meta = new List(); RTagValue tag = await TagHandler.PrepareAttribute(_context, "Title", titleInToc, 1); meta.Add(tag); tag = await TagHandler.PrepareAttribute(_context, "Description", link.AltText, 1); meta.Add(tag); tag = await TagHandler.PrepareAttribute(_context, "Source", link.LinkType == LinkType.Pinterest ? "Pinterest" : link.LinkType == LinkType.Instagram ? "Instagram" : link.PinterestUrl, 1); tag.ValueSupplement = link.PinterestUrl; meta.Add(tag); tag = await TagHandler.PrepareAttribute(_context, "Diwan Link", link.DiwanTitle, 1); tag.ValueSupplement = link.DiwanUrl; meta.Add(tag); tag = await TagHandler.PrepareAttribute(_context, "Title in TOC", titleInToc, 1); tag.ValueSupplement = "1";//font size meta.Add(tag); book.Tags = meta.ToArray(); List pages = new List(); int order = 1; RArtifactItemRecord page = new RArtifactItemRecord() { Name = titleInToc, NameInEnglish = titleInToc, Description = link.AltText, DescriptionInEnglish = link.AltText, Order = order, FriendlyUrl = $"p{$"{order}".PadLeft(4, '0')}", LastModified = DateTime.Now }; page.Images = new RPictureFile[] { picture.Result }; page.CoverImageIndex = 0; page.Tags = meta.ToArray(); book.CoverImage = RPictureFile.Duplicate(picture.Result); pages.Add(page); book.Items = pages.ToArray(); book.ItemCount = pages.Count; await _context.Artifacts.AddAsync(book); await _context.SaveChangesAsync(); link.ArtifactId = book.Id; link.ItemId = (await _context.Items.Where(i => i.RArtifactMasterRecordId == link.ArtifactId).FirstOrDefaultAsync()).Id; } } else { return new RServiceResult(false, $"Http result is not ok (url {link.PinterestImageUrl}"); } } } } _context.PinterestLinks.Update(link); await _context.SaveChangesAsync(); return new RServiceResult(true); } catch (Exception exp) { return new RServiceResult(false, exp.ToString()); } } /// /// remove pinterest link /// /// /// public async Task> RemovePinterestLinkAsync(Guid id) { try { PinterestLink link = await _context.PinterestLinks .Where(l => l.Id == id) .SingleAsync(); _context.Remove(link); await _context.SaveChangesAsync(); 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; } /// /// Synchronize suggested pinterest link /// /// /// public async Task> SynchronizeSuggestedPinterestLink(Guid linkId) { PinterestLink link = await _context.PinterestLinks .Where(l => l.Id == linkId) .SingleOrDefaultAsync(); link.Synchronized = true; _context.PinterestLinks.Update(link); await _context.SaveChangesAsync(); return new RServiceResult(true); } /// /// Search Artifacts /// /// /// /// public async Task> SearchArtifacts(PagingParameterModel paging, string term) { term = term.Trim().ApplyCorrectYeKe(); if (string.IsNullOrEmpty(term)) { return new RServiceResult<(PaginationMetadata PagingMeta, RArtifactMasterRecord[] Items)>((null, null), "خطای جستجوی عبارت خالی"); } term = term.Replace("‌", " ");//replace zwnj with space string searchConditions; if (term.IndexOf('"') == 0 && term.LastIndexOf('"') == (term.Length - 1)) { searchConditions = term.Replace("\"", "").Replace("'", ""); searchConditions = $"\"{searchConditions}\""; } else { string[] words = term.Replace("\"", "").Replace("'", "").Split(' ', StringSplitOptions.RemoveEmptyEntries); searchConditions = ""; string emptyOrAnd = ""; foreach (string word in words) { searchConditions += $" {emptyOrAnd} \"*{word}*\" "; emptyOrAnd = " AND "; } } /* CREATE FULLTEXT CATALOG RArtifactMasterRecord GO CREATE FULLTEXT INDEX ON [dbo].[Artifacts]( [Name] LANGUAGE 'English', [NameInEnglish] LANGUAGE 'English', [Description] LANGUAGE 'English', [DescriptionInEnglish] LANGUAGE 'English' ) KEY INDEX [PK_Artifacts]ON ([RArtifactMasterRecord], FILEGROUP [PRIMARY]) WITH (CHANGE_TRACKING = AUTO, STOPLIST = SYSTEM) GO CREATE FULLTEXT INDEX ON [dbo].[Items]( [Name] LANGUAGE 'English', [NameInEnglish] LANGUAGE 'English', [Description] LANGUAGE 'English', [DescriptionInEnglish] LANGUAGE 'English' ) KEY INDEX [PK_Items]ON ([RArtifactMasterRecord], FILEGROUP [PRIMARY]) WITH (CHANGE_TRACKING = AUTO, STOPLIST = SYSTEM) GO CREATE FULLTEXT INDEX ON [dbo].[TagValues]( [Value] LANGUAGE 'English', [ValueInEnglish] LANGUAGE 'English' ) KEY INDEX [PK_TagValues]ON ([RArtifactMasterRecord], FILEGROUP [PRIMARY]) WITH (CHANGE_TRACKING = AUTO, STOPLIST = SYSTEM) */ var source = _context.Artifacts.AsNoTracking().Include(a => a.Tags).Include(a => a.CoverImage) .Where(p => p.Status == PublishStatus.Published && ( EF.Functions.Contains(p.Name, searchConditions) || EF.Functions.Contains(p.NameInEnglish, searchConditions) || EF.Functions.Contains(p.Description, searchConditions) || EF.Functions.Contains(p.DescriptionInEnglish, searchConditions) || p.Tags.Where(t => EF.Functions.Contains(t.Value, searchConditions) || EF.Functions.Contains(t.ValueInEnglish, searchConditions)).Any() ) ).OrderBy(i => i.Id); (PaginationMetadata PagingMeta, RArtifactMasterRecord[] Items) paginatedResult = await QueryablePaginator.Paginate(source, paging); return new RServiceResult<(PaginationMetadata PagingMeta, RArtifactMasterRecord[] Items)>(paginatedResult); } /// /// search artifact items /// /// /// /// public async Task> SearchArtifactItems(PagingParameterModel paging, string term) { term = term.Trim().ApplyCorrectYeKe(); if (string.IsNullOrEmpty(term)) { return new RServiceResult<(PaginationMetadata PagingMeta, RArtifactItemRecordViewModel[] Items)>((null, null), "خطای جستجوی عبارت خالی"); } term = term.Replace("‌", " ");//replace zwnj with space string searchConditions; if (term.IndexOf('"') == 0 && term.LastIndexOf('"') == (term.Length - 1)) { searchConditions = term.Replace("\"", "").Replace("'", ""); searchConditions = $"\"{searchConditions}\""; } else { string[] words = term.Replace("\"", "").Replace("'", "").Split(' ', StringSplitOptions.RemoveEmptyEntries); searchConditions = ""; string emptyOrAnd = ""; foreach (string word in words) { searchConditions += $" {emptyOrAnd} \"*{word}*\" "; emptyOrAnd = " AND "; } } var source = _context.Items.AsNoTracking().Include(a => a.Tags).Include(a => a.Images) .Where(p => p.Images.Count > 0 && ( EF.Functions.Contains(p.Name, searchConditions) || EF.Functions.Contains(p.NameInEnglish, searchConditions) || EF.Functions.Contains(p.Description, searchConditions) || EF.Functions.Contains(p.DescriptionInEnglish, searchConditions) || p.Tags.Where(t => EF.Functions.Contains(t.Value, searchConditions) || EF.Functions.Contains(t.ValueInEnglish, searchConditions)).Any() ) ).OrderBy(i => i.Id); (PaginationMetadata PagingMeta, RArtifactItemRecord[] Items) paginatedResult = await QueryablePaginator.Paginate(source, paging); List viewModels = new List(); foreach (var item in paginatedResult.Items) { RArtifactItemRecordViewModel model = new RArtifactItemRecordViewModel(); if (!item.Images.Any())//?! this sometimes happen! { item.Images = (await _context.Items.AsNoTracking().Include(i => i.Images).Where(i => i.Id == item.Id).SingleAsync()).Images; } model.Item = item; model.ParentFriendlyUrl = (await _context.Artifacts.AsNoTracking().Where(a => a.Id == item.RArtifactMasterRecordId).SingleAsync()).FriendlyUrl; viewModels.Add(model); } return new RServiceResult<(PaginationMetadata PagingMeta, RArtifactItemRecordViewModel[] Items)>((paginatedResult.PagingMeta, viewModels.ToArray())); } /// /// add item to an artifact /// /// /// /// /// public async Task> AddItemToArtifactAsync(string artifactFriendlyUrl, IFormFile file, Stream jpegStream) { try { RArtifactMasterRecord book = await _context.Artifacts.Where(b => b.FriendlyUrl == artifactFriendlyUrl).SingleAsync(); RArtifactItemRecord firstItem = await _context.Items.AsNoTracking().Where(i => i.RArtifactMasterRecordId == book.Id).FirstOrDefaultAsync(); int zeroPads = 4; if (firstItem != null) { zeroPads = firstItem.FriendlyUrl.Length - 1; //removing p at the start } int order = 1 + await _context.Items.Where(i => i.RArtifactMasterRecordId == book.Id).CountAsync(); RArtifactItemRecord page = new RArtifactItemRecord() { Name = $"تصویر {order}", NameInEnglish = $"Image {order} of {book.NameInEnglish}", Description = "", DescriptionInEnglish = "", Order = order, FriendlyUrl = $"p{$"{order}".PadLeft(zeroPads, '0')}", LastModified = DateTime.Now, RArtifactMasterRecordId = book.Id, }; RServiceResult picture = await _pictureFileService.Add(page.Name, page.Description, 1, file, "", jpegStream, $"{order}".PadLeft(zeroPads, '0') + ".jpg", book.FriendlyUrl); if (picture.Result == null) { return new RServiceResult(null, ($"_pictureFileService.Add : {picture.ExceptionString}")); } page.Images = [picture.Result]; page.CoverImageIndex = 0; _context.Add(page); await _context.SaveChangesAsync(); book.ItemCount = order; _context.Update(book); await _context.SaveChangesAsync(); var resUpload = await _UploadArtifactPageToExternalServer(page, _context, book.FriendlyUrl, false); if (resUpload.Result != true) { return new RServiceResult(null, resUpload.ExceptionString); } return new RServiceResult(page); } catch (Exception exp) { return new RServiceResult(null, exp.ToString()); } } /// /// Database Contetxt /// protected readonly RMuseumDbContext _context; /// /// Picture File Service /// protected readonly IPictureFileService _pictureFileService; /// /// Configuration /// protected IConfiguration Configuration { get; } /// /// Background Task Queue Instance /// protected readonly IBackgroundTaskQueue _backgroundTaskQueue; /// /// User Service /// protected readonly IAppUserService _userService; /// /// Messaging service /// protected readonly IRNotificationService _notificationService; /// /// Diwan Service /// private readonly IDiwanService _diwanService; /// /// constructor /// /// /// /// /// /// /// /// public ArtifactService(RMuseumDbContext context, IConfiguration configuration, IPictureFileService pictureFileService, IBackgroundTaskQueue backgroundTaskQueue, IAppUserService userService, IRNotificationService notificationService, IDiwanService diwanService) { _context = context; _pictureFileService = pictureFileService; Configuration = configuration; _backgroundTaskQueue = backgroundTaskQueue; _userService = userService; _notificationService = notificationService; _diwanService = diwanService; } } }