From f8a2b8caefbc61ef3f20c63cb67cb60deb266137 Mon Sep 17 00:00:00 2001 From: MaksimChegulov Date: Wed, 31 Aug 2022 13:02:22 +0300 Subject: [PATCH 001/110] Files: fix cast --- products/ASC.Files/Service/Core/FilesModule.cs | 12 ++++++------ products/ASC.Files/Service/Core/FoldersModule.cs | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/products/ASC.Files/Service/Core/FilesModule.cs b/products/ASC.Files/Service/Core/FilesModule.cs index 6f3863c17f..ae4c074131 100644 --- a/products/ASC.Files/Service/Core/FilesModule.cs +++ b/products/ASC.Files/Service/Core/FilesModule.cs @@ -31,7 +31,7 @@ namespace ASC.Files.Service.Core; public class FilesModule : FeedModule { public override Guid ProductID => WebItemManager.DocumentsProductID; - public override string Name => Feed.Constants.FilesModule; + public override string Name => Constants.FilesModule; public override string Product => "documents"; protected override string DbId => "files"; @@ -67,9 +67,9 @@ public class FilesModule : FeedModule return false; } - var tuple = ((File, SmallShareRecord))data; - var file = tuple.Item1; - var shareRecord = tuple.Item2; + var fileWithShare = (FileWithShare)data; + var file = fileWithShare.File; + var shareRecord = fileWithShare.ShareRecord; bool targetCond; if (feed.Target != null) @@ -105,9 +105,9 @@ public class FilesModule : FeedModule var feed1 = feed.Select(r => { - var tuple = ((File, SmallShareRecord))r.Item2; + var fileWithShare = (FileWithShare)r.Item2; - return new Tuple, SmallShareRecord>(r.Item1, tuple.Item1, tuple.Item2); + return new Tuple, SmallShareRecord>(r.Item1, fileWithShare.File, fileWithShare.ShareRecord); }) .ToList(); diff --git a/products/ASC.Files/Service/Core/FoldersModule.cs b/products/ASC.Files/Service/Core/FoldersModule.cs index 9802c5fe62..6a14f623b4 100644 --- a/products/ASC.Files/Service/Core/FoldersModule.cs +++ b/products/ASC.Files/Service/Core/FoldersModule.cs @@ -31,9 +31,9 @@ namespace ASC.Files.Service.Core; public class FoldersModule : FeedModule { public override Guid ProductID => WebItemManager.DocumentsProductID; - public override string Name => Feed.Constants.FoldersModule; + public override string Name => Constants.FoldersModule; public override string Product => "documents"; - protected override string DbId => Feed.Constants.FilesDbId; + protected override string DbId => Constants.FilesDbId; private const string FolderItem = "folder"; private const string SharedFolderItem = "sharedFolder"; @@ -65,9 +65,9 @@ public class FoldersModule : FeedModule return false; } - var tuple = (Tuple, SmallShareRecord>)data; - var folder = tuple.Item1; - var shareRecord = tuple.Item2; + var folderWithShare = (FolderWithShare)data; + var folder = folderWithShare.Folder; + var shareRecord = folderWithShare.ShareRecord; bool targetCond; if (feed.Target != null) From a48b836633203c26915d1444d903e77ccfd85eae Mon Sep 17 00:00:00 2001 From: MaksimChegulov Date: Wed, 31 Aug 2022 16:08:14 +0300 Subject: [PATCH 002/110] Feed: added rooms module, refactor --- common/ASC.Feed/Core/Constants.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/common/ASC.Feed/Core/Constants.cs b/common/ASC.Feed/Core/Constants.cs index 493921042e..6806c9a29d 100644 --- a/common/ASC.Feed/Core/Constants.cs +++ b/common/ASC.Feed/Core/Constants.cs @@ -51,6 +51,13 @@ public static class Constants public const string CasesModule = "cases"; public const string FilesModule = "files"; public const string FoldersModule = "folders"; + public const string RoomsModule = "rooms"; + + #endregion + + #region Products + + public const string Documents = "documents"; #endregion } From 8110048220ff53c24df873f2fb5b798894f42c38 Mon Sep 17 00:00:00 2001 From: MaksimChegulov Date: Wed, 31 Aug 2022 16:10:10 +0300 Subject: [PATCH 003/110] Files: added rooms feeds, refactor, fix --- .../Core/Core/Dao/Interfaces/IFolderDao.cs | 2 +- .../Core/Core/Dao/TeamlabDao/FolderDao.cs | 27 ++++++++++++++++--- .../Core/Thirdparty/IThirdPartyProviderDao.cs | 5 ++++ 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/products/ASC.Files/Core/Core/Dao/Interfaces/IFolderDao.cs b/products/ASC.Files/Core/Core/Dao/Interfaces/IFolderDao.cs index 7f4c0b3d8c..83fefd3678 100644 --- a/products/ASC.Files/Core/Core/Dao/Interfaces/IFolderDao.cs +++ b/products/ASC.Files/Core/Core/Dao/Interfaces/IFolderDao.cs @@ -341,7 +341,7 @@ public interface IFolderDao /// /// Task> GetBunchObjectIDsAsync(List folderIDs); - + IAsyncEnumerable GetFeedsForRoomsAsync(int tenant, DateTime from, DateTime to); IAsyncEnumerable GetFeedsForFoldersAsync(int tenant, DateTime from, DateTime to); IAsyncEnumerable GetTenantsWithFeedsForFoldersAsync(DateTime fromTime); diff --git a/products/ASC.Files/Core/Core/Dao/TeamlabDao/FolderDao.cs b/products/ASC.Files/Core/Core/Dao/TeamlabDao/FolderDao.cs index 20e4ca543d..e749fad498 100644 --- a/products/ASC.Files/Core/Core/Dao/TeamlabDao/FolderDao.cs +++ b/products/ASC.Files/Core/Core/Dao/TeamlabDao/FolderDao.cs @@ -1349,13 +1349,34 @@ internal class FolderDao : AbstractDao, IFolderDao .ToDictionaryAsync(r => r.LeftNode, r => r.RightNode); } + public async IAsyncEnumerable GetFeedsForRoomsAsync(int tenant, DateTime from, DateTime to) + { + var roomTypes = new List { FolderType.CustomRoom, FolderType.ReviewRoom, FolderType.FillingFormsRoom, FolderType.EditingRoom, FolderType.ReadOnlyRoom }; + Expression> filter = f => roomTypes.Contains(f.FolderType); + + await foreach (var e in GetFeedsInternalAsync(tenant, from, to, filter)) + { + yield return e; + } + } + public async IAsyncEnumerable GetFeedsForFoldersAsync(int tenant, DateTime from, DateTime to) + { + Expression> filter = f => f.FolderType == FolderType.DEFAULT; + + await foreach (var e in GetFeedsInternalAsync(tenant, from, to, filter)) + { + yield return e; + } + } + + public async IAsyncEnumerable GetFeedsInternalAsync(int tenant, DateTime from, DateTime to, Expression> filter) { using var filesDbContext = _dbContextFactory.CreateDbContext(); var q1 = filesDbContext.Folders .Where(r => r.TenantId == tenant) - .Where(r => r.FolderType == FolderType.DEFAULT) + .Where(filter) .Where(r => r.CreateOn >= from && r.ModifiedOn <= to); var q2 = FromQuery(filesDbContext, q1) @@ -1363,7 +1384,7 @@ internal class FolderDao : AbstractDao, IFolderDao var q3 = filesDbContext.Folders .Where(r => r.TenantId == tenant) - .Where(r => r.FolderType == FolderType.DEFAULT); + .Where(filter); var q4 = FromQuery(filesDbContext, q3) .Join(filesDbContext.Security.DefaultIfEmpty(), r => r.Folder.Id.ToString(), s => s.EntryId, (f, s) => new DbFolderQueryWithSecurity { DbFolderQuery = f, Security = s }) @@ -1387,7 +1408,7 @@ internal class FolderDao : AbstractDao, IFolderDao { using var filesDbContext = _dbContextFactory.CreateDbContext(); - var q1 = filesDbContext.Files + var q1 = filesDbContext.Folders .Where(r => r.ModifiedOn > fromTime) .GroupBy(r => r.TenantId) .Where(r => r.Any()) diff --git a/products/ASC.Files/Core/Core/Thirdparty/IThirdPartyProviderDao.cs b/products/ASC.Files/Core/Core/Thirdparty/IThirdPartyProviderDao.cs index 30ae1413a7..47cc788d1e 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/IThirdPartyProviderDao.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/IThirdPartyProviderDao.cs @@ -214,6 +214,11 @@ internal abstract class ThirdPartyProviderDao return null; } + public IAsyncEnumerable GetFeedsForRoomsAsync(int tenant, DateTime from, DateTime to) + { + throw new NotImplementedException(); + } + public IAsyncEnumerable GetFeedsForFoldersAsync(int tenant, DateTime from, DateTime to) { throw new NotImplementedException(); From 7a7ae03b0ec196b2c68af8b48809d07d9d3e5859 Mon Sep 17 00:00:00 2001 From: MaksimChegulov Date: Wed, 31 Aug 2022 16:10:20 +0300 Subject: [PATCH 004/110] Files: fix security --- .../ASC.Files/Core/Core/Security/FileSecurity.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/products/ASC.Files/Core/Core/Security/FileSecurity.cs b/products/ASC.Files/Core/Core/Security/FileSecurity.cs index bbd62afd95..a851b9e62c 100644 --- a/products/ASC.Files/Core/Core/Security/FileSecurity.cs +++ b/products/ASC.Files/Core/Core/Security/FileSecurity.cs @@ -54,6 +54,7 @@ public class FileSecurity : IFileSecurity public FileShare DefaultProjectsShare => FileShare.ReadWrite; public FileShare DefaultCommonShare => FileShare.Read; public FileShare DefaultPrivacyShare => FileShare.Restrict; + public FileShare DefaultVirtualRoomsShare => FileShare.Restrict; private readonly UserManager _userManager; private readonly TenantManager _tenantManager; @@ -640,6 +641,16 @@ public class FileSecurity : IFileSecurity // all can read templates folder return true; } + + if (action == FilesSecurityActions.Read && folder.FolderType == FolderType.VirtualRooms) + { + return true; + } + + if (action == FilesSecurityActions.Read && folder.FolderType == FolderType.Archive) + { + return true; + } } if (e.RootFolderType == FolderType.COMMON && isAdmin) @@ -701,6 +712,8 @@ public class FileSecurity : IFileSecurity var defaultShare = userId == FileConstant.ShareLinkId ? FileShare.Restrict + : e.RootFolderType == FolderType.VirtualRooms + ? DefaultVirtualRoomsShare : e.RootFolderType == FolderType.USER ? DefaultMyShare : e.RootFolderType == FolderType.Privacy From e811c0e4968e1dcd923b8b948b9c16a5fadb61fd Mon Sep 17 00:00:00 2001 From: MaksimChegulov Date: Wed, 31 Aug 2022 16:10:42 +0300 Subject: [PATCH 005/110] Files.Service: refactor --- products/ASC.Files/Service/Core/FilesModule.cs | 4 ++-- products/ASC.Files/Service/Core/FoldersModule.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/products/ASC.Files/Service/Core/FilesModule.cs b/products/ASC.Files/Service/Core/FilesModule.cs index ae4c074131..ed2dd78a1a 100644 --- a/products/ASC.Files/Service/Core/FilesModule.cs +++ b/products/ASC.Files/Service/Core/FilesModule.cs @@ -32,8 +32,8 @@ public class FilesModule : FeedModule { public override Guid ProductID => WebItemManager.DocumentsProductID; public override string Name => Constants.FilesModule; - public override string Product => "documents"; - protected override string DbId => "files"; + public override string Product => Constants.Documents; + protected override string DbId => Constants.FilesDbId; private const string FileItem = "file"; private const string SharedFileItem = "sharedFile"; diff --git a/products/ASC.Files/Service/Core/FoldersModule.cs b/products/ASC.Files/Service/Core/FoldersModule.cs index 6a14f623b4..520a453005 100644 --- a/products/ASC.Files/Service/Core/FoldersModule.cs +++ b/products/ASC.Files/Service/Core/FoldersModule.cs @@ -32,7 +32,7 @@ public class FoldersModule : FeedModule { public override Guid ProductID => WebItemManager.DocumentsProductID; public override string Name => Constants.FoldersModule; - public override string Product => "documents"; + public override string Product => Constants.Documents; protected override string DbId => Constants.FilesDbId; private const string FolderItem = "folder"; From c35d51bd8ebcc9ee4390f79398e0ca8ab486b47f Mon Sep 17 00:00:00 2001 From: MaksimChegulov Date: Wed, 31 Aug 2022 16:11:51 +0300 Subject: [PATCH 006/110] Files.Service: added rooms module --- .../ASC.Files/Service/Core/RoomsModule.cs | 156 ++++++++++++++++++ products/ASC.Files/Service/feed.json | 9 + 2 files changed, 165 insertions(+) create mode 100644 products/ASC.Files/Service/Core/RoomsModule.cs diff --git a/products/ASC.Files/Service/Core/RoomsModule.cs b/products/ASC.Files/Service/Core/RoomsModule.cs new file mode 100644 index 0000000000..3a8c725cb3 --- /dev/null +++ b/products/ASC.Files/Service/Core/RoomsModule.cs @@ -0,0 +1,156 @@ +// (c) Copyright Ascensio System SIA 2010-2022 +// +// This program is a free software product. +// You can redistribute it and/or modify it under the terms +// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software +// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended +// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of +// any third-party rights. +// +// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see +// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html +// +// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021. +// +// The interactive user interfaces in modified source and object code versions of the Program must +// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3. +// +// Pursuant to Section 7(b) of the License you must retain the original Product logo when +// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under +// trademark law for use of our trademarks. +// +// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing +// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 +// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode + +using FeedModule = ASC.Feed.Aggregator.Modules.FeedModule; + +namespace ASC.Files.Service.Core; + +public class RoomsModule : FeedModule +{ + private readonly FilesLinkUtility _filesLinkUtility; + private readonly IFolderDao _folderDao; + private readonly UserManager _userManager; + private readonly FileSecurity _fileSecurity; + + private const string RoomItem = "room"; + private const string SharedRoomItem = "sharedRoom"; + + public RoomsModule( + TenantManager tenantManager, + UserManager userManager, + WebItemSecurity webItemSecurity, + FilesLinkUtility filesLinkUtility, + FileSecurity fileSecurity, + IDaoFactory daoFactory) + : base(tenantManager, webItemSecurity) + { + _userManager = userManager; + _filesLinkUtility = filesLinkUtility; + _fileSecurity = fileSecurity; + _folderDao = daoFactory.GetFolderDao(); + } + + public override string Name => Constants.RoomsModule; + public override string Product => Constants.Documents; + public override Guid ProductID => WebItemManager.DocumentsProductID; + protected override string DbId => Constants.FilesDbId; + + public override bool VisibleFor(Feed.Aggregator.Feed feed, object data, Guid userId) + { + if (!_webItemSecurity.IsAvailableForUser(ProductID, userId)) + { + return false; + } + + var folderWithShare = (FolderWithShare)data; + var folder = folderWithShare.Folder; + var shareRecord = folderWithShare.ShareRecord; + + bool targetCond; + if (feed.Target != null) + { + if (shareRecord != null && shareRecord.Owner == userId) + { + return false; + } + + var owner = (Guid)feed.Target; + var groupUsers = _userManager.GetUsersByGroup(owner).Select(x => x.Id).ToList(); + if (groupUsers.Count == 0) + { + groupUsers.Add(owner); + } + + targetCond = groupUsers.Contains(userId); + } + else + { + targetCond = true; + } + + return targetCond && _fileSecurity.CanReadAsync(folder, userId).Result; + } + + public override IEnumerable> GetFeeds(FeedFilter filter) + { + var rooms = _folderDao.GetFeedsForRoomsAsync(filter.Tenant, filter.Time.From, filter.Time.To).ToListAsync().Result; + + var parentFolderIDs = rooms.Select(r => r.Folder.ParentId).ToList(); + var parentFolders = _folderDao.GetFoldersAsync(parentFolderIDs, checkShare: false).ToListAsync().Result; + + return rooms.Select(f => new Tuple(ToFeed(f, parentFolders.FirstOrDefault(r => r.Id.Equals(f.Folder.ParentId))), f)); + } + + public override IEnumerable GetTenantsWithFeeds(DateTime fromTime) + { + return _folderDao.GetTenantsWithFeedsForFoldersAsync(fromTime).ToListAsync().Result; + } + + private Feed.Aggregator.Feed ToFeed(FolderWithShare folderWithSecurity, Folder rootFolder) + { + var folder = folderWithSecurity.Folder; + var shareRecord = folderWithSecurity.ShareRecord; + + if (shareRecord != null) + { + var feed = new Feed.Aggregator.Feed(shareRecord.Owner, shareRecord.TimeStamp, true) + { + Item = SharedRoomItem, + ItemId = string.Format("{0}_{1}", folder.Id, shareRecord.Subject), + ItemUrl = _filesLinkUtility.GetFileRedirectPreviewUrl(folder.Id, false), + Product = Product, + Module = Name, + Title = folder.Title, + ExtraLocation = rootFolder.FolderType == FolderType.DEFAULT ? rootFolder.Title : string.Empty, + ExtraLocationUrl = rootFolder.FolderType == FolderType.DEFAULT ? _filesLinkUtility.GetFileRedirectPreviewUrl(folder.ParentId, false) : string.Empty, + Keywords = folder.Title, + HasPreview = false, + CanComment = false, + Target = shareRecord.Subject, + GroupId = GetGroupId(SharedRoomItem, shareRecord.Owner, folder.ParentId.ToString()) + }; + + return feed; + } + + return new Feed.Aggregator.Feed(folder.CreateBy, folder.CreateOn) + { + Item = RoomItem, + ItemId = folder.Id.ToString(), + ItemUrl = _filesLinkUtility.GetFileRedirectPreviewUrl(folder.Id, false), + Product = Product, + Module = Name, + Title = folder.Title, + ExtraLocation = rootFolder.FolderType == FolderType.DEFAULT ? rootFolder.Title : string.Empty, + ExtraLocationUrl = rootFolder.FolderType == FolderType.DEFAULT ? _filesLinkUtility.GetFileRedirectPreviewUrl(folder.ParentId, false) : string.Empty, + Keywords = folder.Title, + HasPreview = false, + CanComment = false, + Target = null, + GroupId = GetGroupId(RoomItem, folder.CreateBy, folder.ParentId.ToString()) + }; + } +} \ No newline at end of file diff --git a/products/ASC.Files/Service/feed.json b/products/ASC.Files/Service/feed.json index 8507a5095b..2f9dcf6b85 100644 --- a/products/ASC.Files/Service/feed.json +++ b/products/ASC.Files/Service/feed.json @@ -17,6 +17,15 @@ } ], "instanceScope": "perlifetimescope" + }, + { + "type": "ASC.Files.Service.Core.RoomsModule, ASC.Files.Service", + "services": [ + { + "type": "ASC.Feed.Aggregator.Modules.IFeedModule, ASC.Feed.Aggregator" + } + ], + "instanceScope": "perlifetimescope" } ] } \ No newline at end of file From 481223a51b06e0a5dff01ac78b17f0a5ed9a57e3 Mon Sep 17 00:00:00 2001 From: MaksimChegulov Date: Wed, 31 Aug 2022 20:02:56 +0300 Subject: [PATCH 007/110] Files: added context id --- common/ASC.Feed/Core/Feed.cs | 1 + common/ASC.Feed/Core/FeedRow.cs | 1 + common/ASC.Feed/EF/Model/FeedAggregate.cs | 9 + ...1152410_FeedDbContext_Upgrade1.Designer.cs | 194 ++++++++++++++++++ .../20220831152410_FeedDbContext_Upgrade1.cs | 27 +++ .../FeedDbContextModelSnapshot.cs | 8 +- ...1152410_FeedDbContext_Upgrade1.Designer.cs | 174 ++++++++++++++++ .../20220831152410_FeedDbContext_Upgrade1.cs | 27 +++ .../FeedDbContextModelSnapshot.cs | 6 +- 9 files changed, 445 insertions(+), 2 deletions(-) create mode 100644 migrations/mysql/FeedDbContext/20220831152410_FeedDbContext_Upgrade1.Designer.cs create mode 100644 migrations/mysql/FeedDbContext/20220831152410_FeedDbContext_Upgrade1.cs create mode 100644 migrations/postgre/FeedDbContext/20220831152410_FeedDbContext_Upgrade1.Designer.cs create mode 100644 migrations/postgre/FeedDbContext/20220831152410_FeedDbContext_Upgrade1.cs diff --git a/common/ASC.Feed/Core/Feed.cs b/common/ASC.Feed/Core/Feed.cs index 31c9e615f7..0b1da43194 100644 --- a/common/ASC.Feed/Core/Feed.cs +++ b/common/ASC.Feed/Core/Feed.cs @@ -54,6 +54,7 @@ public class Feed public string GroupId { get; set; } public string Keywords { get; set; } public string Id => $"{Item}_{ItemId}"; + public string ContextId { get; set; } // это значит, что новость может обновляться (пр. добавление комментариев); // следовательно и права доступа могут устаревать diff --git a/common/ASC.Feed/Core/FeedRow.cs b/common/ASC.Feed/Core/FeedRow.cs index 5a4a437a27..ec1b56e1d9 100644 --- a/common/ASC.Feed/Core/FeedRow.cs +++ b/common/ASC.Feed/Core/FeedRow.cs @@ -42,6 +42,7 @@ public class FeedRow public DateTime ModifiedDate => Feed.ModifiedDate; public string GroupId => Feed.GroupId; public string Keywords => Feed.Keywords; + public string ContextId => Feed.ContextId; public string Json { get diff --git a/common/ASC.Feed/EF/Model/FeedAggregate.cs b/common/ASC.Feed/EF/Model/FeedAggregate.cs index 3e64d6b510..c464603d6b 100644 --- a/common/ASC.Feed/EF/Model/FeedAggregate.cs +++ b/common/ASC.Feed/EF/Model/FeedAggregate.cs @@ -40,6 +40,7 @@ public class FeedAggregate : BaseEntity, IMapFrom public DateTime AggregateDate { get; set; } public string Json { get; set; } public string Keywords { get; set; } + public string ContextId { get; set; } public override object[] GetKeys() { @@ -137,6 +138,12 @@ public static class FeedAggregateExtension .UseCollation("utf8_general_ci"); entity.Property(e => e.Tenant).HasColumnName("tenant"); + + entity.Property(e => e.ContextId) + .HasColumnName("context_id") + .HasColumnType("text") + .HasCharSet("utf8") + .UseCollation("utf8_general_ci"); }); } public static void PgSqlAddFeedAggregate(this ModelBuilder modelBuilder) @@ -198,6 +205,8 @@ public static class FeedAggregateExtension .HasMaxLength(50); entity.Property(e => e.Tenant).HasColumnName("tenant"); + + entity.Property(e => e.ContextId).HasColumnName("context_id"); }); } } diff --git a/migrations/mysql/FeedDbContext/20220831152410_FeedDbContext_Upgrade1.Designer.cs b/migrations/mysql/FeedDbContext/20220831152410_FeedDbContext_Upgrade1.Designer.cs new file mode 100644 index 0000000000..2ce264a8f3 --- /dev/null +++ b/migrations/mysql/FeedDbContext/20220831152410_FeedDbContext_Upgrade1.Designer.cs @@ -0,0 +1,194 @@ +// +using System; +using ASC.Feed.Context; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace ASC.Migrations.MySql.Migrations.FeedDb +{ + [DbContext(typeof(FeedDbContext))] + [Migration("20220831152410_FeedDbContext_Upgrade1")] + partial class FeedDbContext_Upgrade1 + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("ASC.Feed.Model.FeedAggregate", b => + { + b.Property("Id") + .HasColumnType("varchar(88)") + .HasColumnName("id") + .UseCollation("utf8_general_ci") + .HasAnnotation("MySql:CharSet", "utf8"); + + b.Property("AggregateDate") + .HasColumnType("datetime") + .HasColumnName("aggregated_date"); + + b.Property("Author") + .IsRequired() + .HasColumnType("char(38)") + .HasColumnName("author") + .UseCollation("utf8_general_ci") + .HasAnnotation("MySql:CharSet", "utf8"); + + b.Property("ContextId") + .HasColumnType("text") + .HasColumnName("context_id") + .UseCollation("utf8_general_ci") + .HasAnnotation("MySql:CharSet", "utf8"); + + b.Property("CreatedDate") + .HasColumnType("datetime") + .HasColumnName("created_date"); + + b.Property("GroupId") + .HasColumnType("varchar(70)") + .HasColumnName("group_id") + .UseCollation("utf8_general_ci") + .HasAnnotation("MySql:CharSet", "utf8"); + + b.Property("Json") + .IsRequired() + .HasColumnType("mediumtext") + .HasColumnName("json") + .UseCollation("utf8_general_ci") + .HasAnnotation("MySql:CharSet", "utf8"); + + b.Property("Keywords") + .HasColumnType("text") + .HasColumnName("keywords") + .UseCollation("utf8_general_ci") + .HasAnnotation("MySql:CharSet", "utf8"); + + b.Property("ModifiedBy") + .IsRequired() + .HasColumnType("char(38)") + .HasColumnName("modified_by") + .UseCollation("utf8_general_ci") + .HasAnnotation("MySql:CharSet", "utf8"); + + b.Property("ModifiedDate") + .HasColumnType("datetime") + .HasColumnName("modified_date"); + + b.Property("Module") + .IsRequired() + .HasColumnType("varchar(50)") + .HasColumnName("module") + .UseCollation("utf8_general_ci") + .HasAnnotation("MySql:CharSet", "utf8"); + + b.Property("Product") + .IsRequired() + .HasColumnType("varchar(50)") + .HasColumnName("product") + .UseCollation("utf8_general_ci") + .HasAnnotation("MySql:CharSet", "utf8"); + + b.Property("Tenant") + .HasColumnType("int") + .HasColumnName("tenant"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "AggregateDate") + .HasDatabaseName("aggregated_date"); + + b.HasIndex("Tenant", "ModifiedDate") + .HasDatabaseName("modified_date"); + + b.HasIndex("Tenant", "Product") + .HasDatabaseName("product"); + + b.ToTable("feed_aggregate", (string)null); + + b.HasAnnotation("MySql:CharSet", "utf8"); + }); + + modelBuilder.Entity("ASC.Feed.Model.FeedLast", b => + { + b.Property("LastKey") + .HasColumnType("varchar(128)") + .HasColumnName("last_key") + .UseCollation("utf8_general_ci") + .HasAnnotation("MySql:CharSet", "utf8"); + + b.Property("LastDate") + .HasColumnType("datetime") + .HasColumnName("last_date"); + + b.HasKey("LastKey") + .HasName("PRIMARY"); + + b.ToTable("feed_last", (string)null); + + b.HasAnnotation("MySql:CharSet", "utf8"); + }); + + modelBuilder.Entity("ASC.Feed.Model.FeedReaded", b => + { + b.Property("Tenant") + .HasColumnType("int") + .HasColumnName("tenant_id"); + + b.Property("UserId") + .HasColumnType("varchar(38)") + .HasColumnName("user_id") + .UseCollation("utf8_general_ci") + .HasAnnotation("MySql:CharSet", "utf8"); + + b.Property("Module") + .HasColumnType("varchar(50)") + .HasColumnName("module") + .UseCollation("utf8_general_ci") + .HasAnnotation("MySql:CharSet", "utf8"); + + b.Property("TimeStamp") + .HasColumnType("datetime") + .HasColumnName("timestamp"); + + b.HasKey("Tenant", "UserId", "Module") + .HasName("PRIMARY"); + + b.ToTable("feed_readed", (string)null); + + b.HasAnnotation("MySql:CharSet", "utf8"); + }); + + modelBuilder.Entity("ASC.Feed.Model.FeedUsers", b => + { + b.Property("FeedId") + .HasColumnType("varchar(88)") + .HasColumnName("feed_id") + .UseCollation("utf8_general_ci") + .HasAnnotation("MySql:CharSet", "utf8"); + + b.Property("UserId") + .HasColumnType("char(38)") + .HasColumnName("user_id") + .UseCollation("utf8_general_ci") + .HasAnnotation("MySql:CharSet", "utf8"); + + b.HasKey("FeedId", "UserId") + .HasName("PRIMARY"); + + b.HasIndex("UserId") + .HasDatabaseName("user_id"); + + b.ToTable("feed_users", (string)null); + + b.HasAnnotation("MySql:CharSet", "utf8"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/migrations/mysql/FeedDbContext/20220831152410_FeedDbContext_Upgrade1.cs b/migrations/mysql/FeedDbContext/20220831152410_FeedDbContext_Upgrade1.cs new file mode 100644 index 0000000000..a398aec75d --- /dev/null +++ b/migrations/mysql/FeedDbContext/20220831152410_FeedDbContext_Upgrade1.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ASC.Migrations.MySql.Migrations.FeedDb +{ + public partial class FeedDbContext_Upgrade1 : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "context_id", + table: "feed_aggregate", + type: "text", + nullable: true, + collation: "utf8_general_ci") + .Annotation("MySql:CharSet", "utf8"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "context_id", + table: "feed_aggregate"); + } + } +} diff --git a/migrations/mysql/FeedDbContext/FeedDbContextModelSnapshot.cs b/migrations/mysql/FeedDbContext/FeedDbContextModelSnapshot.cs index 9e09118252..f38161f9f5 100644 --- a/migrations/mysql/FeedDbContext/FeedDbContextModelSnapshot.cs +++ b/migrations/mysql/FeedDbContext/FeedDbContextModelSnapshot.cs @@ -16,7 +16,7 @@ namespace ASC.Migrations.MySql.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "6.0.4") + .HasAnnotation("ProductVersion", "6.0.7") .HasAnnotation("Relational:MaxIdentifierLength", 64); modelBuilder.Entity("ASC.Feed.Model.FeedAggregate", b => @@ -38,6 +38,12 @@ namespace ASC.Migrations.MySql.Migrations .UseCollation("utf8_general_ci") .HasAnnotation("MySql:CharSet", "utf8"); + b.Property("ContextId") + .HasColumnType("text") + .HasColumnName("context_id") + .UseCollation("utf8_general_ci") + .HasAnnotation("MySql:CharSet", "utf8"); + b.Property("CreatedDate") .HasColumnType("datetime") .HasColumnName("created_date"); diff --git a/migrations/postgre/FeedDbContext/20220831152410_FeedDbContext_Upgrade1.Designer.cs b/migrations/postgre/FeedDbContext/20220831152410_FeedDbContext_Upgrade1.Designer.cs new file mode 100644 index 0000000000..a6743c0c3e --- /dev/null +++ b/migrations/postgre/FeedDbContext/20220831152410_FeedDbContext_Upgrade1.Designer.cs @@ -0,0 +1,174 @@ +// +using System; +using ASC.Feed.Context; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace ASC.Migrations.PostgreSql.Migrations.FeedDb +{ + [DbContext(typeof(FeedDbContext))] + [Migration("20220831152410_FeedDbContext_Upgrade1")] + partial class FeedDbContext_Upgrade1 + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) + .HasAnnotation("ProductVersion", "6.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + modelBuilder.Entity("ASC.Feed.Model.FeedAggregate", b => + { + b.Property("Id") + .HasMaxLength(88) + .HasColumnType("character varying(88)") + .HasColumnName("id"); + + b.Property("AggregateDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("aggregated_date"); + + b.Property("Author") + .HasMaxLength(38) + .HasColumnType("uuid") + .HasColumnName("author") + .IsFixedLength(); + + b.Property("ContextId") + .HasColumnType("text") + .HasColumnName("context_id"); + + b.Property("CreatedDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_date"); + + b.Property("GroupId") + .ValueGeneratedOnAdd() + .HasMaxLength(70) + .HasColumnType("character varying(70)") + .HasColumnName("group_id") + .HasDefaultValueSql("NULL"); + + b.Property("Json") + .IsRequired() + .HasColumnType("text") + .HasColumnName("json"); + + b.Property("Keywords") + .HasColumnType("text") + .HasColumnName("keywords"); + + b.Property("ModifiedBy") + .HasMaxLength(38) + .HasColumnType("uuid") + .HasColumnName("modified_by") + .IsFixedLength(); + + b.Property("ModifiedDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_date"); + + b.Property("Module") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("module"); + + b.Property("Product") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("product"); + + b.Property("Tenant") + .HasColumnType("integer") + .HasColumnName("tenant"); + + b.HasKey("Id"); + + b.HasIndex("Tenant", "AggregateDate") + .HasDatabaseName("aggregated_date"); + + b.HasIndex("Tenant", "ModifiedDate") + .HasDatabaseName("modified_date"); + + b.HasIndex("Tenant", "Product") + .HasDatabaseName("product"); + + b.ToTable("feed_aggregate", "onlyoffice"); + }); + + modelBuilder.Entity("ASC.Feed.Model.FeedLast", b => + { + b.Property("LastKey") + .HasMaxLength(128) + .HasColumnType("character varying(128)") + .HasColumnName("last_key"); + + b.Property("LastDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("last_date"); + + b.HasKey("LastKey") + .HasName("feed_last_pkey"); + + b.ToTable("feed_last", "onlyoffice"); + }); + + modelBuilder.Entity("ASC.Feed.Model.FeedReaded", b => + { + b.Property("UserId") + .HasMaxLength(38) + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property("Tenant") + .HasColumnType("integer") + .HasColumnName("tenant_id"); + + b.Property("Module") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("module"); + + b.Property("TimeStamp") + .HasColumnType("timestamp with time zone") + .HasColumnName("timestamp"); + + b.HasKey("UserId", "Tenant", "Module") + .HasName("feed_readed_pkey"); + + b.ToTable("feed_readed", "onlyoffice"); + }); + + modelBuilder.Entity("ASC.Feed.Model.FeedUsers", b => + { + b.Property("FeedId") + .HasMaxLength(88) + .HasColumnType("character varying(88)") + .HasColumnName("feed_id"); + + b.Property("UserId") + .HasMaxLength(38) + .HasColumnType("uuid") + .HasColumnName("user_id") + .IsFixedLength(); + + b.HasKey("FeedId", "UserId") + .HasName("feed_users_pkey"); + + b.HasIndex("UserId") + .HasDatabaseName("user_id_feed_users"); + + b.ToTable("feed_users", "onlyoffice"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/migrations/postgre/FeedDbContext/20220831152410_FeedDbContext_Upgrade1.cs b/migrations/postgre/FeedDbContext/20220831152410_FeedDbContext_Upgrade1.cs new file mode 100644 index 0000000000..1a87cfa604 --- /dev/null +++ b/migrations/postgre/FeedDbContext/20220831152410_FeedDbContext_Upgrade1.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ASC.Migrations.PostgreSql.Migrations.FeedDb +{ + public partial class FeedDbContext_Upgrade1 : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "context_id", + schema: "onlyoffice", + table: "feed_aggregate", + type: "text", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "context_id", + schema: "onlyoffice", + table: "feed_aggregate"); + } + } +} diff --git a/migrations/postgre/FeedDbContext/FeedDbContextModelSnapshot.cs b/migrations/postgre/FeedDbContext/FeedDbContextModelSnapshot.cs index 22b48b9899..5dce5a975c 100644 --- a/migrations/postgre/FeedDbContext/FeedDbContextModelSnapshot.cs +++ b/migrations/postgre/FeedDbContext/FeedDbContextModelSnapshot.cs @@ -18,7 +18,7 @@ namespace ASC.Migrations.PostgreSql.Migrations #pragma warning disable 612, 618 modelBuilder .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) - .HasAnnotation("ProductVersion", "6.0.4") + .HasAnnotation("ProductVersion", "6.0.7") .HasAnnotation("Relational:MaxIdentifierLength", 63); modelBuilder.Entity("ASC.Feed.Model.FeedAggregate", b => @@ -38,6 +38,10 @@ namespace ASC.Migrations.PostgreSql.Migrations .HasColumnName("author") .IsFixedLength(); + b.Property("ContextId") + .HasColumnType("text") + .HasColumnName("context_id"); + b.Property("CreatedDate") .HasColumnType("timestamp with time zone") .HasColumnName("created_date"); From 99f62b936db0d447d9e1db79bae2387b48bf11cd Mon Sep 17 00:00:00 2001 From: MaksimChegulov Date: Wed, 31 Aug 2022 20:04:52 +0300 Subject: [PATCH 008/110] Files: added getting parent room IDs for the folder list --- .../Core/Core/Dao/Interfaces/IFolderDao.cs | 1 + .../Core/Core/Dao/TeamlabDao/FolderDao.cs | 26 +++++++++++++++++++ .../Core/Thirdparty/IThirdPartyProviderDao.cs | 5 ++++ 3 files changed, 32 insertions(+) diff --git a/products/ASC.Files/Core/Core/Dao/Interfaces/IFolderDao.cs b/products/ASC.Files/Core/Core/Dao/Interfaces/IFolderDao.cs index 83fefd3678..0a791d565d 100644 --- a/products/ASC.Files/Core/Core/Dao/Interfaces/IFolderDao.cs +++ b/products/ASC.Files/Core/Core/Dao/Interfaces/IFolderDao.cs @@ -343,6 +343,7 @@ public interface IFolderDao Task> GetBunchObjectIDsAsync(List folderIDs); IAsyncEnumerable GetFeedsForRoomsAsync(int tenant, DateTime from, DateTime to); IAsyncEnumerable GetFeedsForFoldersAsync(int tenant, DateTime from, DateTime to); + IAsyncEnumerable GetParentRoomsAsync(IEnumerable foldersIds); IAsyncEnumerable GetTenantsWithFeedsForFoldersAsync(DateTime fromTime); diff --git a/products/ASC.Files/Core/Core/Dao/TeamlabDao/FolderDao.cs b/products/ASC.Files/Core/Core/Dao/TeamlabDao/FolderDao.cs index e749fad498..128fa53ece 100644 --- a/products/ASC.Files/Core/Core/Dao/TeamlabDao/FolderDao.cs +++ b/products/ASC.Files/Core/Core/Dao/TeamlabDao/FolderDao.cs @@ -357,6 +357,26 @@ internal class FolderDao : AbstractDao, IFolderDao } } + public async IAsyncEnumerable GetParentRoomsAsync(IEnumerable foldersIds) + { + var roomTypes = new List { FolderType.CustomRoom, FolderType.ReviewRoom, FolderType.FillingFormsRoom, FolderType.EditingRoom, FolderType.ReadOnlyRoom }; + + var filesDbContext = _dbContextFactory.CreateDbContext(); + + var q = GetFolderQuery(filesDbContext) + .AsNoTracking() + .Join(filesDbContext.Tree, r => r.Id, a => a.ParentId, (folder, tree) => new { folder, tree }) + .Where(r => foldersIds.Contains(r.tree.FolderId)) + .OrderByDescending(r => r.tree.Level) + .Where(r => roomTypes.Contains(r.folder.FolderType)) + .Select(r => new ParentRoomPair { FolderId = r.tree.FolderId, ParentRoomId = r.folder.Id }); + + await foreach (var e in q.AsAsyncEnumerable()) + { + yield return e; + } + } + public Task SaveFolderAsync(Folder folder) { return SaveFolderAsync(folder, null); @@ -1615,4 +1635,10 @@ public class DbFolderQueryWithSecurity { public DbFolderQuery DbFolderQuery { get; set; } public DbFilesSecurity Security { get; set; } +} + +public class ParentRoomPair +{ + public int FolderId { get; set; } + public int ParentRoomId { get; set; } } \ No newline at end of file diff --git a/products/ASC.Files/Core/Core/Thirdparty/IThirdPartyProviderDao.cs b/products/ASC.Files/Core/Core/Thirdparty/IThirdPartyProviderDao.cs index 47cc788d1e..e7868d70ae 100644 --- a/products/ASC.Files/Core/Core/Thirdparty/IThirdPartyProviderDao.cs +++ b/products/ASC.Files/Core/Core/Thirdparty/IThirdPartyProviderDao.cs @@ -224,6 +224,11 @@ internal abstract class ThirdPartyProviderDao throw new NotImplementedException(); } + public IAsyncEnumerable GetParentRoomsAsync(IEnumerable foldersIds) + { + throw new NotImplementedException(); + } + public IAsyncEnumerable GetTenantsWithFeedsForFoldersAsync(DateTime fromTime) { throw new NotImplementedException(); From 257693d3f6437b0ad5273a91086811a56264f95a Mon Sep 17 00:00:00 2001 From: MaksimChegulov Date: Wed, 31 Aug 2022 20:06:49 +0300 Subject: [PATCH 009/110] Files.Service: added context id for files inside the room --- products/ASC.Files/Service/Core/FilesModule.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/products/ASC.Files/Service/Core/FilesModule.cs b/products/ASC.Files/Service/Core/FilesModule.cs index ed2dd78a1a..b1335bf872 100644 --- a/products/ASC.Files/Service/Core/FilesModule.cs +++ b/products/ASC.Files/Service/Core/FilesModule.cs @@ -141,8 +141,10 @@ public class FilesModule : FeedModule var folderIDs = files.Select(r => r.File.ParentId).ToList(); var folders = _folderDao.GetFoldersAsync(folderIDs, checkShare: false).ToListAsync().Result; + var roomsIds = _folderDao.GetParentRoomsAsync(folderIDs).ToDictionaryAsync(k => k.FolderId, v => v.ParentRoomId).Result; - return files.Select(f => new Tuple(ToFeed(f, folders.FirstOrDefault(r => r.Id.Equals(f.File.ParentId))), f)); + return files.Select(f => new Tuple(ToFeed(f, folders.FirstOrDefault(r => r.Id.Equals(f.File.ParentId)), + roomsIds.GetValueOrDefault(f.File.ParentId)), f)); } public override IEnumerable GetTenantsWithFeeds(DateTime fromTime) @@ -150,10 +152,11 @@ public class FilesModule : FeedModule return _fileDao.GetTenantsWithFeedsAsync(fromTime).ToListAsync().Result; } - private Feed.Aggregator.Feed ToFeed(FileWithShare tuple, Folder rootFolder) + private Feed.Aggregator.Feed ToFeed(FileWithShare tuple, Folder rootFolder, int roomId) { var file = tuple.File; var shareRecord = tuple.ShareRecord; + var contextId = roomId != default ? $"room_{roomId}" : null; if (shareRecord != null) { @@ -172,7 +175,8 @@ public class FilesModule : FeedModule HasPreview = false, CanComment = false, Target = shareRecord.Subject, - GroupId = GetGroupId(SharedFileItem, shareRecord.Owner, file.ParentId.ToString()) + GroupId = GetGroupId(SharedFileItem, shareRecord.Owner, file.ParentId.ToString()), + ContextId = contextId }; return feed; @@ -196,7 +200,8 @@ public class FilesModule : FeedModule HasPreview = false, CanComment = false, Target = null, - GroupId = GetGroupId(FileItem, file.ModifiedBy, file.ParentId.ToString(), updated ? 1 : 0) + GroupId = GetGroupId(FileItem, file.ModifiedBy, file.ParentId.ToString(), updated ? 1 : 0), + ContextId = contextId }; } From 8e82656e578d09a8785ee164830d56ba7fb843d0 Mon Sep 17 00:00:00 2001 From: MaksimChegulov Date: Wed, 31 Aug 2022 20:06:57 +0300 Subject: [PATCH 010/110] Files.Service: added context id for folders inside the room --- products/ASC.Files/Service/Core/FoldersModule.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/products/ASC.Files/Service/Core/FoldersModule.cs b/products/ASC.Files/Service/Core/FoldersModule.cs index 520a453005..c757dc9770 100644 --- a/products/ASC.Files/Service/Core/FoldersModule.cs +++ b/products/ASC.Files/Service/Core/FoldersModule.cs @@ -107,14 +107,17 @@ public class FoldersModule : FeedModule var parentFolderIDs = folders.Select(r => r.Folder.ParentId).ToList(); var parentFolders = _folderDao.GetFoldersAsync(parentFolderIDs, checkShare: false).ToListAsync().Result; + var roomsIds = _folderDao.GetParentRoomsAsync(parentFolderIDs).ToDictionaryAsync(k => k.FolderId, v => v.ParentRoomId).Result; - return folders.Select(f => new Tuple(ToFeed(f, parentFolders.FirstOrDefault(r => r.Id.Equals(f.Folder.ParentId))), f)); + return folders.Select(f => new Tuple(ToFeed(f, parentFolders.FirstOrDefault(r => r.Id.Equals(f.Folder.ParentId)), + roomsIds.GetValueOrDefault(f.Folder.ParentId)), f)); } - private Feed.Aggregator.Feed ToFeed(FolderWithShare folderWithSecurity, Folder rootFolder) + private Feed.Aggregator.Feed ToFeed(FolderWithShare folderWithSecurity, Folder rootFolder, int roomId) { var folder = folderWithSecurity.Folder; var shareRecord = folderWithSecurity.ShareRecord; + var contextId = roomId != default ? $"room_{roomId}" : null; if (shareRecord != null) { @@ -132,7 +135,8 @@ public class FoldersModule : FeedModule HasPreview = false, CanComment = false, Target = shareRecord.Subject, - GroupId = GetGroupId(SharedFolderItem, shareRecord.Owner, folder.ParentId.ToString()) + GroupId = GetGroupId(SharedFolderItem, shareRecord.Owner, folder.ParentId.ToString()), + ContextId = contextId }; return feed; @@ -152,7 +156,8 @@ public class FoldersModule : FeedModule HasPreview = false, CanComment = false, Target = null, - GroupId = GetGroupId(FolderItem, folder.CreateBy, folder.ParentId.ToString()) + GroupId = GetGroupId(FolderItem, folder.CreateBy, folder.ParentId.ToString()), + ContextId = contextId }; } } From effe113ff08aa8a494913db1545c1695ce12af85 Mon Sep 17 00:00:00 2001 From: MaksimChegulov Date: Thu, 1 Sep 2022 18:19:17 +0300 Subject: [PATCH 011/110] Feed: refactor --- .../Data/FeedAggregateDataProvider.cs | 79 ++++--------------- 1 file changed, 17 insertions(+), 62 deletions(-) diff --git a/common/ASC.Feed/Data/FeedAggregateDataProvider.cs b/common/ASC.Feed/Data/FeedAggregateDataProvider.cs index d97bd7ca0f..f3114aa88f 100644 --- a/common/ASC.Feed/Data/FeedAggregateDataProvider.cs +++ b/common/ASC.Feed/Data/FeedAggregateDataProvider.cs @@ -253,14 +253,14 @@ public class FeedAggregateDataProvider return _mapper.Map, List>(news); } - public int GetNewFeedsCount(DateTime lastReadedTime, AuthContext authContext, TenantManager tenantManager) + public int GetNewFeedsCount(DateTime lastReadedTime) { using var feedDbContext = _dbContextFactory.CreateDbContext(); var count = feedDbContext.FeedAggregates - .Where(r => r.Tenant == tenantManager.GetCurrentTenant().Id) - .Where(r => r.ModifiedBy != authContext.CurrentAccount.ID) + .Where(r => r.Tenant == _tenantManager.GetCurrentTenant().Id) + .Where(r => r.ModifiedBy != _authContext.CurrentAccount.ID) .Join(feedDbContext.FeedUsers, r => r.Id, u => u.FeedId, (agg, user) => new { agg, user }) - .Where(r => r.user.UserId == authContext.CurrentAccount.ID); + .Where(r => r.user.UserId == _authContext.CurrentAccount.ID); if (1 < lastReadedTime.Year) { @@ -316,66 +316,21 @@ public class FeedAggregateDataProvider public class FeedResultItem : IMapFrom { - public string Json { get; private set; } - public string Module { get; private set; } - public Guid AuthorId { get; private set; } - public Guid ModifiedById { get; private set; } - public string GroupId { get; private set; } - public bool IsToday { get; private set; } - public bool IsYesterday { get; private set; } - public bool IsTomorrow { get; private set; } - public DateTime CreatedDate { get; private set; } - public DateTime ModifiedDate { get; private set; } - public DateTime AggregatedDate { get; private set; } - - public FeedResultItem() { } - - public FeedResultItem( - string json, - string module, - Guid authorId, - Guid modifiedById, - string groupId, - DateTime createdDate, - DateTime modifiedDate, - DateTime aggregatedDate, - TenantUtil tenantUtil) - { - var now = tenantUtil.DateTimeFromUtc(DateTime.UtcNow); - - Json = json; - Module = module; - - AuthorId = authorId; - ModifiedById = modifiedById; - - GroupId = groupId; - - var compareDate = JsonNode.Parse(Json)["IsAllDayEvent"].GetValue() - ? tenantUtil.DateTimeToUtc(createdDate).Date - : createdDate.Date; - - if (now.Date == compareDate.AddDays(-1)) - { - IsTomorrow = true; - } - else if (now.Date == compareDate) - { - IsToday = true; - } - else if (now.Date == compareDate.AddDays(1)) - { - IsYesterday = true; - } - - CreatedDate = createdDate; - ModifiedDate = modifiedDate; - AggregatedDate = aggregatedDate; - } + public string Json { get; set; } + public string Module { get; set; } + public Guid AuthorId { get; set; } + public Guid ModifiedById { get; set; } + public string GroupId { get; set; } + public bool IsToday { get; set; } + public bool IsYesterday { get; set; } + public bool IsTomorrow { get; set; } + public DateTime CreatedDate { get; set; } + public DateTime ModifiedDate { get; set; } + public DateTime AggregatedDate { get; set; } public void Mapping(Profile profile) { profile.CreateMap() - .ConvertUsing(); + .AfterMap(); } -} +} \ No newline at end of file From e966afb49aacde780eb2c7552d7f56499b96faa7 Mon Sep 17 00:00:00 2001 From: MaksimChegulov Date: Thu, 1 Sep 2022 18:19:22 +0300 Subject: [PATCH 012/110] Feed: fix --- common/ASC.Feed/Data/FeedReadedDataProvider.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/common/ASC.Feed/Data/FeedReadedDataProvider.cs b/common/ASC.Feed/Data/FeedReadedDataProvider.cs index d88ae8ae6e..3e3a3f9879 100644 --- a/common/ASC.Feed/Data/FeedReadedDataProvider.cs +++ b/common/ASC.Feed/Data/FeedReadedDataProvider.cs @@ -26,6 +26,7 @@ namespace ASC.Feed.Data; +[Scope] public class FeedReadedDataProvider { private readonly AuthContext _authContext; From 91d396c026ed5f97c4756f87ba77b1410d6c9d3a Mon Sep 17 00:00:00 2001 From: MaksimChegulov Date: Thu, 1 Sep 2022 18:19:38 +0300 Subject: [PATCH 013/110] Feed: refactor --- common/ASC.Feed/Mapping/FeedTypeConverter.cs | 54 ++++++++++++++------ 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/common/ASC.Feed/Mapping/FeedTypeConverter.cs b/common/ASC.Feed/Mapping/FeedTypeConverter.cs index 576b1cb206..b74f9fb95f 100644 --- a/common/ASC.Feed/Mapping/FeedTypeConverter.cs +++ b/common/ASC.Feed/Mapping/FeedTypeConverter.cs @@ -26,31 +26,53 @@ namespace ASC.Feed.Mapping; -public class FeedTypeConverter : ITypeConverter, ITypeConverter +[Scope] +public class FeedMappingAction : IMappingAction { private readonly TenantUtil _tenantUtil; - private readonly UserManager _userManager; - public FeedTypeConverter(TenantUtil tenantUtil, UserManager userManager) + public FeedMappingAction(TenantUtil tenantUtil) { _tenantUtil = tenantUtil; - _userManager = userManager; } - public FeedResultItem Convert(FeedAggregate source, FeedResultItem destination, ResolutionContext context) + public void Process(FeedAggregate source, FeedResultItem destination, ResolutionContext context) { - var result = new FeedResultItem( - source.Json, - source.Module, - source.Author, - source.ModifiedBy, - source.GroupId, - _tenantUtil.DateTimeFromUtc(source.CreatedDate), - _tenantUtil.DateTimeFromUtc(source.ModifiedDate), - _tenantUtil.DateTimeFromUtc(source.AggregateDate), - _tenantUtil); + var now = _tenantUtil.DateTimeFromUtc(DateTime.UtcNow); - return result; + destination.CreatedDate = _tenantUtil.DateTimeFromUtc(source.CreatedDate); + destination.ModifiedDate = _tenantUtil.DateTimeFromUtc(source.ModifiedDate); + destination.AggregatedDate = _tenantUtil.DateTimeFromUtc(source.AggregateDate); + + var node = JsonNode.Parse(destination.Json)["IsAllDayEvent"]; + + var compareDate = node != null && node.GetValue() + ? _tenantUtil.DateTimeToUtc(source.CreatedDate).Date + : destination.CreatedDate.Date; + + if (now.Date == compareDate.AddDays(-1)) + { + destination.IsTomorrow = true; + } + else if (now.Date == compareDate) + { + destination.IsToday = true; + } + else if (now.Date == compareDate.AddDays(1)) + { + destination.IsYesterday = true; + } + } +} + +[Scope] +public class FeedTypeConverter : ITypeConverter +{ + private readonly UserManager _userManager; + + public FeedTypeConverter(UserManager userManager) + { + _userManager = userManager; } public FeedMin Convert(FeedResultItem source, FeedMin destination, ResolutionContext context) From bf4a27def576e973433b18b385cc964fb425f131 Mon Sep 17 00:00:00 2001 From: MaksimChegulov Date: Thu, 1 Sep 2022 18:22:40 +0300 Subject: [PATCH 014/110] Web.Api: added feed api --- web/ASC.Web.Api/ASC.Web.Api.csproj | 1 + web/ASC.Web.Api/Api/FeedController.cs | 172 ++++++++++++++++++ .../ApiModels/ResponseDto/FeedDto.cs | 50 +++++ web/ASC.Web.Api/GlobalUsings.cs | 5 +- web/ASC.Web.Api/Mapping/FeedMapperAction.cs | 43 +++++ 5 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 web/ASC.Web.Api/Api/FeedController.cs create mode 100644 web/ASC.Web.Api/ApiModels/ResponseDto/FeedDto.cs create mode 100644 web/ASC.Web.Api/Mapping/FeedMapperAction.cs diff --git a/web/ASC.Web.Api/ASC.Web.Api.csproj b/web/ASC.Web.Api/ASC.Web.Api.csproj index fabdc71285..7f9ac7b7b2 100644 --- a/web/ASC.Web.Api/ASC.Web.Api.csproj +++ b/web/ASC.Web.Api/ASC.Web.Api.csproj @@ -24,6 +24,7 @@ + diff --git a/web/ASC.Web.Api/Api/FeedController.cs b/web/ASC.Web.Api/Api/FeedController.cs new file mode 100644 index 0000000000..c9697efec0 --- /dev/null +++ b/web/ASC.Web.Api/Api/FeedController.cs @@ -0,0 +1,172 @@ +// (c) Copyright Ascensio System SIA 2010-2022 +// +// This program is a free software product. +// You can redistribute it and/or modify it under the terms +// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software +// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended +// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of +// any third-party rights. +// +// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see +// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html +// +// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021. +// +// The interactive user interfaces in modified source and object code versions of the Program must +// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3. +// +// Pursuant to Section 7(b) of the License you must retain the original Product logo when +// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under +// trademark law for use of our trademarks. +// +// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing +// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 +// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode + +namespace ASC.Web.Api.Controllers; + +[Scope] +[DefaultRoute] +[ApiController] +public class FeedController : ControllerBase +{ + private readonly FeedReadedDataProvider _feedReadedDataProvider; + private readonly ApiContext _apiContext; + private readonly ICache _newFeedsCountCache; + private readonly FeedAggregateDataProvider _feedAggregateDataProvider; + private readonly TenantUtil _tenantUtil; + private readonly SecurityContext _securityContext; + private readonly IMapper _mapper; + + public FeedController( + FeedReadedDataProvider feedReadedDataProvider, + ApiContext apiContext, + ICache newFeedsCountCache, + FeedAggregateDataProvider feedAggregateDataProvider, + TenantUtil tenantUtil, + SecurityContext securityContext, + IMapper mapper) + { + _feedReadedDataProvider = feedReadedDataProvider; + _apiContext = apiContext; + _newFeedsCountCache = newFeedsCountCache; + _feedAggregateDataProvider = feedAggregateDataProvider; + _tenantUtil = tenantUtil; + _securityContext = securityContext; + _mapper = mapper; + } + + private string Key => $"newfeedscount/{_securityContext}"; + + /// + ///Opens feeds for reading. + /// + /// + ///Read feeds + /// + [HttpPut("read")] + public void Read() + { + _feedReadedDataProvider.SetTimeReaded(); + } + + /// + ///Returns a list of feeds that are filtered by the parameters specified in the request. + /// + /// + ///Get feeds + /// + /// Product which feeds you want to read + /// Time from which the feeds should be displayed + /// Time until which the feeds should be displayed + /// Author whose feeds you want to read + /// Displays only fresh feeds + /// Time when the feeds were read + ///List of filtered feeds + [HttpGet("filter")] + public object GetFeed( + string product, + ApiDateTime from, + ApiDateTime to, + Guid? author, + bool? onlyNew, + ApiDateTime timeReaded) + { + var filter = new FeedApiFilter + { + Product = product, + Offset = Convert.ToInt32(_apiContext.StartIndex), + Max = Convert.ToInt32(_apiContext.Count) - 1, + Author = author ?? Guid.Empty, + SearchKeys = _apiContext.FilterValues, + OnlyNew = onlyNew.HasValue && onlyNew.Value + }; + + if (from != null && to != null) + { + var f = _tenantUtil.DateTimeFromUtc(from.UtcTime); + filter.From = new DateTime(f.Year, f.Month, f.Day, 0, 0, 0); + + var t = _tenantUtil.DateTimeFromUtc(to.UtcTime); + filter.To = new DateTime(t.Year, t.Month, t.Day, 23, 59, 59); + } + else + { + filter.From = from != null ? from.UtcTime : DateTime.MinValue; + filter.To = to != null ? to.UtcTime : DateTime.MaxValue; + } + + var lastTimeReaded = _feedReadedDataProvider.GetTimeReaded(); + + var readedDate = timeReaded != null ? timeReaded.UtcTime : lastTimeReaded; + + if (filter.OnlyNew) + { + filter.From = lastTimeReaded; + filter.Max = 100; + } + else if (timeReaded == null) + { + _feedReadedDataProvider.SetTimeReaded(); + _newFeedsCountCache.Remove(Key); + } + + var feeds = _feedAggregateDataProvider + .GetFeeds(filter) + .GroupBy(n => n.GroupId, + n => _mapper.Map(n), + (n, group) => + { + var firstFeed = group.First(); + firstFeed.GroupedFeeds = group.Skip(1); + return firstFeed; + }) + .ToList(); + + return new { feeds, readedDate }; + } + + /// + ///Returns a number of fresh feeds. + /// + /// + ///Count fresh feeds + /// + ///Number of fresh feeds + [HttpGet("newfeedscount")] + public object GetFreshNewsCount() + { + var cacheKey = Key; + var resultfromCache = _newFeedsCountCache.Get(cacheKey); + + if (!int.TryParse(resultfromCache, out var result)) + { + var lastTimeReaded = _feedReadedDataProvider.GetTimeReaded(); + result = _feedAggregateDataProvider.GetNewFeedsCount(lastTimeReaded); + _newFeedsCountCache.Insert(cacheKey, result.ToString(), DateTime.UtcNow.AddMinutes(3)); + } + + return result; + } +} \ No newline at end of file diff --git a/web/ASC.Web.Api/ApiModels/ResponseDto/FeedDto.cs b/web/ASC.Web.Api/ApiModels/ResponseDto/FeedDto.cs new file mode 100644 index 0000000000..154bc0c437 --- /dev/null +++ b/web/ASC.Web.Api/ApiModels/ResponseDto/FeedDto.cs @@ -0,0 +1,50 @@ +// (c) Copyright Ascensio System SIA 2010-2022 +// +// This program is a free software product. +// You can redistribute it and/or modify it under the terms +// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software +// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended +// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of +// any third-party rights. +// +// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see +// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html +// +// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021. +// +// The interactive user interfaces in modified source and object code versions of the Program must +// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3. +// +// Pursuant to Section 7(b) of the License you must retain the original Product logo when +// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under +// trademark law for use of our trademarks. +// +// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing +// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 +// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode + +namespace ASC.Web.Api.ApiModels.ResponseDto; + +public class FeedDto : IMapFrom +{ + public ApiDateTime AggregatedDate { get; set; } + public ApiDateTime CreatedDate { get; set; } + public ApiDateTime ModifiedDate { get; set; } + public ApiDateTime TimeReaded { get; set; } + public bool IsToday { get; set; } + public bool IsTomorrow { get; set; } + public bool IsYesterday { get; set; } + public IEnumerable GroupedFeeds { get; set; } + public string Json { get; set; } + public string GroupId { get; set; } + public string Module { get; set; } + + public void Mapping(Profile profile) + { + profile.CreateMap() + .ConvertUsing(); + + profile.CreateMap(); + } +} \ No newline at end of file diff --git a/web/ASC.Web.Api/GlobalUsings.cs b/web/ASC.Web.Api/GlobalUsings.cs index 066b8a4635..5df2673b23 100644 --- a/web/ASC.Web.Api/GlobalUsings.cs +++ b/web/ASC.Web.Api/GlobalUsings.cs @@ -83,7 +83,9 @@ global using ASC.Data.Backup.EF.Context; global using ASC.Data.Storage; global using ASC.Data.Storage.Configuration; global using ASC.Data.Storage.Encryption; -global using ASC.Data.Storage.Migration; +global using ASC.Data.Storage.Migration; +global using ASC.Feed; +global using ASC.Feed.Data; global using ASC.FederatedLogin; global using ASC.FederatedLogin.Helpers; global using ASC.FederatedLogin.LoginProviders; @@ -102,6 +104,7 @@ global using ASC.Web.Api.ApiModels.RequestsDto; global using ASC.Web.Api.ApiModels.ResponseDto; global using ASC.Web.Api.Core; global using ASC.Web.Api.Log; +global using ASC.Web.Api.Mapping; global using ASC.Web.Api.Models; global using ASC.Web.Api.Routing; global using ASC.Web.Core; diff --git a/web/ASC.Web.Api/Mapping/FeedMapperAction.cs b/web/ASC.Web.Api/Mapping/FeedMapperAction.cs new file mode 100644 index 0000000000..eef3adfd71 --- /dev/null +++ b/web/ASC.Web.Api/Mapping/FeedMapperAction.cs @@ -0,0 +1,43 @@ +// (c) Copyright Ascensio System SIA 2010-2022 +// +// This program is a free software product. +// You can redistribute it and/or modify it under the terms +// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software +// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended +// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of +// any third-party rights. +// +// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see +// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html +// +// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021. +// +// The interactive user interfaces in modified source and object code versions of the Program must +// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3. +// +// Pursuant to Section 7(b) of the License you must retain the original Product logo when +// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under +// trademark law for use of our trademarks. +// +// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing +// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 +// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode + +namespace ASC.Web.Api.Mapping; + +[Scope] +public class DateTimeMappingConverter : ITypeConverter +{ + private readonly ApiDateTimeHelper _apiDateTimeHelper; + + public DateTimeMappingConverter(ApiDateTimeHelper apiDateTimeHelper) + { + _apiDateTimeHelper = apiDateTimeHelper; + } + + public ApiDateTime Convert(DateTime source, ApiDateTime destination, ResolutionContext context) + { + return _apiDateTimeHelper.Get(source); + } +} \ No newline at end of file From 7c3af54718fa96c19081c63d05b32614247fc537 Mon Sep 17 00:00:00 2001 From: MaksimChegulov Date: Thu, 1 Sep 2022 19:38:26 +0300 Subject: [PATCH 015/110] Web.Api: fix --- web/ASC.Web.Api/Api/FeedController.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/ASC.Web.Api/Api/FeedController.cs b/web/ASC.Web.Api/Api/FeedController.cs index c9697efec0..73b547fb29 100644 --- a/web/ASC.Web.Api/Api/FeedController.cs +++ b/web/ASC.Web.Api/Api/FeedController.cs @@ -29,6 +29,7 @@ namespace ASC.Web.Api.Controllers; [Scope] [DefaultRoute] [ApiController] +[ControllerName("feeds")] public class FeedController : ControllerBase { private readonly FeedReadedDataProvider _feedReadedDataProvider; @@ -57,7 +58,7 @@ public class FeedController : ControllerBase _mapper = mapper; } - private string Key => $"newfeedscount/{_securityContext}"; + private string Key => $"newfeedscount/{_securityContext.CurrentAccount.ID}"; /// ///Opens feeds for reading. From 50175aad40a364e32d9f02480b0b05eb49f2bf24 Mon Sep 17 00:00:00 2001 From: MaksimChegulov Date: Thu, 1 Sep 2022 20:22:59 +0300 Subject: [PATCH 016/110] Web.Api: added id, withRelated for feed filter --- common/ASC.Feed/Core/FeedApiFilter.cs | 5 ++++- common/ASC.Feed/Data/FeedAggregateDataProvider.cs | 15 ++++++++++++++- web/ASC.Web.Api/Api/FeedController.cs | 9 +++++++-- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/common/ASC.Feed/Core/FeedApiFilter.cs b/common/ASC.Feed/Core/FeedApiFilter.cs index 9e3a441fde..e5c5bf3dc9 100644 --- a/common/ASC.Feed/Core/FeedApiFilter.cs +++ b/common/ASC.Feed/Core/FeedApiFilter.cs @@ -28,6 +28,7 @@ namespace ASC.Feed; public class FeedApiFilter { + public string Id { get; set; } public string Product { get; set; } public DateTime From { get; set; } public DateTime To { get; set; } @@ -36,4 +37,6 @@ public class FeedApiFilter public Guid Author { get; set; } public string[] SearchKeys { get; set; } public bool OnlyNew { get; set; } -} + public bool WithoutMe { get; set; } + public bool WithRelated { get; set; } +} \ No newline at end of file diff --git a/common/ASC.Feed/Data/FeedAggregateDataProvider.cs b/common/ASC.Feed/Data/FeedAggregateDataProvider.cs index f3114aa88f..6d85da7608 100644 --- a/common/ASC.Feed/Data/FeedAggregateDataProvider.cs +++ b/common/ASC.Feed/Data/FeedAggregateDataProvider.cs @@ -205,13 +205,26 @@ public class FeedAggregateDataProvider using var feedDbContext = _dbContextFactory.CreateDbContext(); var q = feedDbContext.FeedAggregates .Where(r => r.Tenant == _tenantManager.GetCurrentTenant().Id) - .Where(r => r.ModifiedBy != _authContext.CurrentAccount.ID) .Join(feedDbContext.FeedUsers, a => a.Id, b => b.FeedId, (aggregates, users) => new { aggregates, users }) .Where(r => r.users.UserId == _authContext.CurrentAccount.ID) .OrderByDescending(r => r.aggregates.ModifiedDate) .Skip(filter.Offset) .Take(filter.Max); + if (filter.WithoutMe) + { + q = q.Where(r => r.aggregates.ModifiedBy != _authContext.CurrentAccount.ID); + } + + if (filter.WithRelated && !string.IsNullOrEmpty(filter.Id)) + { + q = q.Where(r => r.aggregates.Id == filter.Id || r.aggregates.ContextId == filter.Id); + } + else if (!string.IsNullOrEmpty(filter.Id)) + { + q = q.Where(r => r.aggregates.Id == filter.Id); + } + if (filter.OnlyNew) { q = q.Where(r => r.aggregates.AggregateDate >= filter.From); diff --git a/web/ASC.Web.Api/Api/FeedController.cs b/web/ASC.Web.Api/Api/FeedController.cs index 73b547fb29..a64f79a8cd 100644 --- a/web/ASC.Web.Api/Api/FeedController.cs +++ b/web/ASC.Web.Api/Api/FeedController.cs @@ -29,7 +29,6 @@ namespace ASC.Web.Api.Controllers; [Scope] [DefaultRoute] [ApiController] -[ControllerName("feeds")] public class FeedController : ControllerBase { private readonly FeedReadedDataProvider _feedReadedDataProvider; @@ -87,21 +86,27 @@ public class FeedController : ControllerBase ///List of filtered feeds [HttpGet("filter")] public object GetFeed( + string id, string product, ApiDateTime from, ApiDateTime to, Guid? author, bool? onlyNew, + bool? withoutMe, + bool? withRelated, ApiDateTime timeReaded) { var filter = new FeedApiFilter { + Id = id, Product = product, Offset = Convert.ToInt32(_apiContext.StartIndex), Max = Convert.ToInt32(_apiContext.Count) - 1, Author = author ?? Guid.Empty, SearchKeys = _apiContext.FilterValues, - OnlyNew = onlyNew.HasValue && onlyNew.Value + OnlyNew = onlyNew.HasValue && onlyNew.Value, + WithoutMe = withoutMe.HasValue && withoutMe.Value, + WithRelated = withRelated.HasValue && withRelated.Value, }; if (from != null && to != null) From 181cd0bc43ea67ba8b165e068b0b09125421838f Mon Sep 17 00:00:00 2001 From: TimofeyBoyko Date: Fri, 2 Sep 2022 11:22:26 +0300 Subject: [PATCH 017/110] Web:Components: init selector --- packages/components/index.js | 1 + .../components/selector/Selector.stories.js | 139 ++++++++ .../components/selector/StyledSelector.js | 81 +++++ packages/components/selector/index.js | 312 ++++++++++++++++++ .../selector/sub-components/body.js | 108 ++++++ .../selector/sub-components/footer.js | 87 +++++ .../selector/sub-components/header.js | 22 ++ .../selector/sub-components/item.js | 133 ++++++++ .../selector/sub-components/search.js | 17 + .../selector/sub-components/select-all.js | 77 +++++ 10 files changed, 977 insertions(+) create mode 100644 packages/components/selector/Selector.stories.js create mode 100644 packages/components/selector/StyledSelector.js create mode 100644 packages/components/selector/index.js create mode 100644 packages/components/selector/sub-components/body.js create mode 100644 packages/components/selector/sub-components/footer.js create mode 100644 packages/components/selector/sub-components/header.js create mode 100644 packages/components/selector/sub-components/item.js create mode 100644 packages/components/selector/sub-components/search.js create mode 100644 packages/components/selector/sub-components/select-all.js diff --git a/packages/components/index.js b/packages/components/index.js index 3b37d02400..33bf66c33e 100644 --- a/packages/components/index.js +++ b/packages/components/index.js @@ -64,3 +64,4 @@ export * as Themes from "./themes"; export { default as Portal } from "./portal"; export { default as TableContainer } from "./table-container"; export { default as Slider } from "./slider"; +export { default as Selector } from "./Selector"; diff --git a/packages/components/selector/Selector.stories.js b/packages/components/selector/Selector.stories.js new file mode 100644 index 0000000000..b5b0ef59b9 --- /dev/null +++ b/packages/components/selector/Selector.stories.js @@ -0,0 +1,139 @@ +import React from "react"; + +import Selector from "./"; + +export default { + title: "Components/Selector", + component: Selector, + parameters: { + docs: { + description: { + component: + "Selector for displaying items list of people or room selector", + }, + }, + }, +}; + +function makeName() { + var result = ""; + var characters = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + var charactersLength = characters.length; + for (var i = 0; i < 15; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; +} + +const getItems = (count) => { + const items = []; + + for (let i = 0; i < count / 2; i++) { + items.push({ + key: `user_${i}`, + id: `user_${i}`, + label: makeName(), + avatar: "static/images/room.archive.svg", + }); + } + + for (let i = 0; i < count / 2; i++) { + items.push({ + key: `room_${i}`, + id: `room_${i}`, + label: makeName(), + icon: "static/images/icons/32/rooms/custom.svg", + }); + } + + return items; +}; + +const getAccessRights = () => { + const accesses = [ + { + key: "roomManager", + label: "Room manager", + access: 0, + }, + { + key: "editor", + label: "Editor", + access: 1, + }, + { + key: "formFiller", + label: "Form filler", + access: 2, + }, + { + key: "reviewer", + label: "Reviewer", + access: 3, + }, + { + key: "commentator", + label: "Commentator", + access: 4, + }, + { + key: "viewer", + label: "Viewer", + access: 5, + }, + ]; + + return accesses; +}; + +const Template = (args) => { + return ( +
+ +
+ ); +}; + +export const Default = Template.bind({}); + +const items = getItems(1000); + +const selectedItems = [items[0], items[3], items[7]]; + +const accessRights = getAccessRights(); + +const selectedAccessRight = accessRights[1]; + +Default.args = { + height: "485px", // container height + headerLabel: "Room list", + onBackClick: () => console.log("back click"), + searchPlaceholder: "Search", + searchValue: "", + items, + onSelect: (item) => console.log("select " + item), + isMultiSelect: false, + selectedItems, + acceptButtonLabel: "Add", + onAccept: (items, access) => console.log("accept " + items + access), + withSelectAll: false, + selectAllLabel: "All accounts", + selectAllIcon: "static/images/room.archive.svg", + onSelectAll: () => console.log("select all"), + withAccessRights: true, + accessRights, + selectedAccessRight, + onAccessRightsChange: (access) => + console.log("access rights change " + access), + withCancelButton: false, + cancelButtonLabel: "Cancel", + onCancel: () => console.log("cancel"), +}; diff --git a/packages/components/selector/StyledSelector.js b/packages/components/selector/StyledSelector.js new file mode 100644 index 0000000000..61a4d120da --- /dev/null +++ b/packages/components/selector/StyledSelector.js @@ -0,0 +1,81 @@ +import styled, { css } from "styled-components"; + +const StyledSelector = styled.div` + width: 100%; + height: 100%; + + display: flex; + flex-direction: column; + + overflow: hidden; +`; + +const StyledSelectorHeader = styled.div` + width: calc(100% - 32px); + min-height: 53px; + height: 53px; + max-height: 53px; + + padding: 0 16px; + + border-bottom: 1px solid #eceef1; + + display: flex; + align-items: center; + + .arrow-button { + cursor: pointer; + margin-right: 12px; + } + + .heading-text { + font-weight: 700; + font-size: 21px; + line-height: 28px; + } +`; + +const StyledSelectorBody = styled.div` + width: 100%; + height: 100%; + + padding: 16px 0; + + box-sizing: border-box; + + .search-input { + padding: 0 16px; + + margin-bottom: 12px; + } +`; + +const StyledSelectorFooter = styled.div` + width: calc(100% - 32px); + max-height: 73px; + height: 73px; + min-height: 73px; + + padding: 0 16px; + + display: flex; + align-items: center; + justify-content: space-between; + + gap: 8px; + + border-top: 1px solid #eceef1; + + .button { + min-height: 40px; + + margin-bottom: 2px; + } +`; + +export { + StyledSelector, + StyledSelectorHeader, + StyledSelectorBody, + StyledSelectorFooter, +}; diff --git a/packages/components/selector/index.js b/packages/components/selector/index.js new file mode 100644 index 0000000000..a704a661c1 --- /dev/null +++ b/packages/components/selector/index.js @@ -0,0 +1,312 @@ +import React from "react"; +import PropTypes from "prop-types"; + +import Header from "./sub-components/header"; +import Body from "./sub-components/body"; +import Footer from "./sub-components/footer"; + +import { StyledSelector } from "./StyledSelector"; + +const Selector = ({ + id, + className, + style, + headerLabel, + onBackClick, + searchPlaceholder, + searchValue, + onSearch, + onClearSearch, + items, + onSelect, + isMultiSelect, + selectedItems, + acceptButtonLabel, + onAccept, + withSelectAll, + selectAllLabel, + selectAllIcon, + onSelectAll, + withAccessRights, + accessRights, + selectedAccessRight, + onAccessRightsChange, + withCancelButton, + cancelButtonLabel, + onCancel, +}) => { + const [isLoading, setIsLoading] = React.useState(false); + + const [footerVisible, setFooterVisible] = React.useState(false); + + const [isSearch, setIsSearch] = React.useState(false); + + const [renderedItems, setRenderedItems] = React.useState([]); + const [newSelectedItems, setNewSelectedItems] = React.useState([]); + + const [newSelectedAccessRights, setNewSelectedAccessRights] = React.useState( + {} + ); + + const onBackClickAction = React.useCallback(() => { + onBackClick && onBackClick(); + }, [onBackClick]); + + const onSearchAction = React.useCallback( + (value) => { + onSearch && onSearch(value); + if (value) { + setIsSearch(true); + } else { + setIsSearch(false); + } + }, + [onSearch] + ); + + const onClearSearchAction = React.useCallback(() => { + onClearSearch && onClearSearch(); + setIsSearch(false); + }, [onClearSearch]); + + const onSelectAction = (item) => { + onSelect && + onSelect({ + id: item.id, + avatar: item.avatar, + icon: item.icon, + label: item.label, + }); + + if (isMultiSelect) { + setRenderedItems((value) => { + const idx = value.findIndex((x) => item.id === x.id); + + const newValue = value.map((item) => ({ ...item })); + + if (idx === -1) return newValue; + + newValue[idx].isSelected = !value[idx].isSelected; + + return newValue; + }); + + if (item.isSelected) { + setNewSelectedItems((value) => { + const newValue = value + .filter((x) => x.id !== item.id) + .map((x) => ({ ...x })); + compareSelectedItems(newValue); + return newValue; + }); + } else { + setNewSelectedItems((value) => { + value.push({ + id: item.id, + avatar: item.avatar, + icon: item.icon, + label: item.label, + }); + + compareSelectedItems(value); + + return value; + }); + } + } else { + setRenderedItems((value) => { + const idx = value.findIndex((x) => item.id === x.id); + + const newValue = value.map((item) => ({ ...item, isSelected: false })); + + if (idx === -1) return newValue; + + newValue[idx].isSelected = !item.isSelected; + + return newValue; + }); + + const newItem = { + id: item.id, + avatar: item.avatar, + icon: item.icon, + label: item.label, + }; + + setNewSelectedItems([newItem]); + compareSelectedItems([newItem]); + } + }; + + const onSelectAllAction = React.useCallback(() => { + onSelectAll && onSelectAll(); + if (newSelectedItems.length === 0) { + const cloneItems = items.map((x) => ({ ...x })); + + const cloneRenderedItems = items.map((x) => ({ ...x, isSelected: true })); + + setRenderedItems(cloneRenderedItems); + setNewSelectedItems(cloneItems); + compareSelectedItems(cloneItems); + } else { + const cloneRenderedItems = items.map((x) => ({ + ...x, + isSelected: false, + })); + + setRenderedItems(cloneRenderedItems); + setNewSelectedItems([]); + compareSelectedItems([]); + } + }, [items, newSelectedItems]); + + const onAcceptAction = React.useCallback(() => { + onAccept && onAccept(newSelectedItems, newSelectedAccessRights); + }, [newSelectedItems, newSelectedAccessRights]); + + const onCancelAction = React.useCallback(() => { + onCancel && onCancel(); + }, [onCancel]); + + const onChangeAccessRightsAction = React.useCallback( + (access) => { + setNewSelectedAccessRights({ ...access }); + onAccessRightsChange && onAccessRightsChange(access); + }, + [onAccessRightsChange] + ); + + const compareSelectedItems = React.useCallback( + (newList) => { + let isEqual = true; + + if (selectedItems.length !== newList.length) { + return setFooterVisible(true); + } + + newList.forEach((item) => { + isEqual = selectedItems.some((x) => x.id === item.id); + }); + + return setFooterVisible(!isEqual); + }, + [selectedItems] + ); + + React.useEffect(() => { + setNewSelectedAccessRights({ ...selectedAccessRight }); + }, [selectedAccessRight]); + + React.useEffect(() => { + if (items && selectedItems) { + if (selectedItems.length === 0 || !isMultiSelect) { + const cloneItems = items.map((x) => ({ ...x, isSelected: false })); + return setRenderedItems(cloneItems); + } + + const newItems = items.map((item) => { + const { id } = item; + + const isSelected = selectedItems.some( + (selectedItem) => selectedItem.id === id + ); + + return { ...item, isSelected }; + }); + + const cloneSelectedItems = selectedItems.map((item) => ({ ...item })); + + setRenderedItems(newItems); + setNewSelectedItems(cloneSelectedItems); + compareSelectedItems(cloneSelectedItems); + } + }, [items, selectedItems, isMultiSelect, compareSelectedItems]); + + return ( + +
+ + + + {footerVisible && ( +