From d166f9cca311c6edcf351a8c724fc2f8dbccc7e9 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Mon, 8 May 2017 22:14:01 -0400 Subject: [PATCH] add/edit group with collection ids --- src/Api/Controllers/GroupsController.cs | 32 +++++++++++- .../Models/Api/Request/GroupRequestModel.cs | 2 + .../Models/Api/Response/GroupResponseModel.cs | 12 +++++ src/Core/Repositories/IGroupRepository.cs | 3 ++ .../Repositories/SqlServer/GroupRepository.cs | 52 +++++++++++++++++++ src/Sql/Sql.sqlproj | 3 ++ .../Group_CreateWithCollections.sql | 34 ++++++++++++ .../Group_ReadWithCollectionsById.sql | 15 ++++++ .../Group_UpdateWithCollections.sql | 40 ++++++++++++++ 9 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 src/Sql/dbo/Stored Procedures/Group_CreateWithCollections.sql create mode 100644 src/Sql/dbo/Stored Procedures/Group_ReadWithCollectionsById.sql create mode 100644 src/Sql/dbo/Stored Procedures/Group_UpdateWithCollections.sql diff --git a/src/Api/Controllers/GroupsController.cs b/src/Api/Controllers/GroupsController.cs index 424ce4e80e..60b5ddec7a 100644 --- a/src/Api/Controllers/GroupsController.cs +++ b/src/Api/Controllers/GroupsController.cs @@ -41,6 +41,18 @@ namespace Bit.Api.Controllers return new GroupResponseModel(group); } + [HttpGet("{id}/details")] + public async Task GetDetails(string orgId, string id) + { + var groupDetails = await _groupRepository.GetByIdWithCollectionsAsync(new Guid(id)); + if(groupDetails?.Item1 == null || !_currentContext.OrganizationAdmin(groupDetails.Item1.OrganizationId)) + { + throw new NotFoundException(); + } + + return new GroupDetailsResponseModel(groupDetails.Item1, groupDetails.Item2); + } + [HttpGet("")] public async Task> Get(string orgId) { @@ -65,7 +77,15 @@ namespace Bit.Api.Controllers } var group = model.ToGroup(orgIdGuid); - await _groupRepository.CreateAsync(group); + if(model.CollectionIds == null) + { + await _groupRepository.CreateAsync(group); + } + else + { + await _groupRepository.CreateAsync(group, model.CollectionIds.Select(c => new Guid(c))); + } + return new GroupResponseModel(group); } @@ -79,7 +99,15 @@ namespace Bit.Api.Controllers throw new NotFoundException(); } - await _groupRepository.ReplaceAsync(model.ToGroup(group)); + if(model.CollectionIds == null) + { + await _groupRepository.ReplaceAsync(model.ToGroup(group)); + } + else + { + await _groupRepository.ReplaceAsync(model.ToGroup(group), model.CollectionIds.Select(c => new Guid(c))); + } + return new GroupResponseModel(group); } diff --git a/src/Core/Models/Api/Request/GroupRequestModel.cs b/src/Core/Models/Api/Request/GroupRequestModel.cs index c5ecfbb672..27ddb21f2e 100644 --- a/src/Core/Models/Api/Request/GroupRequestModel.cs +++ b/src/Core/Models/Api/Request/GroupRequestModel.cs @@ -2,6 +2,7 @@ using System.ComponentModel.DataAnnotations; using Bit.Core.Models.Table; using Newtonsoft.Json; +using System.Collections.Generic; namespace Bit.Core.Models.Api { @@ -10,6 +11,7 @@ namespace Bit.Core.Models.Api [Required] [StringLength(300)] public string Name { get; set; } + public IEnumerable CollectionIds { get; set; } public Group ToGroup(Guid orgId) { diff --git a/src/Core/Models/Api/Response/GroupResponseModel.cs b/src/Core/Models/Api/Response/GroupResponseModel.cs index 1b2e94ae78..6c7dbe83b7 100644 --- a/src/Core/Models/Api/Response/GroupResponseModel.cs +++ b/src/Core/Models/Api/Response/GroupResponseModel.cs @@ -1,5 +1,6 @@ using System; using Bit.Core.Models.Table; +using System.Collections.Generic; namespace Bit.Core.Models.Api { @@ -22,4 +23,15 @@ namespace Bit.Core.Models.Api public string OrganizationId { get; set; } public string Name { get; set; } } + + public class GroupDetailsResponseModel : GroupResponseModel + { + public GroupDetailsResponseModel(Group group, IEnumerable collectionIds) + : base(group, "groupDetails") + { + CollectionIds = collectionIds; + } + + public IEnumerable CollectionIds { get; set; } + } } diff --git a/src/Core/Repositories/IGroupRepository.cs b/src/Core/Repositories/IGroupRepository.cs index d23950e46d..0fffdffd3e 100644 --- a/src/Core/Repositories/IGroupRepository.cs +++ b/src/Core/Repositories/IGroupRepository.cs @@ -7,6 +7,9 @@ namespace Bit.Core.Repositories { public interface IGroupRepository : IRepository { + Task>> GetByIdWithCollectionsAsync(Guid id); Task> GetManyByOrganizationIdAsync(Guid organizationId); + Task CreateAsync(Group obj, IEnumerable collectionIds); + Task ReplaceAsync(Group obj, IEnumerable collectionIds); } } diff --git a/src/Core/Repositories/SqlServer/GroupRepository.cs b/src/Core/Repositories/SqlServer/GroupRepository.cs index 0dbe97e0cc..c2f807bc2a 100644 --- a/src/Core/Repositories/SqlServer/GroupRepository.cs +++ b/src/Core/Repositories/SqlServer/GroupRepository.cs @@ -6,6 +6,8 @@ using Dapper; using System.Data; using System.Data.SqlClient; using System.Linq; +using Newtonsoft.Json; +using Bit.Core.Utilities; namespace Bit.Core.Repositories.SqlServer { @@ -19,6 +21,22 @@ namespace Bit.Core.Repositories.SqlServer : base(connectionString) { } + public async Task>> GetByIdWithCollectionsAsync(Guid id) + { + using(var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.QueryMultipleAsync( + $"[{Schema}].[Group_ReadWithCollectionsById]", + new { Id = id }, + commandType: CommandType.StoredProcedure); + + var group = await results.ReadFirstOrDefaultAsync(); + var colletionIds = (await results.ReadAsync()).ToList(); + + return new Tuple>(group, colletionIds); + } + } + public async Task> GetManyByOrganizationIdAsync(Guid organizationId) { using(var connection = new SqlConnection(ConnectionString)) @@ -31,5 +49,39 @@ namespace Bit.Core.Repositories.SqlServer return results.ToList(); } } + + public async Task CreateAsync(Group obj, IEnumerable collectionIds) + { + obj.SetNewId(); + var objWithCollections = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(obj)); + objWithCollections.CollectionIds = collectionIds.ToGuidIdArrayTVP(); + + using(var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.ExecuteAsync( + $"[{Schema}].[Group_CreateWithCollections]", + objWithCollections, + commandType: CommandType.StoredProcedure); + } + } + + public async Task ReplaceAsync(Group obj, IEnumerable collectionIds) + { + var objWithCollections = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(obj)); + objWithCollections.CollectionIds = collectionIds.ToGuidIdArrayTVP(); + + using(var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.ExecuteAsync( + $"[{Schema}].[Group_UpdateWithCollections]", + objWithCollections, + commandType: CommandType.StoredProcedure); + } + } + + public class GroupWithCollections : Group + { + public DataTable CollectionIds { get; set; } + } } } diff --git a/src/Sql/Sql.sqlproj b/src/Sql/Sql.sqlproj index 899abb918f..7258756273 100644 --- a/src/Sql/Sql.sqlproj +++ b/src/Sql/Sql.sqlproj @@ -184,5 +184,8 @@ + + + \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/Group_CreateWithCollections.sql b/src/Sql/dbo/Stored Procedures/Group_CreateWithCollections.sql new file mode 100644 index 0000000000..1a08d26ea0 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/Group_CreateWithCollections.sql @@ -0,0 +1,34 @@ +CREATE PROCEDURE [dbo].[Group_CreateWithCollections] + @Id UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Name VARCHAR(MAX), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @CollectionIds AS [dbo].[GuidIdArray] READONLY +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[Group_Create] @Id, @OrganizationId, @Name, @CreationDate, @RevisionDate + + ;WITH [AvailableCollectionsCTE] AS( + SELECT + [Id] + FROM + [dbo].[Collection] + WHERE + [OrganizationId] = @OrganizationId + ) + INSERT INTO [dbo].[CollectionGroup] + ( + [CollectionId], + [GroupId] + ) + SELECT + [Id], + @Id + FROM + @CollectionIds + WHERE + [Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) +END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/Group_ReadWithCollectionsById.sql b/src/Sql/dbo/Stored Procedures/Group_ReadWithCollectionsById.sql new file mode 100644 index 0000000000..fb9d2951e4 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/Group_ReadWithCollectionsById.sql @@ -0,0 +1,15 @@ +CREATE PROCEDURE [dbo].[Group_ReadWithCollectionsById] + @Id UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[Group_ReadById] @Id + + SELECT + [CollectionId] + FROM + [dbo].[CollectionGroup] + WHERE + [GroupId] = @Id +END \ No newline at end of file diff --git a/src/Sql/dbo/Stored Procedures/Group_UpdateWithCollections.sql b/src/Sql/dbo/Stored Procedures/Group_UpdateWithCollections.sql new file mode 100644 index 0000000000..c8c8a67740 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/Group_UpdateWithCollections.sql @@ -0,0 +1,40 @@ +CREATE PROCEDURE [dbo].[Group_UpdateWithCollections] + @Id UNIQUEIDENTIFIER, + @OrganizationId UNIQUEIDENTIFIER, + @Name VARCHAR(MAX), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @CollectionIds AS [dbo].[GuidIdArray] READONLY +AS +BEGIN + SET NOCOUNT ON + + EXEC [dbo].[Group_Update] @Id, @OrganizationId, @Name, @CreationDate, @RevisionDate + + ;WITH [AvailableCollectionsCTE] AS( + SELECT + Id + FROM + [dbo].[Collection] + WHERE + OrganizationId = @OrganizationId + ) + MERGE + [dbo].[CollectionGroup] AS [Target] + USING + @CollectionIds AS [Source] + ON + [Target].[CollectionId] = [Source].[Id] + AND [Target].[GroupId] = @Id + WHEN NOT MATCHED BY TARGET + AND [Source].[Id] IN (SELECT [Id] FROM [AvailableCollectionsCTE]) THEN + INSERT VALUES + ( + [Source].[Id], + @Id + ) + WHEN NOT MATCHED BY SOURCE + AND [Target].[GroupId] = @Id THEN + DELETE + ; +END \ No newline at end of file